Javascript 在 HTML5 画布中调整图像大小
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2303690/
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
Resizing an image in an HTML5 canvas
提问by Telanor
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 sitecan 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?
我正在尝试使用 javascript 和 canvas 元素在客户端创建一个缩略图图像,但是当我缩小图像时,它看起来很糟糕。它看起来好像在 Photoshop 中缩小了尺寸,重采样设置为“最近的邻居”而不是双三次。我知道有可能让它看起来正确,因为这个网站也可以使用画布来做。我尝试使用与“[Source]”链接中所示相同的代码,但它看起来仍然很糟糕。有什么我遗漏的,需要设置的一些设置还是什么?
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.
我正在尝试调整 jpg 的大小。我尝试在链接的网站和 photoshop 中调整相同的 jpg 大小,缩小后看起来不错。
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:
编辑2:
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:
Photoshop:


Canvas:
帆布:


Image with image-rendering: optimizeQuality set and scaled with width/height:
带有图像渲染的图像:优化质量设置并按宽度/高度缩放:


Image with image-rendering: optimizeQuality set and scaled with -moz-transform:
带有图像渲染的图像:优化质量设置并使用 -moz-transform 缩放:


Canvas resize on pixastic:
画布在 pixastic 上调整大小:


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.
我想这意味着 firefox 没有像它应该的那样使用双三次采样。我只需要等到他们真正添加它。
EDIT3:
编辑3:
回答by syockit
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:
那么如果所有浏览器(实际上,Chrome 5 给了我很好的浏览器)都不能给你足够好的重采样质量,你会怎么做?然后你自己实现它们!哦,拜托,我们正在进入 Web 3.0、HTML5 兼容浏览器、超级优化的 JIT javascript 编译器、多核(?)机器、大量内存的新时代,你害怕什么?嘿,javascript中有java这个词,所以应该保证性能,对吧?看,缩略图生成代码:
// 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!
...使用它您可以产生这样的结果!


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 Generatoris it also covered under GPL2? I dunno)
(因为代码的很多部分都是基于Anrieff Gallery Generator 的,它是否也包含在 GPL2 下?我不知道)
? actually due to limitation of javascript, multi-core is not supported.
? 实际上由于javascript的限制,不支持多核。
回答by ViliusL
Fast image resize/resample algorithm using Hermitefilter with JavaScript. Support transparency, gives good quality. Preview:

使用带 JavaScript 的Hermite过滤器快速调整图像大小/重新采样算法。支持透明度,提供良好的质量。预览:

