HTML5画布大小调整(低比例)图像高质量? ?

我使用 html5画布元素在浏览器中调整图像的大小。结果证明质量非常低。我发现这个: 当缩放一个 < 画布 > 时禁用插值,但它不帮助提高质量。

下面是我的 css 和 js 代码以及用 Photoshop 调用并在画布 API 中缩放的图像。

在浏览器中缩放图像时,我需要做什么才能获得最佳质量?

注意: 我想缩小一个较大的图像到一个较小的,修改画布的颜色,并将结果从画布发送到服务器。

CSS:

canvas, img {
image-rendering: optimizeQuality;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}

约翰逊:

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {




var originalContext = $originalCanvas[0].getContext('2d');
originalContext.imageSmoothingEnabled = false;
originalContext.webkitImageSmoothingEnabled = false;
originalContext.mozImageSmoothingEnabled = false;
originalContext.drawImage(this, 0, 0, 379, 500);
});

使用 Photoshop 调整图像大小:

enter image description here

图片在画布上调整大小:

enter image description here

编辑:

我试图通过以下几个步骤来缩小规模:

在 HTML5画布 Html5画布图像: 如何应用反锯齿

这是我使用的函数:

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
var imgWidth = img.width,
imgHeight = img.height;


var ratio = 1, ratio1 = 1, ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;


// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
}
else {
ratio = ratio2;
}


var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);


// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);




var rounds = 2;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
console.log("Step: "+i);


// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;


copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);


// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


} // end for




// copy back to canvas
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);




}

如果我使用2步缩小规模,结果如下:

enter image description here

如果我使用3步缩小规模,结果如下:

enter image description here

如果我使用4步缩小规模,结果如下:

enter image description here

如果我使用20步缩小规模,结果如下:

enter image description here

注意: 结果表明,从1步到2步,图像质量有了很大的提高,但是添加到过程中的步骤越多,图像就变得越模糊。

有没有一种方法可以解决这个问题: 你添加的步骤越多,图像就越模糊

编辑2013-10-04: 我尝试了游戏炼金术师的算法。这里是比较 Photoshop 的结果。

PhotoShop 图片来源:

PhotoShop Image

游戏炼金术士算法:

GameAlchemist's Algorithm

187291 次浏览

为什么要使用画布来调整图像的大小?现代浏览器都使用双三次插值——和 Photoshop 使用的过程一样(如果你做得对的话)——而且它们比画布过程更快。只需指定所需的图像大小(只使用一个维度,高度或宽度,以便按比例调整大小)。

大多数浏览器都支持这个功能,包括 IE 的早期版本 可能需要特定于浏览器的 CSS

一个调整图像大小的简单函数(使用 jQuery)如下:

function resizeImage(img, percentage) {
var coeff = percentage/100,
width = $(img).width(),
height = $(img).height();


return {"width": width*coeff, "height": height*coeff}
}

然后只需使用返回的值调整图像的一个或两个维度的大小。

显然,您可以进行不同的改进,但这样可以完成工作。

将下面的代码粘贴到本页面的控制台中,然后观察 Gravatars 会发生什么情况:

function resizeImage(img, percentage) {
var coeff = percentage/100,
width = $(img).width(),
height = $(img).height();


return {"width": width*coeff, "height": height*coeff}
}


$('.user-gravatar32 img').each(function(){
var newDimensions = resizeImage( this, 150);
this.style.width = newDimensions.width + "px";
this.style.height = newDimensions.height + "px";
});

因为你的问题是降低你的图像尺寸,所以没有必要谈论插值-这是关于创建像素-。这里的问题是采样不足。

要对图像进行下采样,我们需要将原始图像中的每个 p * p 像素的正方形转换为目标图像中的单个像素。

出于性能方面的原因,浏览器会做一个非常简单的下采样: 为了构建更小的图像,他们只需要在源中选择一个像素,并将其值用于目标。它会“忘记”一些细节,增加噪音。

然而,这里有一个例外: 由于2X 图像下采样非常容易计算(平均4像素制作一个) ,并用于视网膜/HiDPI 像素,这种情况下处理得当-浏览器确实使用4像素制作一个-。

但是... 如果你使用几次2倍的下采样,你将面临的问题是,连续舍入误差将增加太多的噪音。
更糟糕的是,你不会总是调整大小的2次幂,调整到最近的幂 + 最后一次调整大小是非常嘈杂的。

