Create a Pixelated Image Using d3

Updated Feb 8, 2018

d3 is great for creating charts but also fun to play around with in other ways. Here we’ll use d3 in conjunction with canvas to create a pixelated version of an image. It’s hard to think of any useful applications for this but we’ll go ahead anyway.

Below is the original image, inside a canvas element, and a pixelated SVG version below created with d3. Click the “Change” button to change the source image. Use the slider to change the size of the pixels:

First set up the HTML. This could be included in the code but having it in one place in the HTML let’s you easily change the image source and size:

HTML Copy
<div style="display:none;">
  <img id="image" src="[your image url]" width="[width]" height="[height]" />
</div>
<canvas width="[width]" height="[height]"></canvas>
<div id="pixelated"></div>

First we create a hidden image #image that will be the source for the canvas. Set the URL, width, and height accordingly. Next the canvas element with width and height matching the image. And finally a container #pixelated where we’ll attach the SVG. For the Javascript we start with some definitions and loading the image:

Setting up Copy
import {
  select as d3_select
from 'd3';
const canvas  = document.querySelector('canvas');
const context = canvas.getContext('2d');
const srcImg  = document.getElementById('image');
const image   = new Image();
let svg;
image.src    = srcImg.src;
image.onload = function () {
  // Render the image on the canvas.
  context.drawImage(image, 00);
  // Create main SVG element.
  svg = d3_select('#pixelated').append('svg')
    .attr('width'image.width)
    .attr('height'image.height)
    .append('g');
  // Create pixelated version with 10x10 squares.
  pixelate(image, 10);
}

The image load handler closes with a call to a function pixelate, passing in the image and the size (width and height) for the squares to create. (NB: The opening example just calls pixelate with the current slider value for the square size on change events.) The pixelate function reads the colors from the canvas image to create the data for the pixelated rendering:

pixelate() Copy
function pixelate (image, size) {
  // Number of squares left-to-right and top-to-bottom.
  const xSquares = image.width / size;
  const ySquares = image.height / size;
  let data = [];
  for (let x = 0; x < xSquares; x++) {
    for (let y = 0; y < ySquares; y++) {
      const rgba = context.getImageData(x*size, y*size, size, size).data;
      const len  = rgba.length;
      const num  = len / 4;
      let r = 0;
      let g = 0;
      let b = 0;
      // Sum the color values.
      for (let i = 0; i < len; i += 4) {
        r += rgba[i];
        g += rgba[i+1];
        b += rgba[i+2];
      }
      // Save the position and average color value.
      data.push({
        x   : x*size,
        y   : y*size,
        rgb : 'rgb(' + [
          Math.round(r / num),
          Math.round(g / num),
          Math.round(b / num)
        ].join(','+ ')'
      });
    }
  }
  drawSvg(data, size);
}

The pixelate function creates an array of objects, each representing a square in the final rendering with x and y positions and an average color value.

The pixelate function closes by calling drawSvg, passing along the image data. This function uses d3 to bind the data to rect elements:

drawSvg() Copy
function drawSvg (colors, size) {
  const squares = svg.selectAll('.square').data(colors);
  squares.enter().append('rect')
    .attr('class''square')
    .attr('x', d => d.x)
    .attr('y', d => d.y)
    .attr('width', size)
    .attr('height', size)
    .attr('fill', d => d.rgb);
  squares
    .attr('x', d => d.x)
    .attr('y', d => d.y)
    .attr('width', size)
    .attr('height', size)
    .attr('fill', d => d.rgb);
  squares.exit().remove();
}

The drawSvg function just uses the basic d3 enter-update-exit pattern to render the squares at the specified positions with the specified fill color. And now at long last you can use d3 to render pixelated images.

Comments

No comments exist. Be the first!

Leave a comment