Tarayıcımdaki görüntüleri yeniden boyutlandırmak için html5 canvas öğelerini kullanıyorum. Kalitenin çok düşük olduğu ortaya çıkıyor. Bunu buldum: https://stackoverflow.com/questions/7615009/disable-interpolation-when-scaling-a-canvas ancak kaliteyi artırmaya yardımcı olmuyor.
Aşağıda css ve js kodlarımın yanı sıra Photoshop ile taranmış ve canvas API'de ölçeklendirilmiş görüntü var.
**Tarayıcıda bir görüntüyü ölçeklendirirken optimum kaliteyi elde etmek için ne yapmam gerekir?
Not: Büyük bir görüntüyü küçük bir görüntüye ölçeklendirmek, bir tuvalde rengi değiştirmek ve sonucu tuvalden sunucuya göndermek istiyorum.
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);
});
Görüntü photoshop ile yeniden boyutlandırıldı:
Görüntü tuval üzerinde yeniden boyutlandırıldı:
Düzenle:
Aşağı ölçeklendirmeyi önerildiği gibi birden fazla adımda yapmaya çalıştım:
https://stackoverflow.com/questions/2303690/resizing-an-image-in-an-html5-canvas ve https://stackoverflow.com/questions/17861447/html5-canvas-drawimage-how-to-apply-antialiasing
Bu benim kullandığım işlevdir:
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);
}
İşte 2 adım aşağı boyutlandırma kullanırsam sonuç:
İşte 3 adımlı bir boyutlandırma kullanırsam sonuç:
İşte 4 kademeli bir boyutlandırma kullanırsam sonuç:
İşte 20 kademeli bir boyutlandırma kullanırsam sonuç:
Not: 1 adımdan 2 adıma kadar görüntü kalitesinde büyük bir iyileşme olduğu ortaya çıkıyor, ancak işleme ne kadar çok adım eklerseniz görüntü o kadar bulanık hale geliyor.
**Eklediğiniz adım sayısı arttıkça görüntünün daha da bulanıklaşması sorununu çözmenin bir yolu var mı?
Düzenleme 2013-10-04: GameAlchemist'in algoritmasını denedim. İşte Photoshop ile karşılaştırıldığında sonuç.
PhotoShop Görüntüsü:
GameAlchemist'in Algoritması:
Sorununuz görüntünüzü küçültmek olduğu için, piksel oluşturmakla ilgili olan enterpolasyondan bahsetmenin bir anlamı yoktur. Buradaki sorun aşağı örneklemedir.
Bir görüntüyü aşağı örneklemek için, orijinal görüntüdeki p * p piksellerinin her bir karesini hedef görüntüde tek bir piksele dönüştürmemiz gerekir.
Performans nedeniyle Tarayıcılar çok basit bir alt örnekleme yapar: daha küçük bir görüntü oluşturmak için kaynakta sadece BİR piksel seçer ve hedef için onun değerini kullanır. bu da bazı ayrıntıları unutur ve gürültü ekler.
Yine de bunun bir istisnası vardır: 2X görüntü alt örneklemenin hesaplanması çok basit olduğundan (bir tane yapmak için ortalama 4 piksel) ve retina/HiDPI pikselleri için kullanıldığından, bu durum düzgün bir şekilde ele alınır - Tarayıcı bir tane yapmak için 4 piksel kullanır -.
ANCAK... birkaç kez 2X alt örnekleme kullanırsanız, ardışık yuvarlama hatalarının çok fazla gürültü eklemesi sorunuyla karşılaşırsınız.
Daha da kötüsü, her zaman ikinin kuvveti kadar yeniden boyutlandıramazsınız ve en yakın kuvvete göre yeniden boyutlandırma + son boyutlandırma çok gürültülüdür.
Aradığınız şey piksel açısından mükemmel bir alt örneklemedir, yani: ölçek ne olursa olsun tüm girdi piksellerini hesaba katacak şekilde görüntünün yeniden örneklenmesidir.
Bunu yapmak için, her bir girdi pikseli için, girdi piksellerinin ölçeklendirilmiş izdüşümünün bir hedef pikselin tam içinde olmasına, bir X sınırıyla, bir Y sınırıyla veya her ikisiyle çakışmasına bağlı olarak bir, iki veya dört hedef piksele katkısını hesaplamalıyız.
(Burada bir şema iyi olurdu, ama şemam yok.)
İşte 1/3 ölçekli bir zombat üzerindeki piksel mükemmel ölçeğime karşı tuval ölçeğinin bir örneği.
Resmin Tarayıcınızda ölçeklendirilebileceğine ve S.O. tarafından .jpeg'leştirildiğine dikkat edin.
Yine de özellikle vombatın arkasındaki çimenlerde ve sağındaki dallarda çok daha az gürültü olduğunu görüyoruz. Kürkündeki gürültü onu daha kontrastlı hale getiriyor, ancak kaynak resimden farklı olarak beyaz tüyleri var gibi görünüyor.
Sağdaki resim daha az akılda kalıcı ama kesinlikle daha güzel.
İşte piksel mükemmel ölçek küçültme yapmak için kod:
keman sonucu:
kemanın kendisi : 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;
}
Hedef görüntünün ara değerlerini depolamak için bir float tampon gerektiğinden (-> sonuç tuvalini sayarsak, bu algoritmada kaynak görüntünün 6 katı bellek kullanırız), oldukça bellek açgözlüdür.
Aynı zamanda oldukça pahalıdır, çünkü her kaynak piksel hedef boyutu ne olursa olsun kullanılır ve getImageData / putImageDate için ödeme yapmamız gerekir, ayrıca oldukça yavaştır.
Ancak bu durumda her bir kaynak değerini işlemekten daha hızlı olmanın bir yolu yok ve durum o kadar da kötü değil: Bir vombatın 740 * 556 görüntüsü için işleme 30 ila 40 ms arasında sürüyor.
İyi kalitede hızlı kanvas yeniden örnekleme:
Güncelleme: sürüm 2.0 (daha hızlı, web çalışanları + aktarılabilir nesneler) - https://github.com/viliusle/Hermite-resize
/**
* 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);
}
Görüntüleri yeniden boyutlandırmak için neden tuval kullanılmalı? Modern tarayıcıların tümü bikübik enterpolasyon kullanır - Photoshop tarafından kullanılan aynı işlem (eğer doğru yapıyorsanız) - ve bunu tuval işleminden daha hızlı yaparlar. Sadece istediğiniz görüntü boyutunu belirtin (orantılı olarak yeniden boyutlandırmak için yalnızca bir boyut, yükseklik veya genişlik kullanın).
Bu, IE'nin sonraki sürümleri de dahil olmak üzere çoğu tarayıcı tarafından desteklenmektedir. Daha önceki sürümler tarayıcıya özel CSS gerektirebilir.
Bir resmi yeniden boyutlandırmak için basit bir fonksiyon (jQuery kullanarak) şu şekilde olabilir:
function resizeImage(img, percentage) {
var coeff = percentage/100,
width = $(img).width(),
height = $(img).height();
return {"width": width*coeff, "height": height*coeff}
}
Ardından, görüntüyü bir veya her iki boyutta yeniden boyutlandırmak için döndürülen değeri kullanın.
Açıkçası yapabileceğiniz farklı iyileştirmeler var, ancak bu işi hallediyor.
Aşağıdaki kodu bu sayfanın konsoluna yapıştırın ve gravatarlara ne olduğunu izleyin:
function resizeImage(img, percentage) {
var coeff = percentage/100,
width = $(img).width(),
height = $(img).height();
return {"width": width*coeff, "height": height*coeff}
}
$('.user-gravatar32 img').each(function(){
var newDimensions = resizeImage( this, 150);
this.style.width = newDimensions.width + "px";
this.style.height = newDimensions.height + "px";
});