Tuval içeren resim filtreleri

Ilmari Heikkinen

Giriş

HTML5 tuval öğesi, resim filtreleri yazmak için kullanılabilir. Yapmanız gereken tuvale bir resim çizmek, tuval piksellerini tekrar okumak ve filtrenizi üzerlerinde çalıştırmaktır. Ardından sonucu yeni bir tuvale yazabilirsiniz (veya eskisini yeniden kullanabilirsiniz).

Basit görünüyor mu? Güzel. Hadi başlayalım!

Orijinal test resmi
Orijinal test resmi

Pikseller işleniyor

İlk olarak 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;
};

Şimdi de resimleri filtrelemek için bir yönteme ihtiyacımız var. Bir filtre ile resmi alıp filtrelenmiş pikselleri döndüren bir filterImage yöntemine 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

Artık piksel işleme ardışık düzenini bir araya getirdiğimize göre bazı basit filtreler yazmanın zamanı geldi. Başlamak için 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;
};

Piksellere sabit bir değer eklenerek parlaklık 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 resme eşik eklemek de oldukça basittir. Bir pikselin gri tonlama değerini eşik değeriyle karşılaştırır ve rengi buna göre ayarlayabilirsiniz:

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;
};

Dönen resimler

Dönş. filtreleri, resim işleme için oldukça kullanışlı genel filtrelerdir. Temel fikir, kaynak resimdeki piksellerin ağırlıklarının toplamını almak ve bunu çıktı değeri olarak kullanmaktır. Bükülme filtreleri; bulanıklaştırma, 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;
};

Aşağıda bir 3x3 keskinleştirme filtresi verilmiştir. Ağırlığı merkez piksele nasıl odakladığını inceleyin. Resmin 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 başka bir örnek daha verelim: Kutu bulanıklığı. Kutu bulanıklaştırma işlevi, evrişim matrisi içindeki piksel değerlerinin ortalamasını verir. Bunu yapmanın yolu, NxN boyutunda bir konvolüsyon matrisi oluşturmaktır. Burada, ağırlıkların her biri 1 / (NxN) olur. Bu şekilde, matrisin içindeki piksellerin her biri, çıktı resmine 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 hesaplanan resimleri birleştirerek resmin kenarlarını bulur. Burada Sobel filtresini uygulama şeklimiz, önce resmin granitlenmesini, ardından yatay ve dikey renk geçişlerini almak ve son olarak gradyan resimlerini birleştirerek nihai resmi oluşturmaktır.

Terminolojiye göre, buradaki "gradyan" bir resim konumundaki piksel değerindeki değişikliği ifade eder. Bir pikselin değeri 20 olan sol komşusu ve 50 değerinde sağ komşusu varsa pikseldeki yatay gradyan 30 olur. Dikey renk geçişi aynı fikre sahiptir, ancak yukarıdaki ve altındaki komşuları kullanı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ü başka havalı evrişim filtresi var. Örneğin, yukarıdaki kıvrım oyuncağına bir Döşeme filtresi uygulamayı deneyin ve işlevine bakın.

Sonuç

Bu küçük makalenin HTML tuval etiketi kullanarak JavaScript'te resim filtreleri yazmaya ilişkin temel kavramların tanıtılmasında yararlı olduğunu umuyorum. Gidip daha fazla resim filtresi uygulamanızı öneririz. Oldukça eğlenceli!

Filtrelerinizden daha iyi performans elde etmeniz gerekirse genellikle resim işlemeyi gerçekleştirmek için bunları WebGL parça gölgelendiricileri kullanacak şekilde taşıyabilirsiniz. Gölgelendiriciler sayesinde, en basit filtreleri gerçek zamanlı olarak çalıştırabilirsiniz. Bu da, bunları video ve animasyonların işlenmesinde kullanmak için kullanabilirsiniz.