6

So the challenge that I've created for myself is as such.

I have a source photo:

Source Photo

That I am mapping color values and creating a pixelated representation of it using divs

Here's the result:

Result Photo

The code that I'm accomplishing this with is:

'use strict';

var imageSource = 'images/unicorn.jpg';

var img = new Image();
img.src = imageSource;
var canvas = $('<canvas/>')[0];
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var context = canvas.getContext('2d');

console.log('img height: ' + img.height);
console.log('img width: ' + img.width);

var pixelDensity = 70;

var timerStart = new Date();


for (var i = pixelDensity/2; i < img.height; i += (img.height/pixelDensity) ) {
    $('.container').append($('<div class="row">'));
    for(var j = pixelDensity/2; j < img.width; j += img.height/pixelDensity) {
        var value = context.getImageData(j, i, 1, 1).data;
        var colorValue = 'rgb(' + value[0] + ', ' + value[1] + ', ' + value[2] + ')';
        $('.row:last').append($('<div class="block">').css({'background-color' : colorValue}));
    }
}

var timerStop = new Date();

console.log(timerStop - timerStart + ' ms');

The pixelDensity variable controls how close together the color samples are. The smaller the number, the fewer the samples, taking less time to produce the result. As you increase the number, the samples go up and the function slows down considerably.

I'm curious to know what is making this thing take such a long time. I've looked at slightly similar projects - most notably Jscii - which process the image data much faster, but I can't figure out what the difference is that's offering the added performance.

Thanks for your help!

4
  • Don't touch the DOM unless you have to. Build your offscreen object model and then append it one time. Presumably, the more samples, the more divs, the more dom edits. So, built it all offscreen, append it all at once, one time. Commented Jan 31, 2013 at 19:26
  • That's what I was originally doing, but I found pulling the image data was what was taking the most time. I'll try it again given the advice below. Thanks for the help! Commented Jan 31, 2013 at 19:40
  • Another thing. When you are trying to eek out every bit of performance possible, then every little thing helps. Cache your for loop conditions. for(var j = pixelDensity/2; j < img.width; j += img.height/pixelDensity) becomes for( var j = pixelDensity/2, jmax = img.width, jinc = img.height/pixelDensity; j < jmax; j += jinc ) Commented Jan 31, 2013 at 19:43
  • @TravisJ - Combining your loop caching with dmk's recommendation reduced my processing time from six seconds to 60ms. Thanks a ton! Commented Feb 21, 2013 at 18:15

4 Answers 4

1

The main issue is that you append DOM elements to a page right in a loop. This would run much faster if you'll create wrapper element with all your data before actually adding it to the page.

Edit: I've also noticed you call context.getImageData for every pixel, this is what takes most of the time. Instead, you should cache image data in variable and get color values from it. You'll also need to cache loop conditions as @Travis J mentioned and round them:

var wrapper = $('<div class="container">');
var imgData = context.getImageData(0, 0, img.width, img.height).data;
var getRGB = function(i) { return [imgData[i], imgData[i+1], imgData[i+2]]; };
var start = Math.round(pixelDensity/2),
    inc = Math.round(img.height/pixelDensity);

for (var i = start; i < img.height; i += inc) {
    var row = $('<div class="row">');
    for(var j = start; j < img.width; j += inc) {
        var colorValue = getRGB((i * (img.width*4)) + (j*4));
        row.append($('<div class="block">').css({'background-color' : 'rgb('+(colorValue.join(','))+')'}));
    }
    wrapper.append(row);
}

$('body').append(wrapper);

This would reduce time from 6-9 seconds to 600-1000 milliseconds. You can also use plain javascript to manipulate DOM elements instead of jquery to make it even faster.

Sign up to request clarification or add additional context in comments.

1 Comment

This was the issue. Thank you so much for the response!
1

Why don't you consider drawing the result on the canvas instead of creating so many divs? In theory it should be much faster...

Comments

0

I've had similar speed problems before - The reason it takes so long is because you're adding in the elements by giving it the HTML in the form of text, which means it has to parse that text every time. If you use the JavaScript DOM to append new elements you'll notice a significant speed increase.

EDIT: In case you're not familiar with creating new elements this way, the syntax looks like this:

var newPixel = document.createElement('div');
newPixel.style.height = 3;
newPixel.style.backgroundColor = 'black';
// etc...
parentElement.appendChild(newPixel);

Comments

0

You could speed up somewhat by reducing the number of jquery lookups. For example, before your first loop start, do this:

var $container = $('.container');

Now you don't have to look up the container every time.

Also, when you create a row, use the same trick to avoid the 'row:last' lookup:

var $row = $('<div class="row">');
$container.append($row);
...
$row.append($('<div class="block">')...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.