Optimize Images With HTML5 Canvas

Advertisement

Images have always been the heaviest component of websites. Even if high-speed Internet access gets cheaper and more widely available, websites will get heavier more quickly. If you really care about your visitors, then spend some time deciding between good-quality images that are bigger in size and poorer-quality images that download more quickly. And keep in mind that modern Web browsers have enough power to enhance images right on the user’s computer. In this article, I’ll demonstrate one possible solution.

Let’s refer to an image that I came across recently in my job. As you can see, this image is of stage curtains and has some (intentional) light noise:

1

Optimizing an image like this would be a real pain because it contains a lot of red (which causes more artifacts in JPEG) and noise (which creates awful artifacts in JPEG and is bad for PNG packing). The best optimization I could get for this image was 330 KB JPEG, which is quite much for a single image. So, I decided to do some experiments with image enhancement right in the user’s browser.

If you look closely at this image, you’ll see that it consists of two layers: the noise and the stage curtains. If we remove the noise, then the image shrinks to 70 KB in JPEG2, which is really nice. Thus, our goal becomes to pass a noiseless image to the user and then add noise to the image right in the Web browser. This will greatly reduce the downloading time and make the Web page perform better.

In Photoshop, generating monochromatic noise is very easy: just go to FilterNoiseAdd Noise. But in the original image, the noise actually darkens some pixels (i.e. there are no white pixels). This brings a new challenge: to apply a noise layer with the “Multiply” blending mode on the stage image.

HTML5 Canvas

All modern Web browsers support the canvas element3. While early canvas implementations offered only a drawing API, modern implementations allow authors to analyze and manipulate every image pixel. This can be done with the ImageData4 interface, which represents an image data’s width, height and array of pixels.

The canvas pixel array is a plain array containing each pixels’s RGBa data. Here is what that data array looks like:

pixelData = [pixel1_red, pixel1_green,
pixel1_blue, pixel1_alpha, pixel2_red,
pixel2_green, pixel2_blue, pixel2_alpha, …];

Thus, an image data array contains total_pixels×4 elements. For example, a 200×100 image would contain 200×100×4 = 80,000 elements in this array.

To analyze and manipulate individual canvas pixels, we have to get image data from it, then modify the pixel array and then put data back into the canvas:

// Coordinates of image pixel that we will modify
var x = 10, y = 20;

// Create a new canvas
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 100;
document.body.appendChild(canvas);

// Get drawing context
var ctx = canvas.getContext('2d');

// Get image data
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Calculate offset for pixel
var offset = (x - 1 + (y - 1) * canvas.width) * 4;

// Set pixel color to opaque orange
imageData.data[offset]     = 255; // red channel
imageData.data[offset + 1] = 127; // green channel
imageData.data[offset + 2] = 0;   // blue channel
imageData.data[offset + 3] = 255; // alpha channel

// Put image data back into canvas
ctx.putImageData(imageData, 0, 0);

Generating Noise

Once we know how to manipulate individual canvas pixels, we can easily create noise layer. A simple function that generates monochromatic noise might look like this:

function addNoise(canvas) {
   var ctx = canvas.getContext('2d');

   // Get canvas pixels
   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data;

   for (var i = 0, il = pixels.length; i < il; i += 4) {
var color = Math.round(Math.random() * 255);

      // Because the noise is monochromatic, we should put the same value in the R, G and B channels
      pixels[i] = pixels[i + 1] = pixels[i + 2] = color;

      // Make sure pixels are opaque
      pixels[i + 3] = 255;
   }

   // Put pixels back into canvas
   ctx.putImageData(imageData, 0, 0);
}

// Set up canvas
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 200;
document.body.appendChild(canvas);

addNoise(canvas);

The result could look like this:

Noise

Pretty good for starters. But we can’t just create a noise layer and place it above the scene image. Rather, we should blend it in “Multiply” mode.

Blend Modes

Anyone who has worked with Adobe Photoshop or any other advanced image editor knows what layer blend modes are:

Blending modes

Some folks regard image blend modes as some sort of rocket science, but in most cases the algorithms behind them are pretty simple. For example, here’s what the Multiply blending algorithm looks like:

(colorA * colorB) / 255