你所寻找的是一个像素完美的下采样,也就是说: 一个重新采样的图像,将考虑到所有的输入像素-无论规模-。
为此,我们必须计算每个输入像素对一个、两个或四个目标像素的贡献,具体取决于输入像素的缩放投影是在目标像素内部,还是与 X 边框、 Y 边框重叠,或两者兼而有之。
(在这里有一个计划是不错的,但是我没有。)

这里有一个例子,画布比例与我的像素完美比例的1/3规模的僵尸。

请注意,图片可能会在您的浏览器中缩放,并且是由 S.O. 缩放的。
但是我们看到噪音很小尤其是袋熊身后的草地和它右边的树枝。毛发中的噪音使它更加对比鲜明,但它看起来像他有白色的头发-不像来源图片-。
右边的图片不那么吸引人,但绝对更好。

enter image description here

下面是完美缩放像素的代码:

假结果: Http://jsfiddle.net/gamealchemist/r6avp/embedded/result/
自欺欺人: http://jsfiddle.net/gamealchemist/r6avp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
var imgCV = document.createElement('canvas');
imgCV.width = img.width;
imgCV.height = img.height;
var imgCtx = imgCV.getContext('2d');
imgCtx.drawImage(img, 0, 0);
return downScaleCanvas(imgCV, scale);
}


// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
var sqScale = scale * scale; // square scale = area of source pixel within target
var sw = cv.width; // source image width
var sh = cv.height; // source image height
var tw = Math.floor(sw * scale); // target image width
var th = Math.floor(sh * scale); // target image height
var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
var tX = 0, tY = 0; // rounded tx, ty
var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
// weight is weight of current source point within target.
// next weight is weight of current source point within next target's point.
var crossX = false; // does scaled px cross its current px right border ?
var crossY = false; // does scaled px cross its current px bottom border ?
var sBuffer = cv.getContext('2d').
getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
var sR = 0, sG = 0,  sB = 0; // source's current point r,g,b
/* untested !
var sA = 0;  //source alpha  */


for (sy = 0; sy < sh; sy++) {
ty = sy * scale; // y src position within target
tY = 0 | ty;     // rounded : target pixel's y
yIndex = 3 * tY * tw;  // line index within target array
crossY = (tY != (0 | ty + scale));
if (crossY) { // if pixel is crossing botton target pixel
wy = (tY + 1 - ty); // weight of point within target pixel
nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
}
for (sx = 0; sx < sw; sx++, sIndex += 4) {
tx = sx * scale; // x src position within target
tX = 0 |  tx;    // rounded : target pixel's x
tIndex = yIndex + tX * 3; // target pixel index within target array
crossX = (tX != (0 | tx + scale));
if (crossX) { // if pixel is crossing target pixel's right
wx = (tX + 1 - tx); // weight of point within target pixel
nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
}
sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
sG = sBuffer[sIndex + 1];
sB = sBuffer[sIndex + 2];


/* !! untested : handling alpha !!
sA = sBuffer[sIndex + 3];
if (!sA) continue;
if (sA != 0xFF) {
sR = (sR * sA) >> 8;  // or use /256 instead ??
sG = (sG * sA) >> 8;
sB = (sB * sA) >> 8;
}
*/
if (!crossX && !crossY) { // pixel does not cross
// just add components weighted by squared scale.
tBuffer[tIndex    ] += sR * sqScale;
tBuffer[tIndex + 1] += sG * sqScale;
tBuffer[tIndex + 2] += sB * sqScale;
} else if (crossX && !crossY) { // cross on X only
w = wx * scale;
// add weighted component for current px
tBuffer[tIndex    ] += sR * w;
tBuffer[tIndex + 1] += sG * w;
tBuffer[tIndex + 2] += sB * w;
// add weighted component for next (tX+1) px
nw = nwx * scale
tBuffer[tIndex + 3] += sR * nw;
tBuffer[tIndex + 4] += sG * nw;
tBuffer[tIndex + 5] += sB * nw;
} else if (crossY && !crossX) { // cross on Y only
w = wy * scale;
// add weighted component for current px
tBuffer[tIndex    ] += sR * w;
tBuffer[tIndex + 1] += sG * w;
tBuffer[tIndex + 2] += sB * w;
// add weighted component for next (tY+1) px
nw = nwy * scale
tBuffer[tIndex + 3 * tw    ] += sR * nw;
tBuffer[tIndex + 3 * tw + 1] += sG * nw;
tBuffer[tIndex + 3 * tw + 2] += sB * nw;
} else { // crosses both x and y : four target points involved
// add weighted component for current px
w = wx * wy;
tBuffer[tIndex    ] += sR * w;
tBuffer[tIndex + 1] += sG * w;
tBuffer[tIndex + 2] += sB * w;
// for tX + 1; tY px
nw = nwx * wy;
tBuffer[tIndex + 3] += sR * nw;
tBuffer[tIndex + 4] += sG * nw;
tBuffer[tIndex + 5] += sB * nw;
// for tX ; tY + 1 px
nw = wx * nwy;
tBuffer[tIndex + 3 * tw    ] += sR * nw;
tBuffer[tIndex + 3 * tw + 1] += sG * nw;
tBuffer[tIndex + 3 * tw + 2] += sB * nw;
// for tX + 1 ; tY +1 px
nw = nwx * nwy;
tBuffer[tIndex + 3 * tw + 3] += sR * nw;
tBuffer[tIndex + 3 * tw + 4] += sG * nw;
tBuffer[tIndex + 3 * tw + 5] += sB * nw;
}
} // end for sx
} // end for sy


