Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resizing an image in an HTML5 canvas

I'm trying to create a thumbnail image on the client side using javascript and a canvas element, but when I shrink the image down, it looks terrible. It looks as if it was downsized in photoshop with the resampling set to 'Nearest Neighbor' instead of Bicubic. I know its possible to get this to look right, because this site can do it just fine using a canvas as well. I've tried using the same code they do as shown in the "[Source]" link, but it still looks terrible. Is there something I'm missing, some setting that needs to be set or something?

EDIT:

I'm trying to resize a jpg. I have tried resizing the same jpg on the linked site and in photoshop, and it looks fine when downsized.

Here is the relevant code:

reader.onloadend = function(e) {     var img = new Image();     var ctx = canvas.getContext("2d");     var canvasCopy = document.createElement("canvas");     var copyContext = canvasCopy.getContext("2d");      img.onload = function()     {         var ratio = 1;          if(img.width > maxWidth)             ratio = maxWidth / img.width;         else if(img.height > maxHeight)             ratio = maxHeight / img.height;          canvasCopy.width = img.width;         canvasCopy.height = img.height;         copyContext.drawImage(img, 0, 0);          canvas.width = img.width * ratio;         canvas.height = img.height * ratio;         ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);     };      img.src = reader.result; } 

EDIT2:

Seems I was mistaken, the linked website wasn't doing any better of a job of downsizing the image. I tried the other methods suggested and none of them look any better. This is what the different methods resulted in:

Photoshop:

alt text

Canvas:

alt text

Image with image-rendering: optimizeQuality set and scaled with width/height:

alt text

Image with image-rendering: optimizeQuality set and scaled with -moz-transform:

alt text

Canvas resize on pixastic:

alt text

I guess this means firefox isn't using bicubic sampling like its supposed to. I'll just have to wait until they actually add it.

EDIT3:

Original Image

like image 381
Telanor Avatar asked Feb 20 '10 20:02

Telanor


People also ask

How do you change the size of an image in html5?

Step 1: Firstly, we have to type the Html code in any text editor or open the existing Html file in the text editor in which we want to change the size of an image. Step 2: Now, place the cursor inside the img tag. And then, we have to use the height and width attribute of the img tag for changing the size of an image.

How do you change the size of a picture in canvas?

Right-click on the image and select Resize Image.... After selecting Resize Image... a dialog will appear that gives you precise control over the size of the image.

How do you scale an image in HTML?

To resize an image in HTML, use the width and height attributes of the img tag. You can also use various CSS properties to resize images. You should be seeing this image at its original size, unless your device is narrow and has resized it.

How do I stretch an image in canvas?

To stretch the image and canvas simultaneously, use the Image Size command. To stretch the canvas separately, use the Canvas Size command. If your image doesn't touch the edges of the canvas, it will create a frame around the image that represents the empty space on the canvas not covered by the image.


2 Answers

So what do you do if all the browsers (actually, Chrome 5 gave me quite good one) won't give you good enough resampling quality? You implement them yourself then! Oh come on, we're entering the new age of Web 3.0, HTML5 compliant browsers, super optimized JIT javascript compilers, multi-core(†) machines, with tons of memory, what are you afraid of? Hey, there's the word java in javascript, so that should guarantee the performance, right? Behold, the thumbnail generating code:

// returns a function that calculates lanczos weight function lanczosCreate(lobes) {     return function(x) {         if (x > lobes)             return 0;         x *= Math.PI;         if (Math.abs(x) < 1e-16)             return 1;         var xx = x / lobes;         return Math.sin(x) * Math.sin(xx) / x / xx;     }; }  // elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius function thumbnailer(elem, img, sx, lobes) {     this.canvas = elem;     elem.width = img.width;     elem.height = img.height;     elem.style.display = "none";     this.ctx = elem.getContext("2d");     this.ctx.drawImage(img, 0, 0);     this.img = img;     this.src = this.ctx.getImageData(0, 0, img.width, img.height);     this.dest = {         width : sx,         height : Math.round(img.height * sx / img.width),     };     this.dest.data = new Array(this.dest.width * this.dest.height * 3);     this.lanczos = lanczosCreate(lobes);     this.ratio = img.width / sx;     this.rcp_ratio = 2 / this.ratio;     this.range2 = Math.ceil(this.ratio * lobes / 2);     this.cacheLanc = {};     this.center = {};     this.icenter = {};     setTimeout(this.process1, 0, this, 0); }  thumbnailer.prototype.process1 = function(self, u) {     self.center.x = (u + 0.5) * self.ratio;     self.icenter.x = Math.floor(self.center.x);     for (var v = 0; v < self.dest.height; v++) {         self.center.y = (v + 0.5) * self.ratio;         self.icenter.y = Math.floor(self.center.y);         var a, r, g, b;         a = r = g = b = 0;         for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {             if (i < 0 || i >= self.src.width)                 continue;             var f_x = Math.floor(1000 * Math.abs(i - self.center.x));             if (!self.cacheLanc[f_x])                 self.cacheLanc[f_x] = {};             for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {                 if (j < 0 || j >= self.src.height)                     continue;                 var f_y = Math.floor(1000 * Math.abs(j - self.center.y));                 if (self.cacheLanc[f_x][f_y] == undefined)                     self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)                             + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);                 weight = self.cacheLanc[f_x][f_y];                 if (weight > 0) {                     var idx = (j * self.src.width + i) * 4;                     a += weight;                     r += weight * self.src.data[idx];                     g += weight * self.src.data[idx + 1];                     b += weight * self.src.data[idx + 2];                 }             }         }         var idx = (v * self.dest.width + u) * 3;         self.dest.data[idx] = r / a;         self.dest.data[idx + 1] = g / a;         self.dest.data[idx + 2] = b / a;     }      if (++u < self.dest.width)         setTimeout(self.process1, 0, self, u);     else         setTimeout(self.process2, 0, self); }; thumbnailer.prototype.process2 = function(self) {     self.canvas.width = self.dest.width;     self.canvas.height = self.dest.height;     self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);     self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);     var idx, idx2;     for (var i = 0; i < self.dest.width; i++) {         for (var j = 0; j < self.dest.height; j++) {             idx = (j * self.dest.width + i) * 3;             idx2 = (j * self.dest.width + i) * 4;             self.src.data[idx2] = self.dest.data[idx];             self.src.data[idx2 + 1] = self.dest.data[idx + 1];             self.src.data[idx2 + 2] = self.dest.data[idx + 2];         }     }     self.ctx.putImageData(self.src, 0, 0);     self.canvas.style.display = "block"; }; 

