)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Smooth Bezier on Canvas

  1. const canvas = document.createElement('canvas');
  2. const c = canvas.getContext('2d');
  3.  
  4. document.body.appendChild(canvas);
  5.  
  6. function draw() {
  7.   canvas.width = window.innerWidth;
  8.   canvas.height = window.innerHeight;
  9.   c.fillStyle = 'gray';
  10.   c.fillRect(0, 0, canvas.width, canvas.height);
  11.  
  12.   c.strokeStyle = 'white';
  13.   c.lineWidth = 2;
  14.  
  15.   c.beginPath();
  16.   c.moveTo(0, 0);
  17.   bezierSkin([10, 10, 210, 10, 10, 300, 210, 300], false);
  18.   bezierSkin([200, 10, 330, 10, 250, 300]);
  19.   bezierSkin(
  20.     Array(30)
  21.       .fill(0)
  22.       .map((a, b) => (b % 2 == 0) * 300 + Math.random() * 300)
  23.   );
  24.   c.stroke();
  25. }
  26. window.addEventListener('resize', draw);
  27. draw();
  28.  
  29. // array of xy coords, closed boolean
  30. function bezierSkin(bez, closed = true) {
  31.   const avg = calcAvgs(bez);
  32.   const leng = bez.length;
  33.   let i, n;
  34.  
  35.   if (closed) {
  36.     c.moveTo(avg[0], avg[1]);
  37.     for (i = 2; i < leng; i += 2) {
  38.       n = i + 1;
  39.       c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
  40.     }
  41.     c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);
  42.   } else {
  43.     c.moveTo(bez[0], bez[1]);
  44.     c.lineTo(avg[0], avg[1]);
  45.     for (i = 2; i < leng - 2; i += 2) {
  46.       n = i + 1;
  47.       c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
  48.     }
  49.     c.lineTo(bez[leng - 2], bez[leng - 1]);
  50.   }
  51. }
  52.  
  53. // create anchor points by averaging the control points
  54. function calcAvgs(p) {
  55.   const avg = [];
  56.   const leng = p.length;
  57.   let prev;
  58.   for (var i = 2; i < leng; i++) {
  59.     prev = i - 2;
  60.     avg.push((p[prev] + p[i]) / 2);
  61.   }
  62.   // close
  63.   avg.push((p[0] + p[leng - 2]) / 2);
  64.   avg.push((p[1] + p[leng - 1]) / 2);
  65.   return avg;
  66. }

Being able to draw smooth lines that connect arbitrary points is something that I find myself needing very frequently. This is a port of an old old snippet of mine that does just that. By averaging control points of a quadratic bezier curve we ensure that our resulting Bezier curves are always smooth.

It would be very cool if html5 canvas implemented the Catmull Rom Spline but it unfortunately does not. The wonderful Raphael library used to have support for it.

