我使用html5画布元素在浏览器中调整图像大小。原来质量很低。我发现:当缩放
下面是我的css和js代码,以及用Photoshop缩放和在画布API中缩放的图像。
当在浏览器中缩放图像时,我必须做什么来获得最佳质量?
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调整图像大小:
在画布上调整了图像大小:
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步缩小尺寸的结果:
下面是我使用三步缩小尺寸的结果:
下面是如果我使用4步缩小尺寸的结果:
PhotoShop图像:
GameAlchemist的算法:
既然你的问题是缩小你的图像,那么讨论插值是没有意义的--它是关于创建像素的--。这里的问题是下采样。
要对图像进行下采样,我们需要将原始图像中p*p像素的每个平方变成目标图像中的单个像素。
出于性能的原因,浏览器会做一个非常简单的下采样:为了构建更小的图像,它们只会在源中选取一个像素,并将其值用于目标。这会“忘记”一些细节并增加噪音。
但是有一个例外:由于2倍图像下采样非常容易计算(平均4个像素生成一个),并且用于视网膜/HIDPI像素,所以这种情况处理得很好--浏览器确实使用了4个像素生成一个--。
但是...如果您多次使用2倍下采样,您将面临一个问题,即连续的舍入误差将增加太多噪声。
更糟糕的是,您不会总是以2的次方调整大小,并且调整到最近的次方+最后一次调整大小是非常嘈杂的。
您所寻求的是像素完美的下采样,即:将所有输入像素考虑在内的图像的重新采样--无论比例大小--。
为此,我们必须计算每个输入像素对一个、两个或四个目标像素的贡献,这取决于输入像素的缩放投影是否在目标像素内部,是否与X边框、Y边框或两者重叠。
(这里有一个方案很好,但我没有。)
这是一个画布比例尺和我的像素完美比例尺的例子,在僵尸的1/3比例尺上。
请注意,图片可能会在浏览器中缩放,并由S.O.jpeg化。
但我们看到噪声要小得多,特别是袋熊后面的草丛和它右边的树枝。毛皮中的噪声使它更有对比度,但它看起来像是白头发--不像源图--。
右图不那么上口,但肯定更好。
下面是执行像素完美缩放的代码:
拨弄结果: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毫秒。
问题内容: 我正在尝试使用javascript和canvas元素在客户端创建缩略图,但是当我缩小图像时,它看起来很糟糕。看起来好像是在Photoshop中缩小了尺寸,将重采样设置为“最近的邻居”而不是Bicubic。我知道有可能使它看起来正确,因为该站点也可以使用画布来完成它。我已经尝试使用与[[Source]]链接中所示相同的代码,但是看起来仍然很糟糕。是否缺少我需要的设置,需要设置的设置? 编
谢谢你看。 我在这里尝试做的是...从计算机加载图像(通过使用FileReader),将img标签的src放入文件。然后用那个图像画画布。 问题是当我加载另一个图像文件时。它在画布中加载,但不适合画布大小。它保持原始图像大小,图像的“部分”显示在画布中。 为了解决这个问题,我尝试了以下方法:ctx。drawImage(img,0,0,document.getElementById('canvas'
我有一个表单,允许用户上传图像。 加载映像后,我们对其执行一些缩放,以便在将其传递回服务器之前减小其文件大小。 为此,我们将其放置在画布上并在画布上进行操作。 这段代码将在画布上呈现缩放后的图像,画布大小为320 x 240px: ... 哪里有帆布。宽度和画布。高度是图像高度和宽度x,是基于原始图像大小的比例因子。 但当我使用代码时: ...我只得到画布上图像的一部分,在这种情况下是左上角。我需
问题内容: 我想拍摄一张图像并更改图像的比例,虽然它是一个numpy数组。 例如,我有一个可口可乐瓶的图像: bottle-1 转换为一个numpy的形状数组,我想调整其大小以表示第二个图像的大小: bottle-2 形状为。 如何在保持原始图像的同时将图像尺寸更改为特定形状?其他答案建议每隔一行或第三行剥离,但是我想要做的基本上是像通过图像编辑器(但在python代码中)那样收缩图像。是否有任何
一直以来,我都在开发一款分辨率为800x480(流行手机分辨率)的应用程序。 我的画布HTML: 现在,为了使其全屏显示其他分辨率,我在调整大小事件后执行以下操作: 这是可行的,但问题是我不能以这种方式保持纵横比。800x480是4:3,但是如果我在5:3的手机上运行这个应用程序,有些东西(尤其是圆圈)看起来会被拉伸。 有没有什么方法可以让它在所有分辨率下看起来都很好,而不必为每个纵横比创建一组独
我正在使用画布来修改我的图像。但是,我有一个问题。如果我使用options.inSamplesize=4;图像变得太小,质量下降。但是,如果我不给,它会从内存溢出并给出一个错误。 //位图缩放位图=位图。创建位图(原始1、0、0、宽度、高度、矩阵、真); 这是我当前的错误日志。 11-09 03:21:56.849 12345-12345/com.example.chkee.appfrontE/A