HTML5画布调整大小(缩小)图像的质量?

我使用html5 canvas元素在浏览器中调整图像大小。事实证明,质量很低。我发现了这一点:在缩放时禁用插值,但它无助于提高质量。

下面是我的css和js代码,以及用Photoshop调用并在canvas 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;

}

JS:

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

});

这是我使用的功能:

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

}

回答:

由于你的问题是缩小图像,因此谈论插值(即创建像素)毫无意义。这里的问题是下采样。

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

出于性能原因,浏览器进行了非常简单的下采样:要生成较小的图像,它们将仅在源中选择一个像素并将其值用作目标。这会“忘记”一些细节并增加噪音。

但是有一个例外:由于2X图像下采样的计算非常简单(平均4个像素即可制作一个),并且用于视网膜/ HiDPI像素,因此这种情况得到了正确处理-浏览器确实使用4个像素来制作一-。

但是…如果你多次使用2X下采样,则会遇到连续的舍入误差会增加过多噪声的问题。

更糟糕的是,你将无法始终将大小调整为2的幂,并且将大小调整为最接近的幂+最后一次调整大小会非常嘈杂。

你要寻找的是像素完美的下采样,即:对图像进行重新采样,无论尺寸如何,都将考虑所有输入像素。

为此,我们必须针对每个输入像素计算其对一个,两个或四个目标像素的贡献,具体取决于输入像素的缩放投影是否恰好在目标像素内部,与X边界,Y边界或两者重叠。

(一个计划在这里会很好,但是我没有一个。)

这是一个画布比例与我的像素完美比例(在1/3缩放比例下)的示例。

请注意,图片可能会在浏览器中缩放,并以.jpeg格式化。

但是,我们看到的噪音要少得多,尤其是在袋熊后面的草丛中以及在其右边的树枝中。皮毛上的噪音使它更具反差,但看起来他像白发(与原始图片不同)。

正确的图像不那么吸引眼球,但绝对更好。

// 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毫秒之间。

以上是 HTML5画布调整大小(缩小)图像的质量? 的全部内容, 来源链接: utcz.com/qa/422232.html

回到顶部