HTML5 Canvas 缩放(降低分辨率)图像高质量?

8 浏览
0 Comments

HTML5 Canvas 缩放(降低分辨率)图像高质量?

我在浏览器中使用html5画布元素来调整图像大小。结果发现图像质量非常低。我找到了这个链接:Disable Interpolation when Scaling a 但它并不能提高图像质量。

以下是我的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 = $('');
var $originalCanvas = $('');
$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 canvas

Html5 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;
    // 使用最适合将图像放入maxWidth x maxHeight框的最小比例。
    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);
    // 初始化
    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);
        // 临时
        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);
        // 复制回来
        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
    // 复制回画布
    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

0
0 Comments

HTML5 Canvas 图片缩放(降低分辨率)的高质量问题是什么原因以及解决方法?

问题的原因是为了缩小图像,而不是进行插值(创建像素)的讨论。这里的问题是降采样。为了对图像进行降采样,我们需要将原始图像中的每个 p * p 像素的正方形转换为目标图像中的单个像素。

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

然而,有一个例外:由于2倍图像降采样非常简单(平均4个像素得到一个像素)并且用于视网膜/高DPI像素,因此这种情况得到了适当处理-浏览器确实使用了4个像素来生成一个像素。

但是...如果您多次使用2倍降采样,您将面临连续的舍入误差会增加太多噪音的问题。

更糟糕的是,您不总是按2的幂进行调整大小,并且调整为最近的幂加上最后一个调整会非常嘈杂。

您想要的是像素完美的降采样,即:图像的重新采样将考虑到所有输入像素-无论比例如何。

为了做到这一点,我们必须计算出每个输入像素对于一个、两个或四个目标像素的贡献,具体取决于缩放的输入像素的投影是否在目标像素的内部,是否重叠X边界,Y边界或两者都重叠。

这里是一个画布缩放和我的像素完美缩放的示例,图像是一只袋鼠的1/3缩放。

请注意,图片可能在您的浏览器中缩放,并且被操作系统.jpeg化。

然而,我们可以看到,在袋鼠后面的草地和右边的树枝上,噪音要少得多。毛发上的噪音使其更加对比鲜明,但看起来像是有白色的毛发-与原始图片不同。

右侧的图像没有那么吸引人,但绝对更好。

以下是执行像素完美缩小的代码:

