Tuval içeren resim filtreleri

Ilmari Heikkinen

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!

Orijinal test resmi
Orijinal test resmi

Pikselleri işleme

Ö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ığı ayarlamak için piksellere sabit bir değer eklenebilir:

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 resmin eşiğini belirlemek de oldukça basittir. Bir pikselin gri tonlama 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 birç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ığı nasıl merkez piksele 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 ]
);

Dağıtma filtresine örnek olarak kutu bulanıklaştırmayı da verebiliriz. Kutu bulanıklaştırma, toplama matrisi içindeki piksel değerlerinin ortalamasını döndürür. Bunu yapmanın yolu, ağırlıkların her birinin 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. Sobel filtresini burada uygulama şeklimiz, önce resmi gri tonlamaya çevirip ardından yatay ve dikey renk geçişlerini alıp son olarak renk geçişi resimlerini birleştirerek nihai resmi oluşturmaktır.

Terminolojiyle ilgili olarak, buradaki "gradyan", bir resim konumundaki piksel değerindeki değişiklik anlamına gelir. Bir pikselin değeri 20 olan bir sol komşusu ve değeri 50 olan bir sağ komşusu varsa pikselde 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.