Obfuscated Canvas Commands

  1. const oCan = (
  2.   d = document,
  3.   b = d.body,
  4.   canvas = b.appendChild(document.createElement('canvas')),
  5.   c = canvas.getContext('2d'),
  6.   props = [],
  7.   o = {},
  8.   docs = {},
  9.   cmds = [],
  10.   L,
  11.   k,
  12.   du,
  13.   j,
  14.   draw,
  15.   id
  16. ) => {
  17.   ;(onresize = () => {
  18.     canvas.width = innerWidth
  19.     canvas.height = innerHeight
  20.     if (draw) {
  21.       clearTimeout(id)
  22.       id = setTimeout(() => cmds.forEach(v => draw(v)), 500)
  23.     }
  24.   })()
  25.  
  26.   Object.assign(b.style, { margin: 0, height: '100%' })
  27.  
  28.   // longer way: console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(c)));
  29.   for (let i in c) props.push(i)
  30.  
  31.   // make alphabetical since object keys have
  32.   // no order
  33.   props.sort().map(i => {
  34.     L = i.match(/[A-Z]/)
  35.     k = i[0]
  36.     if (L) k += L[0]
  37.     du = 0
  38.     if (o[k]) {
  39.       j = 0
  40.       while (o[k]) k += i[++j]
  41.     }
  42.  
  43.     o[k] =
  44.       (typeof c[i])[0] == 'f'
  45.         ? (...args) => c[i].apply(c, args)
  46.         : v => (c[i] = v)
  47.     docs[i] = k
  48.   })
  49.  
  50.   console.log('docs:', JSON.stringify(docs, null, 2))
  51.  
  52.   return (draw = (s, cmd, currFn, args = [], part, fn, num) => {
  53.     cmd = s.split(/\s+/)
  54.     cmds.push(s)
  55.     c.save()
  56.     for (let i = 0; i < cmd.length; i++) {
  57.       part = cmd[i]
  58.       fn = o[part]
  59.       if (fn && currFn != fn) {
  60.         currFn && currFn.apply(c, args)
  61.         currFn = fn
  62.         args = []
  63.       } else {
  64.         num = parseFloat(part)
  65.         args.push(!isNaN(num) ? num : part)
  66.       }
  67.     }
  68.     currFn && currFn.apply(c, args)
  69.     c.restore()
  70.   })
  71. }
  72.  
  73. const c = oCan()
  74. // `font` & text not suppoted
  75. // make str a function so resize works?
  76. c(`
  77.   fS #ccc
  78.   fR 0 0 400 ${innerHeight}
  79.   fS blue
  80.   fR 40 0 20 20
  81.   gCl difference
  82.   ro .25
  83.   fR 50 0 30 30
  84.   gCl source-over
  85.   fS rgba(200,100,9)
  86.   fR 100 100 40 40
  87.   `)

I’ve had this idea for a long time, never bothered doing it until now. I wrote it in a semi-golfed style for no reason… Anyway, this lets you write canvas code in a strange obfuscated syntax that looks like this:

  1. c(`
  2.   fS #ccc
  3.   fR 0 0 400 ${innerHeight}
  4.   fS blue
  5.   fR 40 0 20 20
  6.   gCl difference
  7.   ro .25
  8.   fR 50 0 30 30
  9.   gCl source-over
  10.   fS rgba(200,100,9)
  11.   fR 100 100 40 40
  12.   `)

This snippet logs out some JSON that shows all the method aliases for the canvas context.

Creative Coding Auto-Painting

  1. Object.getOwnPropertyNames(Math).map(i => (this[i] = Math[i]))
  2. ;((
  3.   width = innerWidth * 2,
  4.   height = innerHeight * 2,
  5.   cnv = document.body.appendChild(
  6.     Object.assign(document.createElement('canvas'), {
  7.       width,
  8.       height
  9.     })
  10.   ),
  11.   c = cnv.getContext('2d'),
  12.   r = (n = 1) => Math.random() * n,
  13.   NUM = 50,
  14.   f = () => ({
  15.     ax: r(width),
  16.     ay: r(height),
  17.     x: 0,
  18.     y: 0,
  19.     T: r(9),
  20.     R: r(innerWidth * 0.8) + 40,
  21.     t: r(6),
  22.     C: round(r(255)),
  23.     m: r(5) + 1
  24.   }),
  25.   cs,
  26.   sn,
  27.   dx,
  28.   dy,
  29.   ns = [...Array(NUM)].map(f)
  30. ) => {
  31.   Object.assign(cnv.style, {
  32.     transformOrigin: '0 0',
  33.     transform: 'scale(.5)'
  34.   })
  35.   Object.assign(document.body.style, {
  36.     margin: 0,
  37.     padding: 0
  38.   })
  39.  
  40.   const clear = () => {
  41.     c.fillStyle = '#666668'
  42.     c.fillRect(0, 0, width, height)
  43.     c.globalAlpha = 0.5
  44.   }
  45.  
  46.   onresize = () => {
  47.     width = cnv.width = innerWidth * 2
  48.     height = cnv.height = innerHeight * 2
  49.     clear()
  50.   }
  51.  
  52.   clear()
  53.  
  54.   setInterval(() => {
  55.     for (i = 0; i < 30; i++) {
  56.       ns.map((n, i) => {
  57.         with (n) {
  58.           x = ax + R * cos(t)
  59.           y = ay + R * sin(t) * pow(sin(t * 0.5), m)
  60.           c.fillStyle = `rgba(${C},${C},${C},.02)`
  61.           ;(cs = cos(T)), (sn = sin(T)), (dx = x - ax), (dy = y - ay)
  62.           c.fillRect(cs * dx - sn * dy + ax, sn * dx + cs * dy + ay, 50, 50)
  63.           t += 0.1
  64.           R -= 0.01
  65.           if (R < 5) ns[i] = f()
  66.         }
  67.       })
  68.     }
  69.   }, 16)
  70. })()

