问题描述:

I'm trying to resize some images with canvas but I'm clueless on how to smoothen them.

On photoshop, browsers etc.. there are a few algorithms they use (e.g. bicubic, bilinear) but I don't know if these are built into canvas or not.

Here's my fiddle: http://jsfiddle.net/EWupT/

 var canvas = document.createElement('canvas');

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

canvas.width=300

canvas.height=234

ctx.drawImage(img, 0, 0, 300, 234);

document.body.appendChild(canvas);

The first one is a normal resized image tag, and the second one is canvas. Notice how the canvas one is not as smooth. How can I achieve 'smoothness'?

网友答案:

You can use down-stepping to achive better results. Most browsers seem to use linear interpolation rather than bicubic when resizing images.

Unless one chooses no smoothing or nearest neighbor the browser will always interpolate the image after down-scaling it as this function as a low-pass filter to avoid aliasing.

Bilinear uses 2x2 pixels to do the interpolation while bi-cubic uses 4x4 so by doing it in steps you can get close to bi-cubic result while using bi-linear interpolation as seen in the resulting images.

ONLINE DEMO

/// when image is loaded:
img.onload = function () {

    /// set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    /// step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    /// step 2 - resize 50% of step 1
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    /// step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "http://i.imgur.com/SHo6Fub.jpg";

Depending on how drastic your resize is you can might skip step 2 if the difference is less.

In the demo you can see the new result is now much similar to the image element.

网友答案:

I created a reusable Angular service to handle high quality resizing of images / canvases for anyone who's interested: https://gist.github.com/fisch0920/37bac5e741eaec60e983

The service includes two solutions because they both have their own pros / cons. The lanczos convolution approach is higher quality at the cost of being slower, whereas the step-wise downscaling approach produces reasonably antialiased results and is significantly faster.

Example usage:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})
网友答案:

I created a library that allows you to downstep any percentage while keeping all the color data.

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

That file you can include in the browser. The results will look like photoshop or image magick, preserving all the color data, averaging pixels, rather than taking nearby ones and dropping others. It doesn't use a formula to guess the averages, it takes the exact average.

网友答案:

Since Trung Le Nguyen Nhat's fiddle isn't correct at all (it just uses the original image in the last step)
I wrote my own general fiddle with performance comparison:

FIDDLE

Basically it's:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
网友答案:

Based on K3N answer, I rewrite code generally for anyone wants

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

UPDATE JSFIDDLE DEMO

Here is my ONLINE DEMO

相关阅读:
Top