That is, we have to multiply two colors (each channel’s value) and divide it by 255.

Let’s modify out code snippet: load the image, generate the noise and apply it using the “Multiply” blend mode:

// Load image. Waiting for onload event is important
var img = new Image;
img.onload = function() {
addNoise(img);
};
img.src = "stage-bg.jpg";

function addNoise(img) {
   var canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;

   var ctx = canvas.getContext('2d');

   // Draw image on canvas to get its pixel data
   ctx.drawImage(img, 0, 0);

   // Get image pixels
   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data;

   for (var i = 0, il = pixels.length; i < il; i += 4) {
      // generate "noise" pixel
      var color = Math.random() * 255;

      // Apply noise pixel with Multiply blending mode for each color channel
      pixels[i] =     pixels[i] * color / 255;
      pixels[i + 1] = pixels[i + 1] * color / 255;
      pixels[i + 2] = pixels[i + 2] * color / 255;
   }

   ctx.putImageData(imageData, 0, 0);
   document.body.appendChild(canvas);
}

The result will look like this:

Stage noise

Looks nice, but the noise is very rough. We have to apply transparency to it.

Alpha Compositing

The process of combining two colors with transparency is called “Alpha compositing.” In the simplest case of compositing, the algorithm would look like this:

colorA * alpha + colorB * (1 - alpha)

Here, alpha is the composition coefficient (transparency) from 0 to 1. Choosing which color will be the background (colorB) and which will be the overlay (colorA) is important. In this case, the background will be the curtains image, and the noise will be the overlay.

Let’s add one more argument for the addNoise() function, which will control the alpha blending and modify the main function to respect transparency while blending images:

var img = new Image;
   img.onload = function() {
   addNoise(img, 0.2); // pass 'alpha' argument
};
img.src = "stage-bg.jpg";

function addNoise(img, alpha) { // new 'alpha' argument
   var canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;

   var ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);

   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data, r, g, b;

   for (var i = 0, il = pixels.length; i < il; i += 4) {
      // generate "noise" pixel
      var color = Math.random() * 255;

      // Calculate the target color in Multiply blending mode without alpha composition
      r = pixels[i] * color / 255;
      g = pixels[i + 1] * color / 255;
      b = pixels[i + 2] * color / 255;

      // alpha compositing
      pixels[i] =     r * alpha + pixels[i] * (1 - alpha);
      pixels[i + 1] = g * alpha + pixels[i + 1] * (1 - alpha);
      pixels[i + 2] = b * alpha + pixels[i + 2] * (1 - alpha);
   }

   ctx.putImageData(imageData, 0, 0);
   document.body.appendChild(canvas);
}

The result is exactly what we want: a noise layer applied to the background image in Multiply blending mode, with a transparency of 20%:

Stage noise alpha

Optimization

While the resulting image looks perfect, the performance of this script is pretty poor. On my computer, it takes about 300 milliseconds (ms). On the average user’s computer, it could take even longer. So, we have to optimize this script. Half of the code uses browser API calls such as for creating the canvas, getting the image data and sending it back, so we can’t do much with that. The other half is the main loop for applying noise to the stage image, and it can be perfectly optimized.

Our test image size is 1293×897, which leads to 1,159,821 loop iterations. A pretty big number, so even small modifications could lead to significant performance boosts.

For example, in this cycle we calculated 1 - alpha three times, but this is a static value. We should define a new variable outside of the for loop:

var alpha1 = 1 - alpha;

And then we would replace all occurrences of 1 - alpha with alpha1.

Next, for the noise pixel generation, we use the Math.random() * 255 formula. But a few lines later, we divide this value by 255, so r = pixels[i] * color / 255. Thus, we have no need to multiply and divide; we just use a random value. Here’s what the main loop looks like after these tweaks:

var alpha1 = 1 - alpha;
for (var i = 0, il = pixels.length; i < il; i += 4) {
   // generate "noise" pixel
   var color = Math.random();

   // Calculate the target color in Multiply blending mode without alpha composition
   r = pixels[i] * color;
   g = pixels[i + 1] * color;
   b = pixels[i + 2] * color;

   // Alpha compositing
   pixels[i] =     r * alpha + pixels[i] * alpha1;
   pixels[i + 1] = g * alpha + pixels[i + 1] * alpha1;
   pixels[i + 2] = b * alpha + pixels[i + 2] * alpha1;
}

