Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 - Poor image quality when resizing canvas

I am in the process of making and image uploading page with client side image resizing (using HTML5 canvas). Everything is working, but the problem is that the image quality is not very good.

Here is a link to a (work in progress) photo gallery with images that my resize/upload code has generated.

Can you see what I mean by poor quality? Lots of jagged edges, especially on the thumbnail images. Any ideas of how to fix this?

Here is my javascript for generating the thumbnails:

img.onload = function()
{
   var canvasWidth = 150;
   var canvasHeight = 150;
   var Rz = resizeCanvasSmall(img,canvasID,canvasWidth,canvasHeight);

   ctx.drawImage(img,Rz[0],Rz[1],Rz[2],Rz[3],Rz[4],Rz[5],Rz[6],Rz[7]);
   dataurl = canvas.toDataURL("image/jpeg",0.8);  // File type and quality (0.0->1.0)
   UploadFile();
}


// Function to resize canvas (for thumbnail images)
// img = image object, canvas = canvas element ID
function resizeCanvasSmall(img,canvas,width,height)
{
var sx; //The x coordinate where to start clipping
var sy; //The y coordinate where to start clipping
var swidth; //The width of the clipped image
var sheight; //The height of the clipped image

var aspectRatio = width / height;

if (img.width > img.height) // If landscape
{
    sheight = img.height;
    swidth = img.height * aspectRatio;
    sy = 0;
    sx = (img.width - swidth) / 2;
}
else //If portrait
{
    swidth = img.width;
    sheight = img.width / aspectRatio;
    sx = 0;
    sy = (img.height - sheight) / 2;
}

document.getElementById(canvas).width = width;
document.getElementById(canvas).height = height;

return [sx,sy,swidth,sheight,0,0,width,height];
}
like image 888
Mark Avatar asked Dec 18 '12 13:12

Mark


2 Answers

The general rule for resizing large amounts is to go in jumps of powers of two so you have fewer aliasing artifacts, then just do one final resize for the rest of the way. Ex: if you need to rescale 1000px down to 68px scale it down to 500px, then 250px, then 125px, then to 68px. It's always a power of two until the last one.

Also, you should preserve the aspect ratio or you'll get funky aliasing at diagonals. If you need square thumbnails from non-square source images then size down as close as you can get to your target square while still being bigger, then crop it in the center. (Or go smaller and pad it). You must preserve the aspect ratio at all costs.

To get even better results than what I've described you'd have to implement your own resizing algorithms which will be far slower (since they would be in JS instead of optimized native code). Jumping into WebGL might also be an option.

like image 158
Josh Marinacci Avatar answered Nov 03 '22 00:11

Josh Marinacci


http://output.jsbin.com/palota/1/

I made a canvas resize plugin. One of the files can be included on the frontend. It goes pixel by pixel. Instead of grabbing a nearby pixel, it averages the colors, so the average pixel color is the same from the original to the new image.

https://github.com/danschumann/limby-resize

and the actual file to include is

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

This will produce the same type of resize as photoshop or imagemagick.

It's a little slower, because we have to look through all the pixels and redistribute every bit of color, but for jobs that aren't done often, it works.

Math behind the algorithm

lets say we have 3 pixels being resized into 2 pixels.

normally, each pixel will have 4 numbers: red, green, blue, alpha. Lets just look at a simplified version where pixels are just 1 number.

Lets say the original image is: 0 | 100 | 255

The regular canvas drawImage resize will result in either 0 | 100 or 0 | 255

This sometimes is fine, but it loses details and it can be a very ugly and jagged image. If you think about it, the sum of all the color in the original is 355 (0 + 100 + 255), leaving the average pixel 118.33. The resized average pixel would be 50 or 127.5, which could look okay or very different!

The image algorithm implemented in limby-resize will produce a similar image to imagemagick, keeping all the pixel data, so the average pixel will be the same.

Our algorithm would produce the following image: 33 | 201.3

(0 * .66 + 100 * .33) | (100 * .33 + 255 * .66)

The total in ours is 234.3, leaving the average of 117.15, which is going to equal the first image ( if we weren't rounding to 2 decimals for this example ).

Browser support

canvas_resize.js should be able to be included on the frontend for better resizing client side.

var img, canvas, resized;
img = new Image;
img.onload = function(){
  canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
  resized = document.createElement('canvas');
  resized.width = 300;
  resized.height = 500;
  // see lib/canvas_resize for window.canvasResize = function(){...}
  canvasResize(canvas, resized);
  // resized will now be a properly resized version of canvas
}

img.src = '/path/to/img.jpg';
like image 27
dansch Avatar answered Nov 03 '22 00:11

dansch