我正在尝试使用javascript和canvas元素在客户端创建缩略图图像,但是当我缩小图像时,它看起来很糟糕.它看起来好像在photoshop中缩小了,重新采样设置为"最近邻"而不是Bicubic.我知道可以让它看起来正确,因为这个网站也可以使用画布做得很好.我尝试使用他们所做的相同代码,如"[Source]"链接所示,但它仍然看起来很糟糕.有什么我缺少的东西,一些需要设置的设置或什么?
编辑:
我正在尝试调整jpg的大小.我已经尝试在链接网站和photoshop中调整相同的jpg,并且在缩小尺寸时看起来很好.
这是相关代码:
reader.onloadend = function(e) { var img = new Image(); var ctx = canvas.getContext("2d"); var canvasCopy = document.createElement("canvas"); var copyContext = canvasCopy.getContext("2d"); img.onload = function() { var ratio = 1; if(img.width > maxWidth) ratio = maxWidth / img.width; else if(img.height > maxHeight) ratio = maxHeight / img.height; canvasCopy.width = img.width; canvasCopy.height = img.height; copyContext.drawImage(img, 0, 0); canvas.width = img.width * ratio; canvas.height = img.height * ratio; ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height); }; img.src = reader.result; }
EDIT2:
似乎我错了,链接的网站没有做任何缩小图像的工作.我尝试了其他建议的方法,但没有一个看起来更好.这就是不同方法产生的结果:
Photoshop中:
帆布:
具有图像渲染的图像:optimizeQuality设置并使用宽度/高度缩放:
具有图像渲染的图像:optimizeQuality设置并使用-moz-transform缩放:
画布在pixastic上调整大小:
我想这意味着firefox没有像我们想象的那样使用双三次采样.我只需要等到实际添加它.
EDIT3:
原始图像
那么,如果所有的浏览器(实际上,Chrome 5给了我很好的浏览器),你会怎么做?不会给你足够好的重新取样质量?你自己实现它们!哦,来吧,我们正在进入Web 3.0的新时代,HTML5兼容的浏览器,超级优化的JIT javascript编译器,多核(†)机器,有大量的内存,你害怕什么?嘿,javascript中有java这个词,所以应该保证性能,对吧?看,缩略图生成代码:
// returns a function that calculates lanczos weight function lanczosCreate(lobes) { return function(x) { if (x > lobes) return 0; x *= Math.PI; if (Math.abs(x) < 1e-16) return 1; var xx = x / lobes; return Math.sin(x) * Math.sin(xx) / x / xx; }; } // elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius function thumbnailer(elem, img, sx, lobes) { this.canvas = elem; elem.width = img.width; elem.height = img.height; elem.style.display = "none"; this.ctx = elem.getContext("2d"); this.ctx.drawImage(img, 0, 0); this.img = img; this.src = this.ctx.getImageData(0, 0, img.width, img.height); this.dest = { width : sx, height : Math.round(img.height * sx / img.width), }; this.dest.data = new Array(this.dest.width * this.dest.height * 3); this.lanczos = lanczosCreate(lobes); this.ratio = img.width / sx; this.rcp_ratio = 2 / this.ratio; this.range2 = Math.ceil(this.ratio * lobes / 2); this.cacheLanc = {}; this.center = {}; this.icenter = {}; setTimeout(this.process1, 0, this, 0); } thumbnailer.prototype.process1 = function(self, u) { self.center.x = (u + 0.5) * self.ratio; self.icenter.x = Math.floor(self.center.x); for (var v = 0; v < self.dest.height; v++) { self.center.y = (v + 0.5) * self.ratio; self.icenter.y = Math.floor(self.center.y); var a, r, g, b; a = r = g = b = 0; for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) { if (i < 0 || i >= self.src.width) continue; var f_x = Math.floor(1000 * Math.abs(i - self.center.x)); if (!self.cacheLanc[f_x]) self.cacheLanc[f_x] = {}; for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) { if (j < 0 || j >= self.src.height) continue; var f_y = Math.floor(1000 * Math.abs(j - self.center.y)); if (self.cacheLanc[f_x][f_y] == undefined) self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000); weight = self.cacheLanc[f_x][f_y]; if (weight > 0) { var idx = (j * self.src.width + i) * 4; a += weight; r += weight * self.src.data[idx]; g += weight * self.src.data[idx + 1]; b += weight * self.src.data[idx + 2]; } } } var idx = (v * self.dest.width + u) * 3; self.dest.data[idx] = r / a; self.dest.data[idx + 1] = g / a; self.dest.data[idx + 2] = b / a; } if (++u < self.dest.width) setTimeout(self.process1, 0, self, u); else setTimeout(self.process2, 0, self); }; thumbnailer.prototype.process2 = function(self) { self.canvas.width = self.dest.width; self.canvas.height = self.dest.height; self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height); self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height); var idx, idx2; for (var i = 0; i < self.dest.width; i++) { for (var j = 0; j < self.dest.height; j++) { idx = (j * self.dest.width + i) * 3; idx2 = (j * self.dest.width + i) * 4; self.src.data[idx2] = self.dest.data[idx]; self.src.data[idx2 + 1] = self.dest.data[idx + 1]; self.src.data[idx2 + 2] = self.dest.data[idx + 2]; } } self.ctx.putImageData(self.src, 0, 0); self.canvas.style.display = "block"; };
...你可以用它来产生这样的结果!
所以无论如何,这是你的例子的"固定"版本:
img.onload = function() { var canvas = document.createElement("canvas"); new thumbnailer(canvas, img, 188, 3); //this produces lanczos3 // but feel free to raise it up to 8. Your client will appreciate // that the program makes full use of his machine. document.body.appendChild(canvas); };
现在是时候把你最好的浏览器放在那里,看看哪一个最不可能增加你客户的血压!
嗯,哪里是我的讽刺标签?
(因为代码的很多部分都基于Anrieff Gallery Generator,它也包含在GPL2中吗?我不知道)
† 实际上由于javascript的限制,不支持多核.
使用带有JavaScript的Hermite过滤器进行快速图像调整大小/重新采样算法.支持透明度,提供优质.预习:
更新:GitHub上添加的版本2.0(更快,Web工作者+可转移对象).最后我搞定了!
Git:https://github.com/viliusle/Hermite-resize
演示:http://viliusle.github.io/miniPaint/
/** * 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); }
尝试异食癖 - 这是一个高度优化的缩放器,具有可选择的算法.见演示.
例如,第一篇文章的原始图像使用Lanczos过滤器和3px窗口在120ms中调整大小,或者使用Box过滤器和0.5px窗口调整为60ms.对于巨大的17mb图像5000x3000px调整大小在桌面上需要大约1秒,在手机上需要3秒.
所有调整大小的原则都在这个主题中得到了很好的描述,而异食癖并没有增加火箭科学.但它对现代JIT-s的优化非常好,并且可以开箱即用(通过npm或bower).此外,它在可用时使用webworkers来避免界面冻结.
我还计划很快添加非锐化掩模支持,因为它在缩减后非常有用.
我知道这是一个旧线程,但对于像我这样的人来说,这可能是有用的,几个月后第一次遇到这个问题.
以下是一些代码,每次重新加载图像时都会调整图像大小.我知道这根本不是最优的,但我提供它作为概念证明.
另外,很抱歉使用jQuery作为简单的选择器,但我对语法感觉太舒服了.
$(document).on('ready', createImage);
$(window).on('resize', createImage);
var createImage = function(){
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth || $(window).width();
canvas.height = window.innerHeight || $(window).height();
var ctx = canvas.getContext('2d');
img = new Image();
img.addEventListener('load', function () {
ctx.drawImage(this, 0, 0, w, h);
});
img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #000;
}
canvas{
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
Canvas Resize
我已经提出了一些算法来对html画布像素数组进行图像插值,这可能在这里很有用:
https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2
这些可以被复制/粘贴,并且可以在网络工作者内部使用来调整图像大小(或任何其他需要插值的操作 - 我现在用它们来消除图像).
我没有添加上面的lanczos内容,所以如果您愿意,可以随意添加它作为比较.
如果你只是想调整图片大小,我建议设置width
和height
使用CSS的形象.这是一个简单的例子:
.small-image { width: 100px; height: 100px; }
请注意,height
并width
还可以使用JavaScript来设置.这是快速代码示例:
var img = document.getElement("my-image"); img.style.width = 100 + "px"; // Make sure you add the "px" to the end, img.style.height = 100 + "px"; // otherwise you'll confuse IE
此外,要确保调整大小的图像看起来不错,请将以下css规则添加到图像选择器:
-ms-interpolation-mode: bicubic
:在IE7中介绍
image-rendering: optimizeQuality
:在FireFox 3.6中引入
据我所知,除IE之外的所有浏览器都使用双三次算法默认调整图像大小,因此调整后的图像在Firefox和Chrome中应该看起来不错.
如果设置CSS width
并且height
不起作用,您可能想要使用css transform
:
-moz-transform: scale(sx[, sy])
-webkit-transform:scale(sx[, sy])
如果出于某种原因需要使用画布,请注意有两种方法可以调整图像大小:通过使用css调整画布大小或通过以较小的尺寸绘制图像.
有关详细信息,请参阅此问题.
希望这可以帮助!
我强烈建议您查看此链接并确保将其设置为true.
控制图像缩放行为
在Gecko 1.9.2中引入(Firefox 3.6/Thunderbird 3.1/Fennec 1.0)
Gecko 1.9.2将mozImageSmoothingEnabled属性引入canvas元素; 如果此布尔值为false,则缩放时图像将不会平滑.默认情况下,此属性为true.查看plainprint?
cx.mozImageSmoothingEnabled = false;
这是一个改编自@ Telanor代码的javascript函数.将图像base64作为第一个参数传递给函数时,它返回调整大小后的图像的base64.maxWidth和maxHeight是可选的.
function thumbnail(base64, maxWidth, maxHeight) { // Max size for thumbnail if(typeof(maxWidth) === 'undefined') var maxWidth = 500; if(typeof(maxHeight) === 'undefined') var maxHeight = 500; // Create and initialize two canvas var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); var canvasCopy = document.createElement("canvas"); var copyContext = canvasCopy.getContext("2d"); // Create original image var img = new Image(); img.src = base64; // Determine new ratio based on max size var ratio = 1; if(img.width > maxWidth) ratio = maxWidth / img.width; else if(img.height > maxHeight) ratio = maxHeight / img.height; // Draw original image in second canvas canvasCopy.width = img.width; canvasCopy.height = img.height; copyContext.drawImage(img, 0, 0); // Copy and resize second canvas to first canvas canvas.width = img.width * ratio; canvas.height = img.height * ratio; ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height); return canvas.toDataURL(); }