After these little optimizations, the addNoise() function runs at about 240 ms (a 20% boost).

Remember that we have more than a million iterations, so every little bit counts. In the main loop, we’re accessing the pixels array twice: once for blending and once for alpha compositing. But array access is too resource-intensive, so we need to use an intermediate variable to store the original pixel value (i.e. we access the array once per iteration), like so:

var alpha1 = 1 - alpha;
var origR, origG, origB;

for (var i = 0, il = pixels.length; i < il; i += 4) {
   // generate "noise" pixel
   var color = Math.random();

   origR = pixels[i]
   origG = pixels[i + 1];
   origB = pixels[i + 2];

   // Calculate the target color in Multiply blending mode without alpha composition
   r = origR * color;
   g = origG * color;
   b = origG * color;

   // Alpha compositing
   pixels[i] =     r * alpha + origR * alpha1;
   pixels[i + 1] = g * alpha + origG * alpha1;
   pixels[i + 2] = b * alpha + origB * alpha1;
}

This reduces the execution of this function down to 200 ms.

Extreme Optimization

An attentive user would notice that the stage curtains are red. In other words, the image data is defined in the red channel only. The green and blue ones are empty, so there’s no need for them in the calculations:

for (var i = 0, il = pixels.length; i < il; i += 4) {
   // generate "noise" pixel
   var color = Math.random();

   origR = pixels[i]

   // Calculate the target color in Multiply blending mode without alpha composition
   r = origR * color;

   // Alpha compositing
   pixels[i] = r * alpha + origR * alpha1;
}

With some high-school algebra, I came up with this formula:

for (var i = 0, il = pixels.length; i < il; i += 4) {
   pixels[i] = pixels[i] * (Math.random() * alpha + alpha1);
}

And the overall execution of the function is reduced to 100 ms, one-third of the original 300 ms, which is pretty awesome.

The for loop contains just one simple calculation, and you might think that we can do nothing more. Actually, we can.

During the execution of the loop, we calculate the random pixel value and apply it to original one. But we don’t need to compute this random pixel on each iteration (remember, we have more than a million of them!). Rather, we can pre-calculate a limited set of random values and then apply them to original pixels. This will work because the generated value is… well, random. There’s no repeating patterns of special cases — just random data.

The trick is to pick the right value’s array size. It should be large enough to not produce visible repeating patterns on the image and small enough to be generated at a reasonable speed. During my experiments, the best random value’s array length was 3.73 of the image width.

Now, let’s generate an array with random pixels and then apply them to original image:

// Pick the best array length
var rl = Math.round(ctx.canvas.width * 3.73);
var randoms = new Array(rl);

// Pre-calculate random pixels
for (var i = 0; i < rl; i++) {
   randoms[i] = Math.random() * alpha + alpha1;
}

// Apply random pixels
for (var i = 0, il = pixels.length; i < il; i += 4) {
   pixels[i] = pixels[i] * randoms[i % rl];
}

This will cut down the execution time to 80 ms in Webkit browsers and have a significant boost in Opera. Also, Opera slows down in performance when the image data array contains float values, so we have to round them with fast bit-wise OR operator.

The final code snippet looks like this:

var img = new Image;
   img.onload = function() {
   addNoise(img, 0.2); // pass 'alpha' argument
};
img.src = "stage-bg.jpg";

function addNoise(img, alpha) {
   var canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;

   var ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);

   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data;
   var alpha1 = 1 - alpha;

   // Pick the best array length
   var rl = Math.round(ctx.canvas.width * 3.73);
   var randoms = new Array(rl);

   // Pre-calculate random pixels
   for (var i = 0; i < rl; i++) {
      randoms[i] = Math.random() * alpha + alpha1;
   }

   // Apply random pixels
   for (var i = 0, il = pixels.length; i < il; i += 4) {
      pixels[i] =  (pixels[i] * randoms[i % rl]) | 0;
   }

   ctx.putImageData(imageData, 0, 0);
   document.body.appendChild(canvas);
}

