gpt4 book ai didi

javascript - HTML5 Canvas 调整大小(缩小)图像质量?

转载 作者:IT老高 更新时间:2023-10-28 13:18:56 28 4
gpt4 key购买 nike

我使用 html5 Canvas 元素在我的浏览器中调整图像大小。事实证明,质量非常低。我发现了这个:Disable Interpolation when Scaling a <canvas>但这无助于提高质量。

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

在浏览器中缩放图像时,如何才能获得最佳质量?

注意:我想将大图像缩小为小图像,修改 Canvas 中的颜色并将结果从 Canvas 发送到服务器。

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

用 Photoshop 调整大小的图像:

enter image description here

在 Canvas 上调整大小的图像:

enter image description here

编辑:

我尝试按照以下建议的多个步骤进行缩减:

Resizing an image in an HTML5 canvasHtml5 canvas drawImage: how to apply antialiasing

这是我用过的函数:

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:我尝试了 GameAlchemist 的算法。这是与 Photoshop 相比的结果。

PhotoShop 图片:

PhotoShop Image

GameAlchemist 的算法:

GameAlchemist's Algorithm

最佳答案

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

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

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

但有一个异常(exception):因为 2X 图像下采样非常易于计算(平均 4 个像素来制作一个)并且用于视网膜/HiDPI 像素,所以这种情况处理得当 - 浏览器确实使用了 4像素制作一个-。

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

您寻求的是像素完美的下采样,即:对图像进行重新采样,将所有输入像素都考虑在内-无论比例如何-。
为此,我们必须针对每个输入像素计算其对一个、两个或四个目标像素的贡献,具体取决于输入像素的缩放投影是否正好在目标像素内、是否与 X 边界、Y 边界重叠,或两者都重叠.
(这里有一个方案很好,但我没有。)

这是一个 Canvas 比例与我的像素完美比例在 1/3 的 zombat 比例上的示例。

请注意,图片可能会在您的浏览器中缩放,并由 S.O..jpegized..
然而,我们看到噪音要小得多,尤其是在袋熊身后的草丛中,以及它右侧的 Twig 上。毛皮中的噪点使它更加对比鲜明,但看起来他有白毛——与源图片不同——。
右图不那么吸引人,但绝对更好。

enter image description here

这是进行像素完美缩小的代码:

fiddle 结果: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
fiddle 本身: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;
}

这是相当内存贪婪,因为需要一个浮点缓冲区来存储目标图像的中间值(-> 如果我们计算结果 Canvas ,我们使用源图像的 6 倍内存这个算法)。
它也很昂贵,因为无论目标大小如何都使用每个源像素,并且我们必须为 getImageData/putImageDate 付费,也很慢。
但是在这种情况下,没有办法比处理每个源值更快,而且情况还不错:对于我的 740 * 556 袋熊图像,处理需要 30 到 40 毫秒。

关于javascript - HTML5 Canvas 调整大小(缩小)图像质量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18922880/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com