// 将图像按(float) scale < 1进行缩放
// 返回一个包含缩放图像的画布。
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);
}
// 将画布按(float) scale < 1进行缩放
// 返回一个包含缩放图像的新画布。
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // 源像素在目标像素内的面积
    var sw = cv.width; // 源图像宽度
    var sh = cv.height; // 源图像高度
    var tw = Math.floor(sw * scale); // 目标图像宽度
    var th = Math.floor(sh * scale); // 目标图像高度
    var sx = 0, sy = 0, sIndex = 0; // 源 x, y, 索引
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // 目标 x, y, 索引
    var tX = 0, tY = 0; // 四舍五入的目标 x, y
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // 权重 / 下一个权重 x / y
    // 权重是当前源点在目标中的权重。
    // 下一个权重是当前源点在下一个目标点中的权重。
    var crossX = false; // 缩放像素是否跨越当前像素的右边界?
    var crossY = false; // 缩放像素是否跨越当前像素的底边界?
    var sBuffer = cv.getContext('2d').getImageData(0, 0, sw, sh).data; // 源缓冲区8位RGBA
    var tBuffer = new Float32Array(3 * tw * th); // 目标缓冲区Float32 RGB
    var sR = 0, sG = 0,  sB = 0; // 源的当前点 r,g,b
    /* 未经测试!
    var sA = 0;  // 源 alpha  */    
    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y 在目标中的位置
        tY = 0 | ty;     // 四舍五入的:目标像素的 y
        yIndex = 3 * tY * tw;  // 在目标数组中的行索引
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // 如果像素越过目标像素的底部
            wy = (tY + 1 - ty); // 在目标像素中的权重
            nwy = (ty + scale - tY - 1); // 在下一个y+1目标像素中的权重
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x 在目标中的位置
            tX = 0 |  tx;    // 四舍五入的:目标像素的 x
            tIndex = yIndex + tX * 3; // 目标像素在目标数组中的索引
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // 如果像素越过目标像素的右边界
                wx = (tX + 1 - tx); // 在目标像素中的权重
                nwx = (tx + scale - tX - 1); // 在下一个x+1目标像素中的权重
            }
            sR = sBuffer[sIndex    ];   // 获取当前源像素的 r,g,b
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];
            /* !! 未经测试:处理 alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // 或者使用 /256 代替??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // 像素未越界
                // 仅通过加权平方比例添加组件
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // 仅越过X轴
                w = wx * scale;
                // 添加当前像素的加权组件
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // 添加下一个(tX+1)像素的加权组件                
                nw = nwx * scale;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // 仅越过Y轴
                w = wy * scale;
                // 添加当前像素的加权组件
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // 添加下一个(tY+1)像素的加权组件                
                nw = nwy * scale;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // 同时越过X轴和Y轴:涉及四个目标点
                // 添加当前像素的加权组件
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // 对于tX + 1; tY 像素
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // 对于tX ; tY + 1 像素
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // 对于tX + 1 ; tY +1 像素
                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
    // 创建结果画布
    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;
    // 将Float32数组转换为UInt8Clamped数组
    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;
    }
    // 将结果写入画布
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

这是一个非常耗费内存的算法,因为需要一个浮点缓冲区来存储目标图像的中间值(如果计算结果画布,这个算法使用源图像内存的6倍)。这也非常昂贵,因为无论目标大小如何,每个源像素都会被使用,并且我们必须付出很多代价来获取和写入图像数据,这也是非常慢的。

但是,在这种情况下,没有比处理每个源值更快的方法,而且情况并不是那么糟糕:对于我一只740x556的袋鼠的图像,处理时间在30到40毫秒之间。

如果在放入画布之前缩放图像,速度会更快吗?

我不明白...似乎这就是我所做的。缓冲区以及我创建的画布(resCV)的大小与缩放图像的大小相同。我认为更快的唯一方法是使用类似于breshensam的整数计算。但是,对于视频游戏来说,40毫秒只是慢了一点(25帧每秒),对于绘图应用程序来说并不慢。

您是否认为在保持质量的同时加快算法的速度有机会吗?

我尝试通过使用0 | 来舍入缓冲区(算法的最后部分)而不是使用Math.ceil来使其更快。速度确实有所提高。但是无论如何,使用get/putImageData存在相当多的开销,并且我们无法避免处理每个像素。

在使用context.ImageSmoothingEnabled或不使用时是否会有区别?

不会,因为此设置是针对drawImage的。在这里,我只使用了get/putImageData,它们与“原始”图像处理有关。

这会生成高质量的缩小图像,但是对于具有不透明度/透明度的GIF/PNG存在问题。整个透明区域会变成黑色。有什么想法为什么会这样?

嗯,我根本没有处理不透明度,所以透明==黑色。我编辑了代码以处理alpha,但我没有时间测试它,所以我暂时将其注释掉。如果您进行了测试,请向我更新。

我创建了一个jsFiddle,其中取消了注释的新脚本的alpha部分。看起来不起作用,因为完全透明的部分仍然变成纯黑色。如果我对alpha使用256而不是8,我会在曲线上获得透明度,所以这似乎是一个更好的解决方案。

好的,感谢您提醒。如果您查看downScaleCanvas的代码末尾,每个像素都会获得255的alpha值,因此所有内容都是不透明的。我必须仔细考虑这个问题。快速修复方法:将#000视为透明,并在这种情况下将alpha设置为0。或者让tbuffer为rgb'c',其中c是非透明像素的计数(您可以猜到结尾)。真正的解决方案似乎是像r,g,b一样插值a,但我不清楚,例如,四分之一的alpha黄色如何与实心绿色混合:结果是固体还是不透明?我不认为我会很快看到这个问题的答案,所以我暂时在这里回复。

我尝试过这个,但是无法成功弄清楚。我能够使jsfiddle.net/kpQyE/2工作,但是只有在比例为0.50时才有效(其他比例会导致图像不好)。我对算法实际上是如何工作的没有很清楚的理解,所以我怀疑我自己能否单独弄清楚。如果您有额外的时间来考虑这个问题,请随时告诉我。谢谢!

好的,所以我看了一下代码:你离解决方案很近了。两个错误:tX+1的索引比(+3,+4,+5,+6)多了一个(+4,+5,+6,+7),并且在rgba中更改行是乘以4,而不是3。我只测试了4个随机值以进行检查(0.1, 0.15, 0.33, 0.8),看起来没问题。您更新的fiddle在这里:jsfiddle.net/gamealchemist/kpQyE/3

您可以更新已打印的解决方案吗?

好的!实际上,不同的fiddles不仅是改进,而且还是不同版本(是否处理透明度,是否期望目标大小或比例等...),所以我对此应该做些什么感到困惑...

有没有好的书籍或资源可以了解图像缩放和操作的算法?

我从非常多不同的来源进行了研究,所以很难说。我可以推荐《图形宝石》这本我非常喜欢的书...不知道,抱歉:-/

您是否看到了最新的回答?Enric说在某些情况下应该使用floor。这是真的吗?如果是,请更正您的答案。

我没有看到这个帖子,不,谢谢告诉我。我更新了帖子和fiddle:jsfiddle.net/gamealchemist/kpQyE/14

你在打印的解答里有一个错误,Enric说你应该在某些情况下使用floor。如果是这样,请更正你的回答。

我没有看到这个帖子,不,感谢告诉我。我更新了帖子和fiddle:jsfiddle.net/gamealchemist/kpQyE/14

它非常优秀,速度非常快,结果与Photoshop的结果非常接近。为了更容易使用,您可以使用一个自调用函数来包装它,并将downScaleCanvas和downScaleImage函数暴露给window对象。这也可以防止某人执行window.log2 = null;并破坏代码。

这会在某个数学计算过程中产生以下错误:SecurityError: The operation is insecure.

0
0 Comments

HTML5 Canvas Resize (Downscale) Image High Quality 的问题是为了实现高质量的图像缩小。解决方法是使用Hermite滤波器进行图像重采样。

问题的原因是在进行图像缩小时,常规的缩放算法会导致图像失真和质量下降。为了解决这个问题,可以使用Hermite滤波器来实现高质量的图像缩小。Hermite滤波器是一种基于插值的算法,可以在图像缩放时保持细节和色彩的准确性。

下面是一个使用Hermite滤波器进行图像重采样的示例代码:

function resample_single(canvas, width, height, resize_canvas) {
    // 省略代码...
}
// 使用示例
var canvas = document.getElementById("myCanvas");
var width = 500;
var height = 300;
resample_single(canvas, width, height, true);

通过调用`resample_single`函数,可以将指定的canvas元素按照指定的宽度和高度进行高质量的缩小。如果`resize_canvas`参数为`true`,则会同时调整canvas元素的大小。

此外,在评论中还提到了一些关于透明图像和旋转canvas的问题,以及一些相关的讨论和解决方法。

总之,通过使用Hermite滤波器进行图像重采样,可以实现高质量的图像缩小。这对于需要在HTML5 Canvas中处理图像的开发者来说是一个有用的工具。

0
0 Comments

HTML5 Canvas Resize (Downscale) Image High Quality问题的出现原因是在缩小图像的过程中,失去了很多细节和信息,导致结果质量较差。解决方法可以是通过添加一些步骤来改进结果质量,例如使用卷积操作(如锐化)来增加图像的清晰度。另外,还可以考虑使用低级别的算法来实现图像缩小,以获得更高质量的结果。具体而言,可以尝试实现IEEE提出的一种基于插值的自适应降采样算法,该算法能够在获得低分辨率图像的同时,保持尽可能多的原始信息。该算法的实现可能需要较大的工作量,但可以获得比传统方法更好的结果质量。

0