Search code examples
javascripthtmlcsscanvas

How to create linear gradient with thousands of properties in JS


I'm making a really in-efficient redrawing tool that takes each pixel of a canvas and takes it's color values and plugs that data into a linear gradient CSS property on a separate div. I want the linear gradient to create a non-fading effect where every pixel is going to have a separate color. I also don't know how the linear gradient will align with my pixel-color values since it reads every line one by one.

My other values like percent and data are arbitrary because I already have that figured out

        var data=(img_ctx.getImageData(x,y,1,1).data);
        draw.style.background+=" ,linear-gradient(0deg, "+Math.floor(percent)+"%, rgba("+data+")";

I just got a div that has no style properties for background, and I added the comma to allow multiple colors, I'm just not sure how to use linear gradient well enough to figure this out.


Solution

  • Linear gradients have only a single axis and fill their container in the perpendicular direction. In other words, you can only control the width of a color along that axis, making it impossible to create a 1px x 1px color within a div larger than 1px.

    So the only way to achieve the redraw is to create a stack of divs with 1px of height (or columns with 1px width).

    You can then contain the colors to 1px along the main axis by adding two stops for each color indicating the start and end along the main axis.

    const scale = 0.1;
    
    const canvas = document.querySelector('canvas');
    
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.onload = onload;
    
    fetch('https://i.kym-cdn.com/entries/icons/original/000/006/428/637738.jpg')
      .then((res) => res.blob())
      .then((blob) => (img.src = URL.createObjectURL(blob)));
    
    function onload() {
      const w = img.width * scale;
      const h = img.height * scale;
      canvas.width = w;
      canvas.height = h;
      canvas.style.width = w.toString() + 'px';
      canvas.style.height = h.toString() + 'px';
    
      ctx.drawImage(img, 0, 0, w, h);
    
      const pixelData = Array.from(ctx.getImageData(0, 0, w, h).data);
    
      for (let row = 0; row < h; row++) {
        const rowDiv = document.createElement('div');
        rowDiv.style.width = w.toString() + 'px';
        rowDiv.style.height = '1px';
    
        const rowStart = row * w * 4;
        let background = 'linear-gradient(to right,';
        for (let col = 0; col < w; col++) {
          const pixelStart = rowStart + col * 4;
          background +=
            'rgba(' +
            pixelData[pixelStart] +
            ',' +
            pixelData[pixelStart + 1] +
            ',' +
            pixelData[pixelStart + 2] +
            ',' +
            pixelData[pixelStart + 3] +
            ')' + col + 'px ' + (col + 1) + 'px,';
        }
        rowDiv.style.background = background.slice(0, -1) + ')';
        document.body.append(rowDiv);
      }
    }
    <h1>Canvas</h1>
    <canvas></canvas>
    
    <h1>Gradients</h1>

    But at this point you might as well just make a div for every pixel.