// create result canvas
var resCV = document.createElement('canvas');
resCV.width = tw;
resCV.height = th;
var resCtx = resCV.getContext('2d');
var imgRes = resCtx.getImageData(0, 0, tw, th);
var tByteBuffer = imgRes.data;
// convert float32 array into a UInt8Clamped Array
var pxIndex = 0; //
for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
tByteBuffer[tIndex + 3] = 255;
}
// writing result to canvas.
resCtx.putImageData(imgRes, 0, 0);
return resCV;
}

由于存储目标图像的中间值需要一个浮点缓冲区(- > ,如果我们计算结果画布,我们将使用6倍于源图像的内存) ,因此 没错内存非常贪婪。
这也是相当昂贵的,因为每个源像素的使用不管目标大小,而且我们必须为 getImageData/putImageDate 付费,速度也相当慢。
但在这种情况下,没有比处理每个源值更快的方法了,情况也没有那么糟糕: 对于我的740 * 556袋熊图像,处理需要30到40毫秒。

质量好的快速帆布重采样: http://jsfiddle.net/9g9Nv/442/

更新: 版本2.0(更快,web 工作者 + 可转移对象)-< a href = “ https://github.com/viliusle/Hermite-resize”rel = “ norefrer”> https://github.com/viliusle/hermite-resize

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

建议1-延长工艺管线

你可以像我在你提到的链接中描述的那样使用降级,但是你似乎用错了方式。

如果图像比例高于1:2,则不需要降低比例(通常情况下,但不限于1:2)。这是你需要进行 激烈缩放的地方,你需要根据图像的内容将它分成两个(很少,更多)步骤(特别是在高频率如细线出现的地方)。

每次你向下采样一个图像,你会失去细节和信息。您不能指望得到的图像像原始图像一样清晰。

如果你然后缩小图像在许多步骤,你会失去总的很多信息,结果将是糟糕的,因为你已经注意到。

试着多走一步,或者最多走两步。

回旋

在 Photoshop 的情况下,通知它应用卷积后的图像已经重新采样,如锐化。这不仅仅是双三次插值发生,所以为了完全模拟 Photoshop,我们还需要添加 Photoshop 正在做的步骤(默认设置)。

对于这个例子,我将使用我的原始答案,你在你的文章中提到,但我已经添加了一个尖锐的卷积,以提高质量作为一个后处理(见底部演示)。

下面是添加锐化滤波器的代码(它是基于一个通用的卷积滤波器-我把锐化的权重矩阵放在里面,还有一个混合因子来调整效果的发音) :

用法:

sharpen(context, width, height, mixFactor);

mixFactor是一个介于[0.0,1.0]之间的值,它允许你淡化锐化效应——经验法则: 尺寸越小,需要的效果就越少。

函数 (基于 这个片段) :

function sharpen(ctx, w, h, mix) {


var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
katet = Math.round(Math.sqrt(weights.length)),
half = (katet * 0.5) |0,
dstData = ctx.createImageData(w, h),
dstBuff = dstData.data,
srcBuff = ctx.getImageData(0, 0, w, h).data,
y = h;
        

while(y--) {


x = w;


while(x--) {


var sy = y,
sx = x,
dstOff = (y * w + x) * 4,
r = 0, g = 0, b = 0, a = 0;


for (var cy = 0; cy < katet; cy++) {
for (var cx = 0; cx < katet; cx++) {


var scy = sy + cy - half;
var scx = sx + cx - half;


if (scy >= 0 && scy < h && scx >= 0 && scx < w) {


var srcOff = (scy * w + scx) * 4;
var wt = weights[cy * katet + cx];


r += srcBuff[srcOff] * wt;
g += srcBuff[srcOff + 1] * wt;
b += srcBuff[srcOff + 2] * wt;
a += srcBuff[srcOff + 3] * wt;
}
}
}


dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
}
}


ctx.putImageData(dstData, 0, 0);
}

