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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19262141/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 14:15:13  来源:igfitidea点击:

Resize image with javascript canvas (smoothly)

javascripthtmlimagecanvashtml5-canvas

提问by steve

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.

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

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

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

第一个是普通调整大小的图像标签,第二个是画布。请注意画布是如何不平滑的。我怎样才能达到“平滑”?

回答by

You can use down-stepping to achieve better results. Most browsers seem to use linear interpolation rather than bi-cubicwhen resizing images.

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

(UpdateThere has been added a quality property to the specs, imageSmoothingQualitywhich is currently available in Chrome only.)

更新规范中添加了一个质量属性,imageSmoothingQuality目前仅在 Chrome 中可用。)

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.

除非选择不平滑或最近邻,否则浏览器将始终在缩小图像后插入图像,因为此功能作为低通滤波器以避免混叠。

Bi-linear 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.

双线性使用 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>

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

根据您调整大小的剧烈程度,如果差异较小,您可以跳过第 2 步。

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

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

回答by Sebastian Ott

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

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

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

回答by fisch2

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

我创建了一个可重用的 Angular 服务来为感兴趣的人处理高质量的图像/画布大小调整:https: //gist.github.com/transitive-bullshit/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.

该服务包括两种解决方案,因为它们都有自己的优点/缺点。lanczos 卷积方法以速度较慢为代价获得更高质量,而逐步降尺度方法产生合理的抗锯齿结果并且速度明显更快。

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

回答by Trung Le Nguyen Nhat

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

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

UPDATE JSFIDDLE DEMO

更新 JSFIDDLE 演示

Here is my ONLINE DEMO

这是我的在线演示

回答by Funkodebat

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

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.

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

回答by Eyal c

While some of those code-snippets are short and working, they aren't trivial to follow and understand.

虽然其中一些代码片段简短且有效,但遵循和理解它们并非易事。

As i am not a fan of "copy-paste" from stack-overflow, i would like developers to understand the code they are push into they software, hope you'll find the below useful.

由于我不是堆栈溢出中“复制粘贴”的粉丝,我希望开发人员了解他们推送到他们软件中的代码,希望您会发现以下有用。

DEMO: Resizing images with JS and HTML Canvas Demo fiddler.

演示:使用 JS 和 HTML Canvas Demo fiddler 调整图像大小。

You may find 3 different methods to do this resize, that will help you understand how the code is working and why.

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

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

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

Full code of both demo, and TypeScript method that you may want to use in your code, can be found in the GitHub project.

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

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

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

This is the final code:

这是最终的代码:

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

回答by cagdas_ucar

I don't understand why nobody is suggesting createImageBitmap.

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

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

works beautifully (assuming you set ids for image and canvas).

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

回答by Diyaz Yakubov

I wrote small js-utility to crop and resize image on front-end. Here is linkon GitHub project. Also you can get blob from final image to send it.

我写了一个小的 js-utility 来裁剪和调整前端图像的大小。这是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;