Search code examples
javascriptraster

How to create image from a 2D array of 1's and 0's in javascript?


I have a an 2d array of 1's and 0's and I'd like to create a black and white image from that array in my react application.

My array looks something like this but much bigger:

var items = [
  [0, 1, 1, 0],
  [1, 1, 1, 0],
  [0, 1, 0, 1]
];

and I want to show something like: enter image description here

How can I output a black and white raster plot from my array in my react application?


Solution

  • You could use a library that renders PNGs or other image formats, but since you have a simple list of pixels, an SVG image is not hard to render out, either. Technically this isn't a raster plot, as it's a vector image but you can render it out at 4x3 pixels if you wanted to and it'll stay crisp.

    function drawSvg(tag, rows) {
      // If you're sure all rows are always the same size, it's 
      // faster and simpler to just do `rows[0].length`.
      const pixelWidth = rows.reduce((max, row) => Math.max(max, row.length), 0);
      const pixelHeight = rows.length;
    
      // The `viewBox` of a SVG tag is the viewport to draw to.
      // Since we want 1 "pixel" in our vector to be 1 pixel in the output,
      // the viewbox will be 0 -0.5 4 3 for our 4x3 sample image.
      // The -0.5 shift upwards is because of how SVG aligns things.
      tag.setAttribute('viewBox', `0 -0.5 ${pixelWidth} ${pixelHeight}`);
      // The width/height of a SVG tag can be overwritten by CSS to your liking,
      // but having them makes sure the image stays the correct aspect ratio.
      tag.setAttribute('width', pixelWidth);
      tag.setAttribute('height', pixelHeight);
    
      const path = tag.querySelector('path');
      
      let data = '';
      for (let line = 0; line < rows.length; line++) {
        // [M]ove absolutely to 0,Y
        data += ` M0,${line}`;
        const row = rows[line];
        for (const pixel of row) {
          if (pixel) {
            // Draw a [h]orizontal line, 1 unit wide.
            data += ` h1`;
          } else {
            // [m]ove relatively to +1,+0.
            data += ` m1,0`;
          }
        }
      }
      // Output will be something like 'M0,0 h1 m1,0 h1'.
      path.setAttribute('d', data);
    
      // You can also create a link to a SVG by embedding it as a data URL.
      // encodeURIComponent is smaller than Base64Encode.
      // You could minify the input, but we don't need to bother for the sample.
      const href = `data:image/svg+xml,${encodeURIComponent(tag.outerHTML)}`;
      
      // Create an <img src="">
      const img = document.createElement('img');
      img.classList.add('output');
      img.setAttribute('src', href);
      tag.parentNode.insertBefore(img, tag);
    
      // Works as a background image, too.
      const div = document.createElement('div');
      div.classList.add('output');
      div.style.backgroundImage = `url("${href}")`;
      tag.parentNode.insertBefore(div, tag);
    }
    
    addEventListener('DOMContentLoaded', () => {
      drawSvg(document.querySelector('svg.output'), [
        [0, 1, 1, 0],
        [1, 1, 1, 0],
        [0, 1, 0, 1]
      ]);
    });
    .output {
      width: 100px;
      height: auto;
      border: 1px solid orange;
    }
    img.output {
      border-color: lime;
    }
    div.output {
      background: no-repeat 0 0;
      background-size: cover;
      height: 75px;
      border-color: aqua;
    }
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1" shape-rendering="crispEdges" class="output">
      <path fill="none" stroke="#000000" d="M0" />
    </svg>