使用这种组合的结果将是:

这里是在线演示

Result downsample and sharpen convolution

这取决于你想为混合效果增加多少锐化效果,你可以从默认的“模糊”到非常锐化:

Variations of sharpen

建议2-低级算法的实现

如果你想得到最好的结果-明智的质量,你将需要去低层次,并考虑实施例如这个全新的算法来做到这一点。

请参阅 IEEE 的 与插值相关的图像下采样(2011)。
以下是全文的链接(PDF)

目前在 JavaScriptAFAIK 中还没有这种算法的实现,所以如果你想全身心投入到这项任务中,你就得全力以赴。

其实质是:

摘要

提出了一种面向插值的自适应下采样算法 本文提出了一种低码率图像编码方法 提出的算法能够获得低分辨率的图像,从 其中高品质的图像与相同的分辨率作为输入 图像可以插值。不同于传统的 下采样算法,这是独立于 插值过程中,提出的下采样算法铰链的 向下采样到插值过程。因此, 提出的下采样算法能够保持原有的 信息的输入图像的最大程度。下采样 然后将图像输入 JPEG 然后对解压缩后的低分辨率图像进行处理。 最终,对处理后的图像进行插值以维护 输入图像的原始分辨率。 实验结果验证 利用该算法的下采样图像,一个 可以获得高质量的插值图像。此外, 提出的算法能够取得比 用于低码率图像编码的 JPEG

Snapshot from paper

(所有细节、公式等参见提供的链接。)

我找到了一个解决方案,它不需要直接访问像素数据,并通过循环执行下采样。根据图像的大小,这可能是非常耗费资源的,最好使用浏览器的内部算法。

DrawImage ()函数采用线性插值、最近邻重采样的方法。

如果一次循环只调整最大大小的一半,那么结果将非常好,比访问像素数据快得多。

这个函数一次下采样到一半,直到达到所需的大小:

  function resize_image( src, dst, type, quality ) {
var tmp = new Image(),
canvas, context, cW, cH;


type = type || 'image/jpeg';
quality = quality || 0.92;


cW = src.naturalWidth;
cH = src.naturalHeight;


tmp.src = src.src;
tmp.onload = function() {


canvas = document.createElement( 'canvas' );


cW /= 2;
cH /= 2;


if ( cW < src.width ) cW = src.width;
if ( cH < src.height ) cH = src.height;


canvas.width = cW;
canvas.height = cH;
context = canvas.getContext( '2d' );
context.drawImage( tmp, 0, 0, cW, cH );


dst.src = canvas.toDataURL( type, quality );


if ( cW <= src.width || cH <= src.height )
return;


tmp.src = dst.src;
}


}
// The images sent as parameters can be in the DOM or be image objects
resize_image( $( '#original' )[0], $( '#smaller' )[0] );

这里是一个可重用的角度服务为高质量的图像/画布调整大小: https://gist.github.com/fisch0920/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
})
})

这是改进的 Hermite 大小调整过滤器,利用1个工人,使窗口不冻结。

Https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()


/* Promise */
blitz({
source: DOM Image/DOM Canvas/jQuery/DataURL/File,
width: 400,
height: 600
}).then(output => {
// handle output
})catch(error => {
// handle error
})


/* Await */
let resized = await blitz({...})


/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
// run your callback.
})

如果您希望只使用画布,最好的结果将与多个下降步骤。但这还不够。为了获得更好的质量,您需要纯粹的 js 实现。我们刚刚发布了 异食癖高速下标器与可变质量/速度。简而言之,它在 ~ 0.1 s 内调整大小为1280.1024px,在1 s 内调整大小为5000 * 3000px,具有最高的质量(3瓣 lanczos 滤波器)。Pica 有 小样,你可以在那里玩你的图像,质量水平,甚至可以在移动设备上尝试。

异食癖还没有不锋利的面具,但很快就会添加。这比实现调整大小的高速卷积滤波器容易得多。

context.scale(xScale, yScale)

