Giriş
HTML5 kanvas öğesi, resim filtreleri yazmak için kullanılabilir. Bunun için bir kanvas üzerine resim çizmeniz, kanvas piksellerini geri okumanız ve filtrenizi bunlar üzerinde çalıştırmanız gerekir. Ardından sonucu yeni bir kanvas üzerine yazabilirsiniz (veya eskisini yeniden kullanabilirsiniz).
Kulağa basit geliyor mu? Güzel. Hadi başlayalım!
Pikseller işleniyor
Öncelikle resim piksellerini alın:
Filters = {};
Filters.getPixels = function(img) {
var c = this.getCanvas(img.width, img.height);
var ctx = c.getContext('2d');
ctx.drawImage(img);
return ctx.getImageData(0,0,c.width,c.height);
};
Filters.getCanvas = function(w,h) {
var c = document.createElement('canvas');
c.width = w;
c.height = h;
return c;
};
Ardından, resimleri filtreleyebileceğimiz bir yönteme ihtiyacımız var. Filtre ve resim alan ve filtrelenmiş pikselleri döndüren bir filterImage
yöntem ne dersiniz?
Filters.filterImage = function(filter, image, var_args) {
var args = [this.getPixels(image)];
for (var i=2; i<arguments.length; i++) {
args.push(arguments[i]);
}
return filter.apply(null, args);
};
Basit filtreler çalıştırma
Pixel işleme ardışık düzenini oluşturduğumuza göre, bazı basit filtreler yazma zamanı geldi. Öncelikle resmi gri tonlamaya dönüştürelim.
Filters.grayscale = function(pixels, args) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
// CIE luminance for the RGB
// The human eye is bad at seeing red and blue, so we de-emphasize them.
var v = 0.2126*r + 0.7152*g + 0.0722*b;
d[i] = d[i+1] = d[i+2] = v
}
return pixels;
};
Parlaklık, piksellere sabit bir değer eklenerek ayarlanabilir:
Filters.brightness = function(pixels, adjustment) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
d[i] += adjustment;
d[i+1] += adjustment;
d[i+2] += adjustment;
}
return pixels;
};
Bir resim eşiği uygulamak da oldukça basit bir işlemdir. Bir pikselin gri tonlamalı değerini eşik değerle karşılaştırmanız ve rengi buna göre ayarlamanız yeterlidir:
Filters.threshold = function(pixels, threshold) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
var v = (0.2126*r + 0.7152*g + 0.0722*b >= threshold) ? 255 : 0;
d[i] = d[i+1] = d[i+2] = v
}
return pixels;
};
Resimleri dönüştürme
Dönüşüm filtreleri, resim işleme için çok kullanışlı genel filtrelerdir. Temel fikir, kaynak görüntüdeki bir piksel dikdörtgeninin ağırlıklı toplamını alıp bunu çıkış değeri olarak kullanmanızdır. Toplama filtreleri bulanıklık, keskinleştirme, kabartma, kenar algılama ve daha pek çok işlem için kullanılabilir.
Filters.tmpCanvas = document.createElement('canvas');
Filters.tmpCtx = Filters.tmpCanvas.getContext('2d');
Filters.createImageData = function(w,h) {
return this.tmpCtx.createImageData(w,h);
};
Filters.convolute = function(pixels, weights, opaque) {
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side/2);
var src = pixels.data;
var sw = pixels.width;
var sh = pixels.height;
// pad output by the convolution matrix
var w = sw;
var h = sh;
var output = Filters.createImageData(w, h);
var dst = output.data;
// go through the destination image pixels
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = weights[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3] * wt;
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = a + alphaFac*(255-a);
}
}
return output;
};
3x3 keskinleştirme filtresi aşağıda gösterilmiştir. Ağırlığı orta pikselde nasıl odakladığını görün. Görüntünün parlaklığını korumak için matris değerlerinin toplamı bir olmalıdır.
Filters.filterImage(Filters.convolute, image,
[ 0, -1, 0,
-1, 5, -1,
0, -1, 0 ]
);
Konvolüsyon filtresine bir başka örnek de kutu bulanıklığıdır. Kutu bulanıklaştırma, toplama matrisi içindeki piksel değerlerinin ortalamasını döndürür. Bunu yapmanın yolu, her ağırlığın 1 / (NxN) olduğu NxN boyutunda bir toplama matrisi oluşturmaktır. Bu sayede matris içindeki her piksel çıkış görüntüsüne eşit miktarda katkıda bulunur ve ağırlıkların toplamı bir olur.
Filters.filterImage(Filters.convolute, image,
[ 1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9 ]
);
Mevcut filtreleri birleştirerek daha karmaşık resim filtreleri oluşturabiliriz. Örneğin, bir Sobel filtresi yazalım. Sobel filtresi, resmin dikey ve yatay gradyanlarını hesaplar ve resimdeki kenarları bulmak için hesaplanan resimleri birleştirir. Burada Sobel filtresini uygulama yöntemimiz, önce resmi grileştirmeyi, ardından yatay ve dikey renk geçişlerini alıp son resmi oluşturmak için gradyan resimleri birleştirmektir.
Terminolojiyle ilgili olarak, buradaki "gradyan", bir resim konumundaki piksel değerindeki değişiklik anlamına gelir. Bir pikselin sol komşusu 20 değerinde ve sağ komşusu 50 değerindeyse pikseldeki yatay gradyan 30 olur. Dikey renk geçişinde de aynı fikir geçerlidir ancak yukarıdaki ve aşağıdaki komşular kullanılır.
var grayscale = Filters.filterImage(Filter.grayscale, image);
// Note that ImageData values are clamped between 0 and 255, so we need
// to use a Float32Array for the gradient values because they
// range between -255 and 255.
var vertical = Filters.convoluteFloat32(grayscale,
[ -1, 0, 1,
-2, 0, 2,
-1, 0, 1 ]);
var horizontal = Filters.convoluteFloat32(grayscale,
[ -1, -2, -1,
0, 0, 0,
1, 2, 1 ]);
var final_image = Filters.createImageData(vertical.width, vertical.height);
for (var i=0; i<final_image.data.length; i+=4) {
// make the vertical gradient red
var v = Math.abs(vertical.data[i]);
final_image.data[i] = v;
// make the horizontal gradient green
var h = Math.abs(horizontal.data[i]);
final_image.data[i+1] = h;
// and mix in some blue for aesthetics
final_image.data[i+2] = (v+h)/4;
final_image.data[i+3] = 255; // opaque alpha
}
Ayrıca keşfetmenizi bekleyen bir sürü harika ardışık filtre var. Örneğin, yukarıdaki örtüşme oyuncağında bir Laplace filtresi uygulamayı deneyin ve ne yaptığını görün.
Sonuç
Bu küçük makalenin, HTML kanvas etiketini kullanarak JavaScript'te resim filtreleri yazmayla ilgili temel kavramları tanıtmada faydalı olduğunu umuyoruz. Daha fazla resim filtresi uygulamanızı öneririm. Bu oldukça eğlenceli bir deneyimdir.
Filtrelerinizden daha iyi performans elde etmek istiyorsanız genellikle bunları, resim işleme işlemini yapmak için WebGL parçacık gölgelendiricileri kullanacak şekilde taşıyabilirsiniz. Gölgelendiriciler sayesinde basit filtrelerin çoğunu gerçek zamanlı olarak çalıştırabilirsiniz. Bu sayede, video ve animasyonlarda son işlem için bu filtreleri kullanabilirsiniz.