Speed coded semi-golfed canvas texture. Best if viewed in fullscreen.

Creative Coding Greyscale Circle Texture

  1. const canvas = document.createElement('canvas'),
  2.   c = canvas.getContext('2d');
  3.  
  4. document.body.appendChild(canvas);
  5. document.body.style.margin = 0;
  6.  
  7. function brush() {
  8.   let pnts = [];
  9.   let cx = innerWidth * Math.random(),
  10.     cy = innerHeight * Math.random();
  11.   let x = y = 0;
  12.   let ro = innerWidth / (1 + Math.random() * 2);
  13.   let oro = ro;
  14.   let r2 = innerWidth / (2 + Math.random() * 2);
  15.   let rx = innerWidth / 4;
  16.   let ry = rx;
  17.  
  18.   ro = Math.min(300, ro);
  19.  
  20.   let t = 0;
  21.   let vt = 0.05;
  22.   let wt = 0.05;
  23.   let fric = 0.99;
  24.   let t2 = 0;
  25.   let x2 = 0;
  26.   let y2 = 0;
  27.   let vx2 = 0;
  28.   let vy2 = 0;
  29.   let col = ['black', 'white'][Math.floor(Math.random() * 2)];
  30.   let cc = 0; //[0, 255][Math.floor(Math.random() * 2)];
  31.   let s = 0.05 + Math.random() * 0.25;
  32.  
  33.   function draw() {
  34.     c.save();
  35.     c.translate(cx, cy);
  36.     c.scale(s, s);
  37.     for (let i = 0; i < 50; i++) {
  38.       if (Math.random() < 0.13) {
  39.         vt += Math.random() * 0.1 - 0.05;
  40.       }
  41.       if (Math.random() < 0.13) {
  42.         wt += Math.random() * 0.1 - 0.05;
  43.       }
  44.       vt *= 0.99;
  45.       wt *= 0.98;
  46.       rx += (vt + wt * r2 + ro - rx) / 12;
  47.       ry += (vt + wt * r2 + ro - ry) / 12;
  48.  
  49.       if (Math.random() < 0.001) {
  50.         ro += 100 * Math.sin(t2 / 5);
  51.         if (Math.abs(ro) < 50) ro = 50;
  52.       }
  53.       t2 += 0.01;
  54.       x = rx * Math.cos(t / 10) + y2;
  55.       y = ry * Math.sin(t / 10) + x2;
  56.  
  57.       if (Math.random() < 0.01) {
  58.         vx2 += Math.random() * 0.2 - 0.1;
  59.       }
  60.       if (Math.random() < 0.01) {
  61.         vy2 += Math.random() * 0.2 - 0.1;
  62.       }
  63.  
  64.       vx2 *= fric;
  65.       vy2 *= fric;
  66.  
  67.       x2 += vx2;
  68.       y2 += vy2;
  69.       t += vt;
  70.  
  71.       c.fillStyle = col;
  72.       c.fillRect(x, y, 4, 4);
  73.       pnts.push([x, y]);
  74.  
  75.       if (Math.random() < 0.1) {
  76.         const a = pnts[Math.floor(Math.random() * pnts.length)];
  77.         const b = pnts[Math.floor(Math.random() * pnts.length)];
  78.         c.strokeStyle = `rgba(${cc}, ${cc}, ${cc}, ${Math.random() / 2})`;
  79.         c.beginPath();
  80.         c.moveTo(a[0], a[1]);
  81.         c.lineTo(b[0], b[1]);
  82.         c.stroke();
  83.       }
  84.     }
  85.     c.restore();
  86.   }
  87.   return draw;
  88. }
  89.  
  90. const NUM = 20;
  91. let brushes = [];
  92.  
  93. function resize() {
  94.   canvas.width = innerWidth;
  95.   canvas.height = innerHeight;
  96.   c.fillStyle = 'gray';
  97.   c.fillRect(0, 0, canvas.width, canvas.height);
  98.  
  99.   brushes = [];
  100.   for (let i = 0; i < NUM; i++) {
  101.     brushes.push(brush());
  102.   }
  103. }
  104. addEventListener('resize', resize);
  105. resize();
  106.  
  107. function loop() {
  108.   c.globalAlpha = 1;
  109.   c.fillStyle = 'rgba(155, 155, 155, 0.0018)';
  110.   c.fillRect(0, 0, canvas.width, canvas.height);
  111.   c.globalAlpha = 0.5;
  112.   for (let i = 0; i < brushes.length; i++) {
  113.     brushes[i]();
  114.   }
  115.   requestAnimationFrame(loop);
  116. }
  117. loop();