...with which you can produce results like these!

img717.imageshack.us/img717/8910/lanczos358.png

so anyway, here is a 'fixed' version of your example:

img.onload = function() {     var canvas = document.createElement("canvas");     new thumbnailer(canvas, img, 188, 3); //this produces lanczos3     // but feel free to raise it up to 8. Your client will appreciate     // that the program makes full use of his machine.     document.body.appendChild(canvas); }; 

Now it's time to pit your best browsers out there and see which one will least likely increase your client's blood pressure!

Umm, where's my sarcasm tag?

(since many parts of the code is based on Anrieff Gallery Generator is it also covered under GPL2? I don't know)

actually due to limitation of javascript, multi-core is not supported.

like image 167
syockit Avatar answered Oct 06 '22 05:10

syockit


Fast image resize/resample algorithm using Hermite filter with JavaScript. Support transparency, gives good quality. Preview:

enter image description here

Update: version 2.0 added on GitHub (faster, web workers + transferable objects). Finally i got it working!

Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/

/**  * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!  *   * @param {HtmlElement} canvas  * @param {int} width  * @param {int} height  * @param {boolean} resize_canvas if true, canvas will be resized. Optional.  */ function resample_single(canvas, width, height, resize_canvas) {     var width_source = canvas.width;     var height_source = canvas.height;     width = Math.round(width);     height = Math.round(height);      var ratio_w = width_source / width;     var ratio_h = height_source / height;     var ratio_w_half = Math.ceil(ratio_w / 2);     var ratio_h_half = Math.ceil(ratio_h / 2);      var ctx = canvas.getContext("2d");     var img = ctx.getImageData(0, 0, width_source, height_source);     var img2 = ctx.createImageData(width, height);     var data = img.data;     var data2 = img2.data;      for (var j = 0; j < height; j++) {         for (var i = 0; i < width; i++) {             var x2 = (i + j * width) * 4;             var weight = 0;             var weights = 0;             var weights_alpha = 0;             var gx_r = 0;             var gx_g = 0;             var gx_b = 0;             var gx_a = 0;             var center_y = (j + 0.5) * ratio_h;             var yy_start = Math.floor(j * ratio_h);             var yy_stop = Math.ceil((j + 1) * ratio_h);             for (var yy = yy_start; yy < yy_stop; yy++) {                 var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;                 var center_x = (i + 0.5) * ratio_w;                 var w0 = dy * dy; //pre-calc part of w                 var xx_start = Math.floor(i * ratio_w);                 var xx_stop = Math.ceil((i + 1) * ratio_w);                 for (var xx = xx_start; xx < xx_stop; xx++) {                     var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;                     var w = Math.sqrt(w0 + dx * dx);                     if (w >= 1) {                         //pixel too far                         continue;                     }                     //hermite filter                     weight = 2 * w * w * w - 3 * w * w + 1;                     var pos_x = 4 * (xx + yy * width_source);                     //alpha                     gx_a += weight * data[pos_x + 3];                     weights_alpha += weight;                     //colors                     if (data[pos_x + 3] < 255)                         weight = weight * data[pos_x + 3] / 250;                     gx_r += weight * data[pos_x];                     gx_g += weight * data[pos_x + 1];                     gx_b += weight * data[pos_x + 2];                     weights += weight;                 }             }             data2[x2] = gx_r / weights;             data2[x2 + 1] = gx_g / weights;             data2[x2 + 2] = gx_b / weights;             data2[x2 + 3] = gx_a / weights_alpha;         }     }     //clear and resize canvas     if (resize_canvas === true) {         canvas.width = width;         canvas.height = height;     } else {         ctx.clearRect(0, 0, width_source, height_source);     }      //draw     ctx.putImageData(img2, 0, 0); } 
like image 43
ViliusL Avatar answered Oct 06 '22 07:10

ViliusL