使用javascript画布调整图像大小(平滑)


90

我正在尝试使用画布调整某些图像的大小,但是我对如何使其平滑一无所知。在Photoshop,浏览器等上。它们使用几种算法(例如,双三次,双线性),但是我不知道这些算法是否内置在画布中。

这是我的小提琴: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);

第一个是正常调整大小的图像标签,第二个是画布。请注意,画布如何不那么光滑。如何获得“顺滑度”?

Answers:


136

您可以使用降级以获得更好的结果。调整图像大小时,大多数浏览器似乎使用线性插值而不是双三次插值

更新在规范中添加了品质属性,该属性imageSmoothingQuality当前仅在Chrome中可用。)

除非没有选择平滑或最接近的邻居,否则浏览器将始终在缩小图像后对图像进行插值,因为该功能用作低通滤波器以避免混叠。

双线性使用2x2像素进行插值,而双三次使用4x4进行插值,因此,通过逐步执行此操作,可以在使用双线性插值时接近双三次结果,如在所得图像中看到的。

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

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
    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 = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

如果差异较小,则可以根据步骤大小调整的大小跳过步骤2。

在演示中,您可以看到新结果现在与image元素非常相似。


1
@steve heh,有时会发生这些事情:)对于图像,通常可以通过设置css BTW来覆盖它。

Ken,第一个结果效果很好,但是当我更改图像时,您会发现它太模糊了jsfiddle.net/kcHLG在这种情况下以及其他情况下可以做什么?
2012年

@steve您可以将步骤数减少到1或不减少(对于某些图像,它可以正常工作)。另请参见与此类似的答案,但在这里我向其添加了锐化卷积,因此可以在缩小图像后使所得图像更清晰。

1
@steve是比尔的经过修饰的小提琴,仅使用了一个额外的步骤:jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic该代码是OPs代码的延续。如果打开小提琴,将会看到ctx被定义。我在这里进行内联以避免误解。

27

由于Trung Le Nguyen Nhat的小提琴根本是不正确的(它只是在最后一步中使用了原始图像),
所以我用性能比较写了我自己的小提琴:

小提琴

基本上是:

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);
}

有史以来最被低估的答案。
Amsakanna

17

我创建了一个可重复使用的Angular服务,为感兴趣的任何人处理高质量的图像/画布大小调整:https : //gist.github.com/transitive-bullshit/37bac5e741eaec60e983

该服务包括两个解决方案,因为它们都有各自的优缺点。lanczos卷积方法具有较高的质量,但代价是速度较慢,而逐步缩小的方法可产生合理的抗锯齿结果,并且速度明显更快。

用法示例:

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
    })
})

抱歉,我更改了github用户名。刚刚更新了链接gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2 '18

3
我看到了棱角
分明

8

尽管其中一些代码段简短而有效,但遵循和理解它们并非易事。

由于我不喜欢从堆栈溢出中“复制粘贴”,我希望开发人员了解他们将代码推送到软件中的方法,希望您会发现以下有用的东西。

演示:使用JS和HTML Canvas演示提琴手调整图像大小。

您可能会发现3种不同的方法来进行此大小调整,这将帮助您了解代码的工作方式以及原因。

https://jsfiddle.net/1b68eLdr/93089/

可以在GitHub项目中找到演示的完整代码以及可能要在代码中使用的TypeScript方法。

https://github.com/eyalc4/ts-image-resizer

这是最终代码:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the dize by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}

4

我创建了一个库,使您可以在保留所有颜色数据的同时降低任何百分比。

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

您可以在浏览器中包含该文件。结果将看起来像photoshop或图像魔术,保留所有颜色数据,平均像素,而不是获取附近的像素并丢弃其他像素。它不使用公式来猜测平均值,而是采用精确的平均值。


1
我现在可能会使用webgl调整大小
Funkodebat

4

基于K3N的答案,我通常为任何人重写代码

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);

更新JSFIDDLE演示

这是我的在线演示


2
这是行不通的:每次您调整画布大小时,都会清除其上下文。您需要2幅画布。这与直接调用带有最终尺寸的drawImage相同。
Kaiido

2

我不明白为什么没人建议createImageBitmap

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

效果很好(假设您为图片和画布设置了ID)。


因为它不受广泛支持caniuse.com/#search=createImageBitmap
马特,

73%的用户支持createImageBitmap。根据您的用例,可能就足够了。只是Safari拒绝添加对此的支持。我认为值得一提的是可能的解决方案。
cagdas_ucar

不错的解决方案,但不幸的是,它不适用于firefox
vcarel

1

我写了小的js实用工具来裁剪和调整前端的图像大小。这是GitHub项目上的链接。您也可以从最终图像中获取Blob并将其发送。

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.