(function () { "use strict"; var W = 1920, H = 1080, TARGET = 325000, SEED = 8262707; var photoLayer, sourceLayer, paintLayer, painted = false, skyMask; var REFERENCE_DATA = "__REFERENCE_DATA__"; // Calibrated from the user-selected atmospheric proof, then widened with // a few pearly notes from the Rouen reference palette. var MONET_LIGHT = ["#cbc2a4", "#c5b396", "#c2a37d", "#b3aa95", "#d3c9b2", "#d6d2c7"]; var MONET_COOL = ["#788688", "#697b82", "#66757a", "#8d9490", "#817e7a", "#8fadcB"]; var MONET_STONE = ["#c2a37d", "#c5b396", "#b3aa95", "#968a7c", "#817e7a", "#ccae70"]; var MONET_DARK = ["#526975", "#66757a", "#6e7178", "#817e7a", "#756c6d"]; var MONET_ROSE = ["#b9827f", "#c69587", "#a87978", "#c2a37d", "#9f8582"]; function embeddedReferenceCanvas() { var binary = atob(REFERENCE_DATA), bytes = new Uint8ClampedArray(binary.length); for (var i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i); // Chroma-key only the photographed blue sky, retaining every dome, // statue, cornice, and balustrade pixel. A continuous warm-to-cool field // removes the photographic rectangle without drawing a hard replacement // shape around the architecture. skyMask = new Uint8Array(960 * 540); for (var y = 0; y < 310; y += 1) { for (var x = 0; x < 960; x += 1) { var p = (y * 960 + x) * 4, r = bytes[p], g = bytes[p + 1], b = bytes[p + 2]; if (b > 118 && b > r * 1.28 && b > g * 1.05) { skyMask[y * 960 + x] = 1; var t = x / 959, warm = Math.exp(-Math.pow((t - .22) / .34, 2)); var cloud = Math.sin(x * .019 + y * .037) * 5 + Math.sin(x * .007 - y * .021) * 4; bytes[p] = Math.max(0, Math.min(255, 159 + warm * 55 + cloud)); bytes[p + 1] = Math.max(0, Math.min(255, 181 + warm * 36 + cloud)); bytes[p + 2] = Math.max(0, Math.min(255, 201 + warm * 10 + cloud * .5)); } } } var canvas = document.createElement("canvas"); canvas.width = 960; canvas.height = 540; canvas.getContext("2d").putImageData(new ImageData(bytes, 960, 540), 0, 0); return canvas; } function glow(ctx, x, y, radius, inner, outer) { var g = ctx.createRadialGradient(x, y, 0, x, y, radius); g.addColorStop(0, inner); g.addColorStop(1, outer); ctx.fillStyle = g; ctx.fillRect(x - radius, y - radius, radius * 2, radius * 2); } function buildReference() { photoLayer.clear(); photoLayer.drawingContext.drawImage(embeddedReferenceCanvas(), 0, 0, W, H); sourceLayer.clear(); sourceLayer.image(photoLayer, 0, 0, W, H); var c = sourceLayer.drawingContext; // Pull the midday photograph toward a pearly after-rain key without // flattening its architecture. All passages are soft gradients; there are // no rectangular cloud masks for the sampler to reinforce. c.save(); c.globalCompositeOperation = "screen"; var veil = c.createLinearGradient(0, 0, W, H); veil.addColorStop(0, "rgba(239,230,205,.28)"); veil.addColorStop(.45, "rgba(205,217,221,.16)"); veil.addColorStop(1, "rgba(189,201,212,.08)"); c.fillStyle = veil; c.fillRect(0, 0, W, H); glow(c, 360, 175, 520, "rgba(255,223,165,.48)", "rgba(255,232,182,0)"); c.restore(); c.save(); c.globalCompositeOperation = "soft-light"; glow(c, 1580, 120, 650, "rgba(82,111,145,.62)", "rgba(82,111,145,0)"); glow(c, 1210, 500, 470, "rgba(229,190,145,.38)", "rgba(229,190,145,0)"); c.restore(); // A compressed, softened echo of the facade becomes the wet piazza. sourceLayer.push(); sourceLayer.translate(0, 1800); sourceLayer.scale(1, -1); sourceLayer.tint(196, 118); sourceLayer.image(photoLayer, 0, 720, W, 360, 0, 420, W, 420); sourceLayer.noTint(); sourceLayer.pop(); c.save(); var wet = c.createLinearGradient(0, 690, 0, H); wet.addColorStop(0, "rgba(151,175,184,.30)"); wet.addColorStop(.55, "rgba(102,132,151,.42)"); wet.addColorStop(1, "rgba(78,105,128,.58)"); c.fillStyle = wet; c.fillRect(0, 690, W, H - 690); c.globalCompositeOperation = "screen"; glow(c, 1110, 760, 520, "rgba(238,211,167,.28)", "rgba(238,211,167,0)"); c.restore(); window.__referenceAttribution = { title: "Saint Peter's Square", creator: "Fatima ezzahra el bouabi", source: "https://commons.wikimedia.org/wiki/File:Saint_Peter%E2%80%99s_square.jpg", license: "CC0 1.0", use: "architectural geometry only; reframed, recolored, reflected, and repainted" }; } function frac(x) { return x - Math.floor(x); } function colorFor(sample, palette, amount, jitter) { return impressionist.sampledColor(sample, { palette: palette, amount: amount, jitter: jitter }); } function opticalField(spec) { impressionist.setLayer(spec.layer); var phi = 0.6180339887498949, psi = 0.7548776662466927; var painted = 0, attempt = 0; while (painted < spec.calls) { var i = attempt; attempt += 1; if (attempt > spec.calls * 30) throw new Error("optical field could not fill " + spec.layer); var u = frac((i + 1) * phi + spec.phase), v = frac((i + 1) * psi + spec.phase * .71); var x = spec.x + u * spec.w, y = spec.y + v * spec.h; var px = impressionistSampler.sample(x, y); if (spec.accept && !spec.accept(px, x, y)) continue; var grad = impressionistSampler.orientation(x, y, spec.gradientRadius || 3); var angle = typeof spec.angle === "function" ? spec.angle(x, y, grad, i) : grad.angle; angle += (impressionist.random() - .5) * 2 * spec.angleJitter; var depth = Math.max(0, Math.min(1, y / H)); var lengthRange = typeof spec.length === "function" ? spec.length(x, y, i) : spec.length; var widthRange = typeof spec.width === "function" ? spec.width(x, y, i) : spec.width; var opacityRange = typeof spec.opacity === "function" ? spec.opacity(x, y, i) : spec.opacity; var length = lengthRange[0] + (lengthRange[1] - lengthRange[0]) * impressionist.random(); var width = widthRange[0] + (widthRange[1] - widthRange[0]) * impressionist.random(); var opacity = opacityRange[0] + (opacityRange[1] - opacityRange[0]) * impressionist.random(); var scale = spec.distanceScale ? impressionist.distanceScale(depth, { near: spec.distanceScale[0], far: spec.distanceScale[1], maxDistance: 1 }) : 1; impressionist.curvedBristle(x, y, { color: colorFor(px, spec.palette, spec.paletteAmount, spec.paletteJitter), opacity: opacity, length: length * scale, width: width * scale, angle: angle, bend: (impressionist.random() - .5) * 2 * spec.bend, segments: 5, bristles: spec.bristles, spread: spec.spread, type: spec.type }); painted += 1; } } function blueAir(px, x, y) { var sx = Math.max(0, Math.min(959, Math.floor(x / W * 960))); var sy = Math.max(0, Math.min(539, Math.floor(y / H * 540))); return Boolean(skyMask[sy * 960 + sx]); } function observedStone(px, x, y) { return y > 135 && y < 850 && !blueAir(px, x, y); } function paint() { var ctx = paintLayer.drawingContext; impressionist.reset({ seed: SEED, ctx: ctx }); impressionistSampler.reset({ seed: SEED, source: sourceLayer.drawingContext, width: W, height: H }); ctx.fillStyle = "#8799a3"; ctx.fillRect(0, 0, W, H); opticalField({ layer: "pearly-ground", type: "broad-underpainting", calls: 18000, bristles: 1, x: 0, y: 0, w: W, h: H, phase: .17, palette: MONET_COOL.concat(MONET_LIGHT), paletteAmount: .42, paletteJitter: .10, length: [28, 78], width: [6, 15], opacity: [.20, .36], angleJitter: 22, bend: 9, spread: 2, distanceScale: [1.15, .72] }); opticalField({ layer: "breathing-sky", type: "curved-sky-bristle", calls: 28000, bristles: 3, x: 0, y: 0, w: W, h: 470, phase: .31, accept: blueAir, palette: MONET_LIGHT.concat(MONET_COOL), paletteAmount: .30, paletteJitter: .11, length: [24, 66], width: [2.6, 8], opacity: [.13, .29], angle: function(x,y){ return -12 + Math.sin(x * .006 + y * .013) * 9; }, angleJitter: 13, bend: 10, spread: 4.2, distanceScale: [1.1, .88] }); opticalField({ layer: "stone-mass", type: "facade-broken-color", calls: 25000, bristles: 3, x: 170, y: 135, w: 1630, h: 695, phase: .47, accept: observedStone, palette: MONET_STONE.concat(MONET_STONE).concat(MONET_COOL), paletteAmount: .48, paletteJitter: .10, length: [9, 31], width: [1.5, 5.2], opacity: [.30, .58], angleJitter: 16, bend: 5.5, spread: 2.4, distanceScale: [.82, 1.08] }); opticalField({ layer: "architecture-turning", type: "form-following-bristle", calls: 35000, bristles: 2, x: 180, y: 135, w: 1600, h: 695, phase: .63, accept: observedStone, palette: MONET_STONE.concat(MONET_DARK).concat(MONET_LIGHT), paletteAmount: .32, paletteJitter: .08, length: [5, 18], width: [.8, 3.2], opacity: [.32, .66], angleJitter: 9, bend: 3.2, spread: 1.5, distanceScale: [.72, 1.04] }); opticalField({ layer: "wet-reflection", type: "vertical-reflection-bristle", calls: 17000, bristles: 2, x: 0, y: 680, w: W, h: 400, phase: .79, palette: MONET_COOL.concat(MONET_STONE).concat(MONET_ROSE).concat(MONET_DARK), paletteAmount: .46, paletteJitter: .14, length: function(x){ var focus = 1 - Math.min(1, Math.abs(x - 980) / 980); return [16 + focus * 16, 45 + focus * 48]; }, width: function(x){ var focus = 1 - Math.min(1, Math.abs(x - 980) / 980); return [1.1 + focus * .5, 4.2 + focus * 2.4]; }, opacity: function(x){ var focus = 1 - Math.min(1, Math.abs(x - 980) / 980); return [.12 + focus * .08, .28 + focus * .22]; }, angle: function(x,y,g,i){ return 88 + Math.sin(x * .014 + i * .03) * 9; }, angleJitter: 13, bend: 12, spread: 3.2, distanceScale: [.68, 1.18] }); opticalField({ layer: "wet-glints", type: "broken-piazza-glint", calls: 19000, bristles: 2, x: 0, y: 720, w: W, h: 360, phase: .91, palette: MONET_LIGHT.concat(MONET_ROSE).concat(MONET_COOL), paletteAmount: .68, paletteJitter: .18, length: [4, 22], width: [.7, 3.4], opacity: [.15, .44], angle: function(){ return 2; }, angleJitter: 18, bend: 4, spread: 1.8, distanceScale: [.62, 1.28] }); opticalField({ layer: "facade-anchors", type: "selective-dark-stone", calls: 2500, bristles: 1, x: 260, y: 235, w: 1420, h: 525, phase: .103, accept: observedStone, palette: MONET_DARK.concat(MONET_STONE), paletteAmount: .62, paletteJitter: .06, length: [3, 11], width: [.7, 2.4], opacity: [.28, .58], angleJitter: 7, bend: 2.2, spread: 1.1, distanceScale: [.78, 1] }); opticalField({ layer: "waterline", type: "broken-waterline", calls: 1500, bristles: 1, x: 70, y: 660, w: 1780, h: 82, phase: .119, palette: MONET_DARK.concat(MONET_STONE), paletteAmount: .48, paletteJitter: .08, length: [8, 36], width: [.8, 2.8], opacity: [.22, .48], angle: function(){ return 1; }, angleJitter: 9, bend: 3, spread: 1, distanceScale: [1, 1] }); opticalField({ layer: "central-reflection", type: "luminous-central-reflection", calls: 1000, bristles: 2, x: 610, y: 690, w: 740, h: 390, phase: .137, palette: MONET_LIGHT.concat(MONET_STONE).concat(MONET_ROSE), paletteAmount: .66, paletteJitter: .1, length: [14, 58], width: [2.2, 8.2], opacity: [.14, .31], angle: function(x,y,g,i){ return 90 + Math.sin(i * .17) * 5; }, angleJitter: 9, bend: 7, spread: 3.2, distanceScale: [.7, 1.18] }); if (impressionist.report().marks !== TARGET) throw new Error("exact mark target failed: " + impressionist.report().marks); // Transparent optical glazes unify the marks without adding synthetic // geometry or inflating the semantic stroke ledger. ctx.save(); ctx.globalCompositeOperation = "multiply"; var depth = ctx.createLinearGradient(0, 650, 0, H); depth.addColorStop(0, "rgba(72,91,108,0)"); depth.addColorStop(1, "rgba(72,91,108,.18)"); ctx.fillStyle = depth; ctx.fillRect(0, 640, W, H - 640); var lateral = ctx.createLinearGradient(0, 0, W, 0); lateral.addColorStop(0, "rgba(64,82,103,.12)"); lateral.addColorStop(.32, "rgba(64,82,103,0)"); lateral.addColorStop(.68, "rgba(64,82,103,0)"); lateral.addColorStop(1, "rgba(64,82,103,.12)"); ctx.fillStyle = lateral; ctx.fillRect(0, 650, W, H - 650); ctx.globalCompositeOperation = "soft-light"; glow(ctx, 760, 470, 720, "rgba(221,173,102,.18)", "rgba(221,173,102,0)"); glow(ctx, 980, 820, 480, "rgba(226,186,118,.20)", "rgba(226,186,118,0)"); ctx.globalCompositeOperation = "screen"; var leftAir = ctx.createLinearGradient(0, 0, 280, 0); leftAir.addColorStop(0, "rgba(179,195,203,.28)"); leftAir.addColorStop(1, "rgba(179,195,203,0)"); ctx.fillStyle = leftAir; ctx.fillRect(0, 170, 300, 540); var rightAir = ctx.createLinearGradient(W, 0, W - 260, 0); rightAir.addColorStop(0, "rgba(181,190,193,.25)"); rightAir.addColorStop(1, "rgba(181,190,193,0)"); ctx.fillStyle = rightAir; ctx.fillRect(W - 300, 170, 300, 540); ctx.restore(); image(paintLayer, -W / 2, -H / 2, W, H); window.__impressionistReport = impressionist.report(); window.__impressionistSamplerReport = impressionistSampler.report(); } function setup() { createCanvas(W, H, WEBGL); photoLayer = createGraphics(W, H); sourceLayer = createGraphics(W, H); paintLayer = createGraphics(W, H); buildReference(); noLoop(); } function draw() { if (painted) return; painted = true; paint(); } window.setup = setup; window.draw = draw; window.__saintPetersScene = { title: "Saint Peter's Basilica After Rain — Optical Facade Study", width: W, height: H, targetMarks: TARGET, engine: "impressionist-v1.3", reference: "CC0 architectural geometry; no ImageGen" }; }());