Update: version 2.0 added on GitHub (faster, web workers + transferable objects). Finally i got it working!
更新:在 GitHub 上添加了 2.0 版(更快,网络工作者 + 可传输对象)。最后我让它工作了!
Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/
Git:https: //github.com/viliusle/Hermite-resize
演示: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);
}
回答by Vitaly
Try pica- that's a highly optimized resizer with selectable algorythms. See demo.
试试pica- 这是一个高度优化的调整器,具有可选择的算法。见演示。
For example, original image from first post is resized in 120ms with Lanczos filter and 3px window or 60ms with Box filter and 0.5px window. For huge 17mb image 5000x3000px resize takes ~1s on desktop and 3s on mobile.
例如,第一篇文章的原始图像使用 Lanczos 过滤器和 3px 窗口在 120 毫秒内调整大小,或者使用 Box 过滤器和 0.5 像素窗口在 60 毫秒内调整大小。对于 17mb 的巨大图像,5000x3000px 调整大小在桌面上需要大约 1 秒,在移动设备上需要 3 秒。
All resize principles were described very well in this thread, and pica does not add rocket science. But it's optimized very well for modern JIT-s, and is ready to use out of box (via npm or bower). Also, it use webworkers when available to avoid interface freezes.
此线程中很好地描述了所有调整大小的原则,并且 pica 没有添加火箭科学。但它针对现代 JIT-s 进行了很好的优化,并且可以开箱即用(通过 npm 或 bower)。此外,它在可用时使用网络工作者以避免界面冻结。
I also plan to add unsharp mask support soon, because it's very useful after downscale.
我还计划尽快添加反锐化蒙版支持,因为它在缩小后非常有用。
回答by cesarsalazar
I know this is an old thread but it might be useful for some people such as myself that months after are hitting this issue for the first time.
我知道这是一个旧线程,但它可能对某些人(例如我自己)在几个月后第一次遇到此问题时有用。
Here is some code that resizes the image every time you reload the image. I am aware this is not optimal at all, but I provide it as a proof of concept.
这是一些每次重新加载图像时调整图像大小的代码。我知道这根本不是最佳选择,但我提供它作为概念证明。
Also, sorry for using jQuery for simple selectors but I just feel too comfortable with the syntax.
另外,很抱歉将 jQuery 用于简单的选择器,但我对语法感到太舒服了。
$(document).on('ready', createImage);
$(window).on('resize', createImage);
var createImage = function(){
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth || $(window).width();
canvas.height = window.innerHeight || $(window).height();
var ctx = canvas.getContext('2d');
img = new Image();
img.addEventListener('load', function () {
ctx.drawImage(this, 0, 0, w, h);
});
img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #000;
}
canvas{
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<head>
<meta charset="utf-8" />
<title>Canvas Resize</title>
</head>
<body>
<canvas id="myCanvas"></canvas>
</body>
</html>
My createImage function is called once when the document is loaded and after that it is called every time the window receives a resize event.
我的 createImage 函数在加载文档时被调用一次,之后每次窗口接收到调整大小事件时都会调用它。
I tested it in Chrome 6 and Firefox 3.6, both on the Mac. This "technique" eats processor as it if was ice cream in the summer, but it does the trick.
我在 Mac 上的 Chrome 6 和 Firefox 3.6 中对其进行了测试。这种“技术”就像夏天吃冰淇淋一样吃处理器,但它确实有效。
回答by Daniel
I've put up some algorithms to do image interpolation on html canvas pixel arrays that might be useful here:
我已经提出了一些算法来对 html canvas 像素数组进行图像插值,这些算法在这里可能有用:
https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2
https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2
These can be copy/pasted and can be used inside of web workers to resize images (or any other operation that requires interpolation - I'm using them to defish images at the moment).
这些可以被复制/粘贴,并且可以在网络工作者内部使用来调整图像大小(或任何其他需要插值的操作 - 我目前正在使用它们来去除图像)。
I haven't added the lanczos stuff above, so feel free to add that as a comparison if you'd like.
我没有在上面添加 lanczos 的东西,所以如果你愿意,可以随意添加它作为比较。
回答by Christophe Marois
This is a javascript function adapted from @Telanor's code. When passing a image base64 as first argument to the function, it returns the base64 of the resized image. maxWidth and maxHeight are optional.
这是一个改编自 @Telanor 代码的 javascript 函数。当将图像 base64 作为第一个参数传递给函数时,它返回调整大小的图像的 base64。maxWidth 和 maxHeight 是可选的。
function thumbnail(base64, maxWidth, maxHeight) {
// Max size for thumbnail
if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
if(typeof(maxHeight) === 'undefined') var maxHeight = 500;
// Create and initialize two canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
// Create original image
var img = new Image();
img.src = base64;
// Determine new ratio based on max size
var ratio = 1;
if(img.width > maxWidth)
ratio = maxWidth / img.width;
else if(img.height > maxHeight)
ratio = maxHeight / img.height;
// Draw original image in second canvas
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
// Copy and resize second canvas to first canvas
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);
return canvas.toDataURL();
}
回答by Evan Carroll
I'd highly suggest you check out this linkand make sure it is set to true.
我强烈建议您查看此链接并确保将其设置为 true。
Controlling image scaling behavior
Introduced in Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)
Gecko 1.9.2 introduced the mozImageSmoothingEnabled property to the canvas element; if this Boolean value is false, images won't be smoothed when scaled. This property is true by default. view plainprint?
- cx.mozImageSmoothingEnabled = false;
控制图像缩放行为
在 Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0) 中引入
Gecko 1.9.2 向 canvas 元素引入了 mozImageSmoothingEnabled 属性;如果此布尔值为 false,则缩放时不会平滑图像。默认情况下,此属性为 true。查看普通版?
- cx.mozImageSmoothingEnabled = false;
回答by Xavi
If you're simply trying to resize an image, I'd recommend setting widthand heightof the image with CSS. Here's a quick example:
如果你只是想调整图片大小,我建议设置width和height使用CSS的形象。这是一个快速示例:
.small-image {
width: 100px;
height: 100px;
}
Note that the heightand widthcan also be set using JavaScript. Here's quick code sample:
请注意,也可以使用 JavaScript 设置height和width。这是快速代码示例:
var img = document.getElement("my-image");
img.style.width = 100 + "px"; // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE
Also, to ensure that the resized image looks good, add the following css rules to image selector:
此外,为了确保调整后的图像看起来不错,请将以下 css 规则添加到图像选择器:
-ms-interpolation-mode: bicubic: introduce in IE7image-rendering: optimizeQuality: introduced in FireFox 3.6
-ms-interpolation-mode: bicubic: 在 IE7 中引入image-rendering: optimizeQuality:在 FireFox 3.6 中引入
As far as I can tell, all browsers except IE using an bicubic algorithm to resize images by default, so your resized images should look good in Firefox and Chrome.
据我所知,除 IE 之外的所有浏览器默认使用双三次算法来调整图像大小,因此您调整后的图像在 Firefox 和 Chrome 中应该看起来不错。
If setting the css widthand heightdoesn't work, you may want to play with a css transform:
如果设置 csswidth并且height不起作用,您可能想使用 css transform:
If for whatever reason you needto use a canvas, please note that there are two ways an image can be resize: by resizing the canvas with css or by drawing the image at a smaller size.
如果出于某种原因您需要使用画布,请注意有两种方法可以调整图像大小:使用 css 调整画布大小或以较小的尺寸绘制图像。
See this questionfor more details.
有关更多详细信息,请参阅此问题。
回答by robert
i got this image by right clicking the canvas element in firefox and saving as.
我通过右键单击 Firefox 中的画布元素并另存为来获得此图像。


var img = new Image();
img.onload = function () {
console.debug(this.width,this.height);
var canvas = document.createElement('canvas'), ctx;
canvas.width = 188;
canvas.height = 150;
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';
so anyway, here is a 'fixed' version of your example:
所以无论如何,这是您示例的“固定”版本:
var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body
document.body.appendChild(canvasCopy);
var copyContext = canvasCopy.getContext("2d");
img.onload = function()
{
var ratio = 1;
// defining cause it wasnt
var maxWidth = 188,
maxHeight = 150;
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;
// the line to change
// ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
// the method signature you are using is for slicing
ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};
// changed for example
img.src = 'original.jpg';
回答by Yaffle
For resizing to image with width less that original, i use:
要调整为宽度小于原始宽度的图像,我使用:
function resize2(i) {
var cc = document.createElement("canvas");
cc.width = i.width / 2;
cc.height = i.height / 2;
var ctx = cc.getContext("2d");
ctx.drawImage(i, 0, 0, cc.width, cc.height);
return cc;
}
var cc = img;
while (cc.width > 64 * 2) {
cc = resize2(cc);
}
// .. than drawImage(cc, .... )
and it works =).
它有效=)。