<canvas id="c"></canvas>
<hr/>
<img id="i" />


<script>
var i = document.getElementById('i');


i.onload = function(){
var width = this.naturalWidth,
height = this.naturalHeight,
canvas = document.getElementById('c'),
ctx = canvas.getContext('2d');


canvas.width = Math.floor(width / 2);
canvas.height = Math.floor(height / 2);


ctx.scale(0.5, 0.5);
ctx.drawImage(this, 0, 0);
ctx.rect(0,0,500,500);
ctx.stroke();


// restore original 1x1 scale
ctx.scale(2, 2);
ctx.rect(0,0,500,500);
ctx.stroke();
};


i.src = 'https://static.md/b70a511140758c63f07b618da5137b5d.png';
</script>

对于那些真正需要调整图像大小的人来说,这不是正确的答案。

我有一个问题与“直接从相机”的图片,我的客户往往上传在“未压缩”JPEG。

不太为人所知的是,画布支持(在大多数2017年的浏览器中)改变 JPEG 的质量

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

使用这个技巧,我可以减少4k x 3k 的图片大于10Mb 到1或2Mb,当然这取决于你的需要。

看这里

也许你可以试试这个,这是我在我的项目中经常使用的。这样你不仅可以得到高质量的图像,而且你的画布上的任何其他元素。

/*
* @parame canvas => canvas object
* @parame rate => the pixel quality
*/
function setCanvasSize(canvas, rate) {
const scaleRate = rate;
canvas.width = window.innerWidth * scaleRate;
canvas.height = window.innerHeight * scaleRate;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
canvas.getContext('2d').scale(scaleRate, scaleRate);
}

DEMO : 使用 JS 和 HTML Canvas DEMO fiddler 调整图像大小。

您可能会找到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 size 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
}
};
}}

如果我们加上 1.0,你会得到准确的答案。

data=canvas.toDataURL('image/jpeg', 1.0);

你可以得到清晰明亮的图像。请检查

我确实尽量避免浏览图像数据,特别是在较大的图像上。因此,我想出了一个相当简单的方法,使用一些额外的步骤,在没有任何限制或限制的情况下,体面地减小图像大小。 这个例程在所需的目标大小之前尽可能降低到最低的半步。然后将其扩大到目标尺寸的两倍,然后再缩小一半。一开始听起来很有趣,但是结果是惊人的好,而且进展很快。

function resizeCanvas(canvas, newWidth, newHeight) {
let ctx = canvas.getContext('2d');
let buffer = document.createElement('canvas');
buffer.width = ctx.canvas.width;
buffer.height = ctx.canvas.height;
let ctxBuf = buffer.getContext('2d');
  



let scaleX = newWidth / ctx.canvas.width;
let scaleY = newHeight / ctx.canvas.height;


let scaler = Math.min(scaleX, scaleY);
//see if target scale is less than half...
if (scaler < 0.5) {
//while loop in case target scale is less than quarter...
while (scaler < 0.5) {
ctxBuf.canvas.width = ctxBuf.canvas.width * 0.5;
ctxBuf.canvas.height = ctxBuf.canvas.height * 0.5;
ctxBuf.scale(0.5, 0.5);
ctxBuf.drawImage(canvas, 0, 0);
ctxBuf.setTransform(1, 0, 0, 1, 0, 0);
ctx.canvas.width = ctxBuf.canvas.width;
ctx.canvas.height = ctxBuf.canvas.height;
ctx.drawImage(buffer, 0, 0);


scaleX = newWidth / ctxBuf.canvas.width;
scaleY = newHeight / ctxBuf.canvas.height;
scaler = Math.min(scaleX, scaleY);
}
//only if the scaler is now larger than half, double target scale trick...
if (scaler > 0.5) {
scaleX *= 2.0;
scaleY *= 2.0;
ctxBuf.canvas.width = ctxBuf.canvas.width * scaleX;
ctxBuf.canvas.height = ctxBuf.canvas.height * scaleY;
ctxBuf.scale(scaleX, scaleY);
ctxBuf.drawImage(canvas, 0, 0);
ctxBuf.setTransform(1, 0, 0, 1, 0, 0);
scaleX = 0.5;
scaleY = 0.5;
}
} else
ctxBuf.drawImage(canvas, 0, 0);


//wrapping things up...
ctx.canvas.width = newWidth;
ctx.canvas.height = newHeight;
ctx.scale(scaleX, scaleY);
ctx.drawImage(buffer, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}