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 kanvas tersebut. Anda kemudian dapat menulis hasilnya ke kanvas baru (atau cukup gunakan kembali kanvas lama).

Kedengaran 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 membutuhkan 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 pipeline pemrosesan piksel dikumpulkan, kini saatnya menulis beberapa filter sederhana. Untuk memulai, mari konversi gambar ke 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;
};

Pembatasan gambar juga cukup sederhana. Anda cukup membandingkan nilai hitam putih sebuah piksel dengan nilai batas 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;
};

Gambar yang berubah

Filter konvolusi adalah filter generik yang sangat berguna untuk pemrosesan gambar. Ide dasarnya adalah Anda mengambil jumlah berat persegi panjang piksel dari gambar sumber dan menggunakannya sebagai nilai output. Filter konvolusi dapat digunakan untuk memburamkan, mempertajam, emboss, 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 3x3 yang dipertajam. 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 ini contoh lain dari filter konvolusi, yaitu {i>box blur<i}. Blur kotak menghasilkan rata-rata nilai piksel di dalam matriks konvolusi. Caranya adalah dengan membuat matriks konvolusi dengan ukuran NxN, dengan bobot masing-masing 1 / (NxN). Dengan begitu, setiap piksel di dalam matriks berkontribusi dengan jumlah yang sama terhadap 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 serta menggabungkan gambar yang dihitung untuk menemukan tepi dalam gambar. Cara kita menerapkan filter Sobel di sini adalah dengan terlebih dahulu melakukan penskalaan abu-abu pada gambar, lalu mengambil gradien horizontal dan vertikal, dan terakhir menggabungkan gambar gradien untuk membuat gambar akhir.

Mengenai 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 pikselnya adalah 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&lt;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
}

Ada banyak filter konvolusi keren lainnya di luar sana yang menunggu Anda menemukannya. Misalnya, coba terapkan filter Laplace pada mainan konvolusi di atas dan lihat fungsinya.

Kesimpulan

Semoga artikel singkat ini bermanfaat untuk memperkenalkan konsep dasar penulisan filter gambar di JavaScript menggunakan tag kanvas HTML. Saya menyarankan Anda untuk menerapkan beberapa filter gambar lagi, ini sangat 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 filter paling sederhana secara real time, sehingga Anda dapat menggunakannya untuk video dan animasi pascapemrosesan.