I learned how to code making things like this in Director Lingo. This speed coded snippet draws some shaky circles and lines. (Make sure to view it in fullscreen).

Save Large Canvas

  1. // https://stackoverflow.com/questions/38781968/problems-downloading-big-filemax-15-mb-on-google-chrome
  2. function dataUriToBlob(dataURI) {
  3.   const binStr = atob(dataURI.split(',')[1]);
  4.   const len = binStr.length;
  5.   const arr = new Uint8Array(len);
  6.   const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
  7.   for (let i = 0; i < len; i++) {
  8.     arr[i] = binStr.charCodeAt(i);
  9.   }
  10.  
  11.   const blob = new Blob([arr], {
  12.     type: mimeString
  13.   });
  14.  
  15.   return URL.createObjectURL(blob);
  16. }
  17.  
  18. const save = document.createElement('a');
  19. save.innerText = 'save this big image';
  20. document.body.appendChild(save);
  21.  
  22. const canvas = document.createElement('canvas');
  23. const c = canvas.getContext('2d');
  24.  
  25. canvas.width = window.innerWidth * 4;
  26. canvas.height = window.innerHeight * 4;
  27. canvas.style.transformOrigin = '0 0';
  28. canvas.style.transform = `scale(0.14)`;
  29. document.body.appendChild(canvas);
  30.  
  31. c.fillStyle = 'black';
  32. c.fillRect(0, 0, canvas.width, canvas.height);
  33.  
  34. const size = Math.max(window.innerWidth, window.innerHeight);
  35.  
  36. // draw some rectangles
  37. c.globalCompositeOperation = 'difference'
  38. for (let i = 0; i < 100; i++) {
  39.   const angle = Math.random() * 180 + 180;
  40.   const color = `hsl(${angle}deg, 50%, 50%)`;
  41.   c.fillStyle = color;
  42.   const width = Math.random() * size + 100;
  43.   const height = Math.random() * size + 100;
  44.   const x = Math.random() * canvas.width - width / 2;
  45.   const y = Math.random() * canvas.height - height / 2;
  46.   c.fillRect(
  47.     x, 
  48.     y, 
  49.     width, 
  50.     height);
  51. }
  52.  
  53. save.download = 'big-image';
  54. save.href = dataUriToBlob(canvas.toDataURL());

Weirdly if you try to save a massive data URI to a file (> 15mb) it will just fail. In order to do this properly, you need to convert to a blob first and then save. I encountered this at some point and learned of the solution from user doppelgreener over at stack overflow. Take a look at the post here.

snippet.zone ~ 2021-24 /// {s/z}