Filter gambar dengan kanvas

Ilmari Heikkinen

Pengantar

Elemen kanvas HTML5 dapat digunakan untuk menulis filter gambar. Yang perlu Anda lakukan adalah menggambar gambar ke kanvas, membaca kembali piksel kanvas, dan menjalankan filter pada piksel tersebut. Kemudian, Anda dapat menulis hasilnya ke kanvas baru (atau, cukup gunakan kembali kanvas lama).

Kedengarannya sederhana? Bagus. Ayo kita mulai!

Gambar pengujian asli
Gambar pengujian asli

Memproses piksel

Pertama, ambil piksel gambar:

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

Selanjutnya, kita memerlukan cara untuk memfilter gambar. Bagaimana dengan metode filterImage yang menggunakan filter dan gambar, serta menampilkan piksel yang difilter?

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

Menjalankan filter sederhana

Setelah kita menyusun pipeline pemrosesan piksel, saatnya menulis beberapa filter sederhana. Untuk memulai, mari kita konversikan gambar menjadi hitam putih.

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

Menyesuaikan kecerahan dapat dilakukan dengan menambahkan nilai tetap ke piksel:

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

Menetapkan nilai minimum gambar juga cukup sederhana. Anda hanya membandingkan nilai grayscale piksel dengan nilai minimum dan menetapkan warna berdasarkan hal tersebut:

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

Mengubah gambar

Filter konvolusi adalah filter generik yang sangat berguna untuk pemrosesan gambar. Ide dasarnya adalah Anda mengambil jumlah bobot piksel persegi panjang dari gambar sumber dan menggunakannya sebagai nilai output. Filter konvolusi dapat digunakan untuk memburamkan, mempertajam, timbul embos, deteksi tepi, dan banyak hal lainnya.

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

Berikut adalah filter pertajam 3x3. Lihat caranya memfokuskan bobot di piksel tengah. Untuk mempertahankan kecerahan gambar, jumlah nilai matriks harus satu.

Filters.filterImage(Filters.convolute, image,
[  0, -1,  0,
-1,  5, -1,
  0, -1,  0 ]
);

Berikut adalah contoh lain filter konvolusi, yaitu blur kotak. Kotak blur ini menghasilkan rata-rata nilai piksel di dalam matriks konvolusi. Cara melakukannya adalah dengan membuat matriks konvolusi berukuran NxN dengan setiap bobotnya adalah 1 / (NxN). Dengan begitu, setiap piksel di dalam matriks berkontribusi dalam jumlah yang sama ke gambar output dan jumlah bobotnya adalah satu.

Filters.filterImage(Filters.convolute, image,
[ 1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9 ]
);

Kita dapat membuat filter gambar yang lebih kompleks dengan menggabungkan filter yang ada. Misalnya, mari kita tulis filter Sobel. Filter Sobel menghitung gradien vertikal dan horizontal gambar dan menggabungkan gambar yang dikomputasi untuk menemukan tepi pada gambar. Cara menerapkan filter Sobel di sini adalah dengan melakukan penskalaan abu-abu pada gambar terlebih dahulu, lalu mengambil gradien horizontal dan vertikal, dan terakhir menggabungkan gambar gradien untuk membentuk gambar akhir.

Terkait terminologi, "gradien" di sini berarti perubahan nilai piksel pada posisi gambar. Jika piksel memiliki tetangga kiri dengan nilai 20 dan tetangga kanan dengan nilai 50, gradien horizontal pada piksel akan menjadi 30. Gradien vertikal memiliki ide yang sama, tetapi menggunakan tetangga di atas dan di bawah.

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
}

Selain itu, ada banyak filter konvolusi keren lainnya yang menunggu Anda menemukannya. Misalnya, coba terapkan filter Laplace di mainan konvolusi di atas dan lihat fungsinya.

Kesimpulan

Semoga artikel singkat ini bermanfaat dalam memperkenalkan konsep dasar menulis filter gambar di JavaScript menggunakan tag kanvas HTML. Saya mendorong Anda untuk menggunakan beberapa filter gambar lainnya, ini cukup menyenangkan!

Jika memerlukan performa yang lebih baik dari filter, Anda biasanya dapat memindahkannya untuk menggunakan shader fragmen WebGL guna melakukan pemrosesan gambar. Dengan shader, Anda dapat menjalankan sebagian besar filter sederhana secara real time, yang memungkinkan Anda menggunakannya untuk pasca-pemrosesan video dan animasi.