This code executes in about 80 ms on my laptop in Safari 5.1 for an image that is 1293×897, which is really fast. You can see the result online5.

The Result

In my opinion, the result is pretty good:

  • The image size was reduced from 330 KB to 70 KB, plus 1 KB of minified JavaScript. Actually, the image could be saved at a lower quality because the noise might hide some JPEG artifacts.
  • This is a perfectly valid progressive enhancement optimization. Users with modern browsers will see a highly detailed image, while users with older browsers (such as IE 6) or with JavaScript disabled will still be able to see the image but with less detail.

The only downside of this approach is that the final image should be calculated each time the user visits the Web page. In some cases, you can store the final image as data:url in localStorage and restore it the next time the page loads. In my case, the size of the final image is 1.24 MB, so I decided not to store it and instead spend the default 5 MB of local storage on more useful data.

Blend Modes Playground

I’ve created a small playground6 that you can use as a starting point for your experiments and optimizations. It contains most Photoshop blend modes and opacity control. Feel free to copy any code found on this playground into your pages.

Conclusion

The technique you’ve just learned can be used in many different ways on modern Web pages. You don’t need to create many heavy, detailed images and force users to download them. Highlights, shades, textures and more can all be generated very quickly right in the user’s browser.

But use this technique wisely. Putting many unoptimized image generators on a single page could cause the browser to hang for a few seconds. You shouldn’t use this technique if you don’t really understand how it works or know what you’re doing. Always prioritize performance ahead of cool technology.

Also, note that the curtains image in this article is for demonstration purposes only. You can achieve almost the same result — a noisy image — just by placing a semi-transparent noise texture above the scene image, as shown in this example7. Anyway, the point of this article was to show you how to enhance images right in the Web browser while keeping their size as small as possible.

Further Reading

(al)

Footnotes

  1. 1 http://coding.smashingmagazine.com/wp-content/uploads/2011/08/image.png
  2. 2 http://coding.smashingmagazine.com/wp-content/uploads/2011/08/stage-bg1.jpg
  3. 3 http://coding.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html
  4. 4 http://coding.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#imagedata
  5. 5 http://media.chikuyonok.ru/sm/canvas/
  6. 6 http://media.chikuyonok.ru/canvas-blending/
  7. 7 http://float-left.ru/im/
  8. 8 http://coding.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html
  9. 9 https://developer.mozilla.org/en/canvas_tutorial
  10. 10 http://en.wikipedia.org/wiki/Alpha_compositing
  11. 11 http://en.wikipedia.org/wiki/Blend_modes
  12. 12 http://stackoverflow.com/questions/5919663/how-does-photoshop-blend-two-images-together

↑ Back to topShare on Twitter

Sergey Chikuyonok is a Russian front-end web-developer and writer with a big passion on optimization: from images and JavaScript effects to working process and time-savings on coding.

