The artist is busy painting a lavender field, but give him any other model (upload an image file) and he'll immediately start working on it.
for(_=';++~d[q]"-s[q]~q;!="!H);GG++qKif(Jata_R+E+B^(U-y)kImagejfor(Q){PMath.O*Orandom()N+20N)%Z.filYcYlStyle=`XGXhsl(WOcos(lV1GU<y+w,hOfloor(a*"*="-e=>{(r,g,b)%,${30+;"-==0;2560,.d_;tjD_(*4+w*4*Omax/.onload=;rHgHbHQU=Omax(y->0&&3*U++U* =document.createElement`ii* V), Osin(l)`;cYlRect(=c.ge)~UPa=.7*Osqrt(1-k*k/F/F)/255;q=xUGp;d=py=IF=h/80Gl=3X#fffrKgKbK~qAnput`;A.type=`file`;b.insertBefore(A,aGA.onchange=f=new FileReader;fmmg`;m.src=f.result;mGx=w=m.width;h=m.height;c.drawj(m,0Gs}};f.readAsD_URL(AYes[0])};x=w=h=512;X#433Gw,GQUU<;U+=.02)Qi=-10;i<10~iPl=6N;X#7cdi* l,-U,3.1W289,80U/4+20*V+2)}%,4,5W86510N}%)2++5,1,U/5)}s0;setInterval(Qtt<w~tPJy<0||x>=w||x<0PMQvv<h;v+=20)Quu<w;u+=20Pi=uZw;U=t+vZh;q=iUL=Omin+(I>600?25:0GJrgbL>MPx=i;y=U;M=L}}q=xyl=-l}F+F}x+=l;S=M=-2;2;Rr)!Eg)!Bb)!JREB^>MPM=^;S=U}}y=S}c.pup,0G++I},33)';G=/[-V-ZN-Qjk^_JKGH!"~]/.exec(_);)with(_.split(G))_=join(shift());eval(_)
Zm9yKF89JzsrK35kW3FdIi1zW3FdfnE7IT0iIUgpO0dHKytxS2lmKEphdGFfUitFK0JeKFUteSlrSW1hZ2VqZm9yKFEpe1BNYXRoLk8qT3JhbmRvbSgpTisyME4pJVouZmlsWWNZbFN0eWxlPWBYR1hoc2woV09jb3MobFYxR1U8eSsfdyxoHk9mbG9vcigdHWEqIiocPSItHBtlPT57GihyLGcsYikZJSwkezMwKxg7Ii09HBc9MDsWMjU2FTAsFC5kXzsTdGpEXygSKjQrdyo0KhFPbWF4GS8QLm9ubG9hZD0aDztySGdIYkgOUVU9T21heCgUeS0MPjAmJgszKlUrFStVKgk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudGBpCGkqCVYpLAlPc2luKGwpB2A7Y1lsUmVjdCgGPWMuZ2USFBQeKQV+VVBhPS43Kk9zcXJ0KDEtayprL0YvRikvMjU1O3E9eBFVBEdwBTtkPXATeT1JFkY9HWgvODBHbD0zA1gjZmZmBhQUAhdySxdnSxdiS35xAUEIbnB1dGA7QS50eXBlPWBmaWxlYDtiLmluc2VydEJlZm9yZShBLGFHQS5vbmNoYW5nZT0aZj1uZXcgRmlsZVJlYWRlcjtmD20IbWdgO20uc3JjPWYucmVzdWx0O20PAh5HeD13PW0ud2lkdGg7aD1tLmhlaWdodDtjLmRyYXdqKG0sFDBHcwUTAh4DfX07Zi5yZWFkQXNEX1VSTChBWWVzWzBdKX07eD13PWg9NTEyO1gjNDMzBhQUHkcCdywVR1FVFlU8FTtVKz0uMDIpUWk9LTEwO2k8MTB+aVBsPTZOO1gjN2NkBmkqCWwsFS1VLDMULjFXMjg5LDgwGFUvNCsyMCpWKzIpfSUGByw0LDVXOBQ2NRgxME59JSkGMisHKzUsMSxVLzUpfXMFEwIVFBUwAztzZXRJbnRlcnZhbCgaUXQWdDx3fnRQSnk8MHx8eD49d3x8eDwwUE0WUXYWdjxoO3YrPTIwKVF1FnU8dzt1Kz0yMFBpPR11Wnc7VT0ddCt2Wmg7cT1pEVUOTD0QT21pbhkrKEk+NjAwPxAyNTowR0pyC2cLYgtMPk1QeD1pO3k9VTtNPUx9fXE9eBF5Dmw9LWx9DEYrH0YEAQEBfXgrPWw7Uz1NPS0yOwwfMgQ7UhtyKSFFG2cpIUIbYikhSlILRQtCC14+TVBNPV47Uz1VfX15PVN9Yy5wdRJwLBQwRysrSX0sMzMpJztHPS9bAS0fVi1aTi1RamteX0pLR0ghIn5dLy5leGVjKF8pOyl3aXRoKF8uc3BsaXQoRykpXz1qb2luKHNoaWZ0KCkpO2V2YWwoXyk=
/*
* global variables
*
* a : (provided by shim) <canvas> DOM element, then alpha value for stroke
* b : (provided by shim) document body, then stroke Blue component in RGB
* c : (provided by shim) canvas 2D context
* d : image buffer for painting (p.data)
* e : (unused) parameter event
* f : FileReader for input
* g : stroke Green component in RGB
* h : [global] image height
* i : stroke X + displacement (brush width)
* j : stroke Y + displacement (brush height)
* m : <img> DOM element used for loading
* p : painting : canvas ImageData (c.getImageData)
* q : array index for iteration
* r : stroke Red component in RGB
* s : [global] source ImageData from the input image
* t : iteration loop
* u : iteration loop for sampling - x coordinate
* v : iteration loop for sampling - y coordinate
* w : [global] image width
* x : stroke X
* y : stroke Y
* z : stroke step (1+2*stroke halfwidth), switches sign every stroke
* B : (deltaB) difference in B channel between canvas and reference
* G : (deltaG) difference in G channel between canvas and reference
* H : stroke halfwidth
* L : score for current point when looking for new stroke
* R : (deltaR) difference in R channel between canvas and reference
* T : global time in 1/30 s (frame count)
* V : (bestValue) temporary best score, in loop to choose next stroke
* Y : (bestY) temporary stroke Y, in loop to decide stroke direction
*/
// form input & canvas creation : derived from http://js1k.com/2016-elemental/demo/2543
A = document.createElement`input`;
A.type = `file`;
b.insertBefore(A,a);
A.onchange = e=> {
f = new FileReader;
f.onload = e=> {
m = document.createElement`img`;
m.src = f.result;
m.onload = e=> {
c.fillStyle = `#fff`;
c.fillRect(0,0, w, h); // clear the previous drawing
x = w = m.width; // set x to image width to force a recomputation of the stroke on the first frame
h = m.height;
c.drawImage(m, 0, 0);
s = c.getImageData(0, 0, w, h).data;
c.fillStyle = `#fff`;
c.fillRect(0, 0, w, h);
p = c.getImageData(0,0,w,h);
d= p.data;
y=T=0;
H = Math.floor(h/80); // stroke halfheight
z = 3; // stroke step (initialized to width)
}
};
f.readAsDataURL(A.files[0])
};
// Draw the built-in image (lavender fields)
x=w=h=512;
c.fillStyle = `#433`;
c.fillRect(0,0,w,h); // earth
c.fillStyle = `#fff`;
c.fillRect(0,0,w,256); // sky (full white)
for (j=0; j<256; j+=.02)
for (i=-10;i<10; ++i) {
z = 6*Math.random();
c.fillStyle = `#7cd`;
c.fillRect(i*3*j+256+j*z,256-j,30,.1); // patch of blue sky
c.fillStyle = `hsl(289,80%,${30+j/4+20*Math.cos(z+2)}%`;
c.fillRect(i*3*j+256+j*Math.cos(z), 3*j+256+j*Math.sin(z), 4, 5); // lavender blossom
c.fillStyle = `hsl(80,65%,${30+10*Math.random()}%)`;
c.fillRect(2+i*3*j+256+j*Math.cos(z), 3*j+256+j*Math.sin(z)+5, 1, j/5); // lavender stalk
}
// end initial draw
// Brush stroke initialization - same code as in load event handler
s = c.getImageData(0, 0, w, h).data;
c.fillStyle = `#fff`;
c.fillRect(0, 0, 2560, 2560); // pretty much fullscreen, using numbers part of the packing scheme
p = c.getImageData(0,0,w,h);
d= p.data;
y=T=0;
H = Math.floor(h/80); // stroke halfheight
z = 3; // stroke step (initialized to positive stroke width)
setInterval(e=> {
// number of brush strokes per frame equal to image width
// to get a constant drawing time no matter the image size
for (t=0; t<w; ++t) {
if (y<0||x>=w||x<0) {
// search for next stroke
V = 0;
for (v=0;v<h; v+=20)
for (u=0;u<w; u+=20) {
i = Math.floor(u+20*Math.random())%w;
j = Math.floor(t+v+20*Math.random())%h; // t to get a different line on greyscale images
q = i*4+w*4*j;
r = d[q]-s[q]; ++q;
g = d[q]-s[q]; ++q;
b = d[q]-s[q]; ++q;
L = Math.max(r,g,b)/Math.min(r,g,b) // score depends on saturation
+(T>600?Math.max(r,g,b)/25:0); // after 20s, add a luminosity component
if (r>0 && g>0 && b>0 && L>V) { // keep the stroke only if
x = i; // the pixel is not already darker than the original
y = j; // and the score (saturation delta) is improved
V = L
}
}
q = x*4+w*4*y;
r = d[q]-s[q]; ++q;
g = d[q]-s[q]; ++q;
b = d[q]-s[q]; ++q;
z = -z; // reverse the brush direction on each stroke
} // if (y<0||x>=w||x<0) ... search for next stroke
// apply brush stroke
// constant width => unroll the loop on x to gain 8 bytes with RegPack
for (j=Math.max(0, y-H+1); j<y+H; ++j) {
a = .7*Math.sqrt(1-(j-y)*(j-y)/H/H)/255;
q = x*4+w*4*j;
d[q]-=Math.floor(a*d[q]*r); ++q;
d[q]-=Math.floor(a*d[q]*g); ++q;
d[q]-=Math.floor(a*d[q]*b); ++q;
++q;
d[q]-=Math.floor(a*d[q]*r); ++q;
d[q]-=Math.floor(a*d[q]*g); ++q;
d[q]-=Math.floor(a*d[q]*b); ++q;
++q;
d[q]-=Math.floor(a*d[q]*r); ++q;
d[q]-=Math.floor(a*d[q]*g); ++q;
d[q]-=Math.floor(a*d[q]*b); ++q;
++q;
}
x+=z;
// orient the brush stroke by shifting Y by one pixel up or down
// towards the pixel that has the most difference between reference and canvas
Y =
V = -2; // value for the best match
for (j=Math.max(0, y-1);j<y+2;++j) {
a = .7*Math.sqrt(1-(j-y)*(j-y)/H/H)/255; // alpha for brush, 0=translucent, 1=opaque
q = x*4+w*4*j;
R = d[q]-Math.floor(a*d[q]*r) - s[q]; ++q;
G = d[q]-Math.floor(a*d[q]*g) - s[q]; ++q;
B = d[q]-Math.floor(a*d[q]*b) - s[q]; ++q;
if (R>0 && G>0 && B>0 && R+G+B>V) {
V = R+G+B;
Y = j;
}
}
y = Y; // -2 if no suitable pixel found = stop the current stroke
} // for (t ... stroke loop
// blit to screen once every frame => 30 fps
c.putImageData(p, 0, 0);
++T;
}, 33)