Advertising
  1. 1

    Woah. Definitely a unique tutorial. Which makes it even more unique is the fact that instead of it being a “optimize images cross-brower” it’s done with HTML5, embracing the present and the future.

    Thank you.

    0
  2. 2

    re: … while users with older browsers (such as IE 6) or with JavaScript disabled will still be able to see the image but with less detail

    this is not true – at least in your final example – the image there is only inside script section. I’m not sure if you mean to add a standard img to noscript

    0
    • 3

      Sergey Chikuyonok

      August 30, 2011 3:55 am

      The provided script is used for demonstration purposes only. You can add noise to image like this:

      <img src=”my-image.jpg” onload=”addNoise(this, 0.2)”/>

      0
  3. 4

    I’m quit sure a better/quicker way to add 0.2 noise is possible. But too hard for me to explain in english. Someone will probably…

    0
  4. 5

    I can’t think of an occasion where I’ve ever intentionally wanted to *add* noise, to an image, but hundreds where it would have been nice to *remove* it – do you have any tricks for that?

    0
    • 6

      Dennis Rasmussen

      August 30, 2011 4:48 am

      This method adds to the alpha level of the image, maybe resetting pixels with extensive alpha will remove the noise?
      Not sure how Photoshop and similar software adds noise to an image though (is it alpha as well?)

      0
    • 7

      Noise is a killer on compression, so it’d always be better to remove noise in the image-preparation stage, rather than once the image has arrived at the client side.

      0
  5. 8

    Dennis Rasmussen

    August 30, 2011 4:43 am

    Dldler: “I’m quit sure a better/quicker way to add 0.2 noise is possible.”

    Feel free to enlighten us then :-)
    Otherwise your statement doesn’t mean anything at all.

    Anyway it’s a great post and I’m sure this method can be used in browser games etc.

    0
  6. 9

    Sorry Dennis. :) You are right and i apologie.
    I’m too old now to learn english :) And enough old to be proud about the little i know.

    In fact, i can try just to say the way.
    For a 0.2 noise effect you dont need a random * alpha value. You need a random * 51 value. This should permit to put away some multiplications… is’nt it ?

    0
    • 10

      Sergey Chikuyonok

      August 30, 2011 6:37 am

      What’s the difference between “random * alpha” and “random * 51”?

      0
      • 11

        You use a random * alphaNumber value. It needs to be multiplied by a byte value. You do this at :

        pixels[i] = (pixels[i] * randoms[i % rl]) | 0;

        …on the most used line.

        random * constByte value have just to be added to, and not multiplied.

        So, you could store random bytes unstead of random Numbers.

        // Pre-calculate random pixels
        var amount = 255*alpha;
        for (var i = 0; i < rl; i++) {
        randoms[i] = Math.round(Math.random() * amount);
        }
        // And then :
        // Apply random pixels
        for (var i = 0, il = pixels.length; i < il; i += 4) {
        pixels[i] = (pixels[i] + randoms[i % rl]) | 0;
        }

        Hope not to be ridiculous :(

        0
        • 12

          Sergey Chikuyonok

          August 30, 2011 7:19 am

          Here’s how it looks like: http://media.chikuyonok.ru/sm/canvas/test1.html (compare it with http://media.chikuyonok.ru/sm/canvas/).

          In this snippet, you’re offsetting color value instead of multiplying it.

          0
          • 13

            Yes. Thats it.
            I’m offseting instead of multiplying. That’s what i tried to said.
            And you are right, this is not an optimization of your “multiply effect” code, but an other way to add noise to a picture. Same argument makes effects differences (but you can also chose the argument to get the effect you want).

            Sorry for the disturb i made. I want to tell you i was happy to try my first html5 file for this. :) (not a programmer, just an old & ‘manual’ graphist) ;-)

            0
          • 14

            @sergey I applaud your dedication to this article. :)

            You didn’t troll or get mad at dldler and instead tried to understand what he’s trying to say, and even provided a comparison of both your codes.

            I’m also glad that you understand each other (I’m lost in the code). It’s amazing watching two people talk about a subject they *seem* to both know extensively.

            @dldler Thanks for trying to explain your point. Wish more people would see your latter comments and not dismiss you as another troll. You really do seem to know an alternative. (Don’t worry about your english, you’re a programmer not an english major.)

            0
        • 15

          ( but I have just try it, and get a 25% speed-up effect). That mean : from 80 down to 60 ms . So i’m ashame of my poor english but happy not to be senile yet. ;)
          One more : in fact I should substract the amount of noise (and not add it) to do the same effect (but both can be done).

          pixels[i] = (pixels[i] – randoms[i % rl]) | 0;

          0
          • 16

            Didler, try translate.google.com . It does quite good translations and might help you. Just a thought :-)

            0
  7. 17

    I wonder if one could combine the above technique with Cicada Method for repeating backgrounds: http://designfestival.com/the-cicada-principle-and-why-it-matters-to-web-designers/

    0
    • 18

      Interesting idea. However, I suspect that if you’re looking to do client-side alpha multiplication, you’d just end up with a few tiled noise images to use as the noise source, which will only save you on the noise value computation. You’d still need to manually iterate through the source and destination pixel arrays and do the multiplication in code, which might end up making even more time. If there were hardware-accelerated image blend effects available, this might improve performance considerably.

      0
  8. 19

    I agree that this technique will shave off KB – and keep the same effect that you had intended in the original image file.

    However, there is something to be said for the man-hours that go into using this technique. Realistically, setting up this coding is a good bit of work – and can become quite a burden on the developer if you consider this needs to be altered every time you try to use another image to ensure quality opacity/noise settings etc.

    For those of us who have strict limits regarding the number of hours we can sink into jobs, this technique is off limits until a generator is set up for it.

    With all that being said, its definitely an innovative approach to a problem we’ve all experienced at some time or another.

    Cheers
    - Will

    0
    • 20

      If you have a deadline and can’t meet it with this technique, don’t use it. This is a full blown tutorial so everything is right here. You wouldn’t need to develop this because it’s already developed right here. The power of the open web. Thank you Smashing.

      0
  9. 21

    I think an even better optimisation might be to generate a small (200x200px maybe) black noise block, varying the alpha randomly within the desired limits then tile it across the image using drawImage().

    That way you reduce your loop iterations from 3 million down to about 40,000.

    Here’s a demo I made that adds noise to a 770x700px image in about 13ms in Firefox:
    http://caerphoto.com/noise.html

    The results aren’t EXACTLY the same but they’re close enough, although you need to use smaller alpha values this way (I used 0.05 in the demo).

    edit:
    I tried it on the demo image in this article and actually an alpha of 0.2 produces almost identical results to the article’s demo. Does it in 22ms too.

    edit 2:
    One other thing, this method allows you to render noise on any image, not just those from the same domain.

    0
    • 22

      Sergey Chikuyonok

      August 30, 2011 1:54 pm

      Did you read the ”Conclusion” section?

      0
      • 23

        Did you look at his example?

        He’s not using a tiled texture per se; he’s generating the tiled “texture” on the fly according to the specific needs of the overlay, then tiling it with drawImage().

        Quite a clever approach to take, I might say. Saves generating pixel data for the entire image. However, you can only mess with a very small amount of data (your sample size). So, if the image has high-contrasting colour ranges in different areas, or you attempt to do something like a multiply effect, you won’t have any kind of accurate values.

        Also; great article. Loved reading it, especially the optimization part. In my job we very rarely (if ever), deal with loops of that magnitude!

        0
        • 24

          Sergey Chikuyonok

          August 31, 2011 12:40 am

          Yes, I looked at example. In “Conclusion” section I provided example where noise texture is a static image file placed above background image WITHOUT canvas. The noise texture is small enough (compared to JS need to generate texture and CPU cycles) and it works across all browsers (even IE7).

          0
      • 25

        I realise that the method is similar to overlaying a noise texture in a separate element, but it’s static (i.e. harder to maintain/adjust), and while a small noise texture works ok for a relatively busy background like the curtains image, the repeating nature becomes more obvious with plainer images such as sky.

        Also, it depends on what the overall point of the article is. Optimisation of one particular noise technique, or optimisation of the ‘adding noise’ process in general? I’m in no way trying to belittle your efforts here – the process of optimisation was a very interesting read, and the tip about using ‘| 0′ to round off numbers was something I’d not seen before.

        If the point was to illustrate the optimisation process for adjusting the pixel data of an image, then sure, my example is not so useful, as it’s limited in scope versus your more flexible approach.

        For the specific example of adding randomly-generated noise, however, I think it IS useful. It could even be expanded to generate a real-time ‘movie film grain’ animation (possibly optimised further by pre-generating a series of noise tiles, then using a different one for tiling each frame). I’ve updated my example to demonstrate this.

        Of course, you could always use a bunch of noise tiles, but then you’re introducing extra HTTP requests, whereas a simple addNoise function can be included in an existing JS file. Yes, you can use data: URIs in your CSS but then you’re back to lacking IE7 support.

        0
  10. 26

    First of all, I would like to congratulate you for this article. It’s surely very useful to understand how much one can control image manipulation on client side using canvas support. a word of caution: one should take into consideration not just the pages performance but also maintainability and scalabilty aspects and weigh out where to use what

    0
  11. 27

    > var rl = Math.round(ctx.canvas.width * 3.73);
    > var randoms = new Array(rl);

    var rl = Math.round(canvas.width * 3.73);
    var randoms = [rl];

    0
    • 28

      You can down-rate this, but new Array() is bad practice in JavaScript.
      Plus, you save some bytes.

      0
    • 29

      x = new Array(y);

      is not the same as

      x = [y];

      The first one creates an array of length y (assuming y is a number), where each element is undefined. The second creates an array with one element, y.

      That said, it’s not actually necessary to set the length of an array before putting values in it. This:

      var x = [];
      x[3] = “foo”;

      will work just fine, and x will be:

      [undefined, undefined, undefined, "foo"]

      There are some use cases for using ‘new Array()’ instead of the literal pattern. For instance:

      function repeatStr(s, count) {
      return (new Array(count + 1)).join(s);
      }

      repeatStr(“hello “, 3); // “hello hello hello “

      0
  12. 30

    Great article! However, what’s about mixing Canvas and WebWorkers? It would be a good idee to offload the pixel-heavy code to different thread without freezing the UI if the image is more complex!

    0
  13. 31

    I love this article!

    0
  14. 32

    Fantastic article! I had no idea so much could be accomplished with the HTML5 Canvas.

    0
  15. 33

    hi this was exactly what i was looking for; finally found a great and simple tutorial on canvas! please keep it going!

    more features perhaps?:-

    0
  16. 34

    Awesome article. And a very practical purpose!

    0
  17. 35

    There’s another step you can take to optimize the for loop. Instead of accessing the pixels.length property in every iteration, if you set that as a static variable just prior to the loop and use it in the loop, it should shave off some time. I don’t have time to work it up and test it myself, but I’d be interested to know how much it really does save.

    0
  18. 37

    While this is certainly a cool post and it definitely demonstrates a great technique using JavaScript and the canvas tag… there’s a much simpler optimization you could use. Optimize a JPEG of the curtains. Create a tiled noise image. (Black solid, transparent where white would be.) Stack images and you are done! This provides more opportunity to be backwards compatible with some older browsers. Plus it has the added benefit of shaving some time off the JavaScript interpretation.

    0
  19. 40

    My mind is officially blown. And before I even finished my first cup of coffee, nonetheless.

    This is maybe a bit over my head for the moment. But some day, it will change my life, I am sure.

    Thanks Sergey!

    0
  20. 41

    I don’t think it will catch on. you have too much time on your hands!

    0
  21. 42

    It does take time to create the effect but the result is outstanding. It would probably catch on if there was a online GUI tool to assist web designers. It would rarely be needed though and could just over-complicate web development…

    0
  22. 43

    Nice tutorial, love the coding.

    @Mary: You’d be lost in the ocean if you couldn’t swim. You learn code after you dive in, and are forced to swim in it.

    0
  23. 44

    Hi, first of all congratulations for your work and thanks for enlightening us with this tutorial.
    I tried to execute this on my system but got no results. I copied your final code snippet and saved it as .js file and then used it in a HTML file.
    I am using some other image and have changed the name and source accordingly in the step
    img.src = “stage-bg.jpg”;
    But still the image is not getting loaded on the browser (firefox).
    Can anyone please guide me?

    0
  24. 45

    Hi I have one more confusion:
    When will the function ‘ img.onload = function() ‘ be called? Whcih line in the final code snippet?

    0
  25. 46

    Wow, a great deal of work to undertake, but I know that in some sites this is essential. I need to try this one out. Many thanks.

    0
  26. 47

    Why not just use a paletted image format, they handle noise quite well, especially as the example image only uses a red hue.

    0
  27. 48

    Interesting post! A question though. Where did you get the red curtain image? I would like to have it in a bigger version. Thanks.

    0
  28. 49

    Good demo, here is an another resource demonstrating completely the HTML5 canvas pixel manipulation and image creation with running code example. http://www.tutorialspark.com/html5/HTML5_Canvas_Pixel-Manipulation.php .No, its not a spam, but a genuine attempt to help honest learners.

    0
  29. 50

    Hi ,

    The article is very useful.I have using in my application and checked it is reducing the image size.But i want to know is that possible to reduce the image constantly to 50KB? because we are using HTML 5 websql to store images.Websql limit is 5MB.Please let me if it is possible.

    Thanks,
    Ramasamy.

    0
  30. 51

    One more doubt this optimization will work on mobile devices(Android)?

    0

↑ Back to top