فلاتر الصور باستخدام اللوحة

إلماري هاكينين

مقدمة

يمكن استخدام عنصر لوحة HTML5 لكتابة فلاتر الصور. ما عليك فعله هو رسم صورة على لوحة رسم، وقراءة بكسلات لوحة الرسم مرة أخرى، وتشغيل عامل التصفية عليها. يمكنك بعد ذلك كتابة النتيجة على لوحة رسومات جديدة (أو فقط إعادة استخدام اللوحة القديمة).

هل تبدو بسيطة؟ جيد. لنبدأ العمل

الصورة الأصلية للاختبار
الصورة الأصلية للاختبار

وحدات بكسل المعالجة

أولاً، استرِد وحدات بكسل الصورة:

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

بعد ذلك، نحتاج إلى طريقة لتصفية الصور. ماذا عن الطريقة filterImage التي تستخدم فلترًا وصورة وتعرض وحدات البكسل المفلترة؟

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

تشغيل فلاتر بسيطة

الآن بعد أن تم تجميع مسار معالجة وحدات البكسل معًا، حان الوقت لكتابة بعض عوامل التصفية البسيطة. للبدء، لنحول الصورة إلى التدرج الرمادي.

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

يمكن ضبط السطوع بإضافة قيمة ثابتة إلى وحدات البكسل:

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

يعد أيضًا حدود الصورة أمرًا بسيطًا للغاية. يمكنك فقط مقارنة قيمة التدرج الرمادي للبكسل بقيمة العتبة وتعيين اللون بناءً على ذلك:

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

صور مترابطة

فلاتر الالتفاف هي فلاتر عامة مفيدة جدًا لمعالجة الصور. الفكرة الأساسية هي أنك تأخذ المجموع المرجح لمستطيلات البكسل من صورة المصدر وتستخدمه كقيمة للمخرجات. يمكن استخدام فلاتر الالتفاف للتعتيم وزيادة الحدة والزخرفة واكتشاف الحواف ومجموعة كاملة من الأشياء الأخرى.

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

إليك فلتر زيادة الحدة 3×3. تعرّف على كيفية تركيز الوزن على البكسل الأوسط. للحفاظ على سطوع الصورة، ينبغي أن يكون مجموع قيم المصفوفة واحدًا.

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

في ما يلي مثال آخر على فلتر الالتفاف، وهو تمويه المربع. ينتج عن تمويه المربع متوسط قيم البكسل داخل مصفوفة الالتفاف. الطريقة للقيام بذلك هي إنشاء مصفوفة التفاف بحجم NxN حيث يكون كل قيمة من الأوزان 1 / (NxN). بهذه الطريقة يساهم كل بكسل من وحدات البكسل داخل المصفوفة في مقدار مساوٍ لصورة الإخراج ويكون مجموع الوزن واحدًا.

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

يمكننا إنشاء فلاتر صور أكثر تعقيدًا من خلال الجمع بين الفلاتر الحالية. على سبيل المثال، لنكتب فلتر Sobel. يحتسب فلتر Sobel التدرجات العمودية والأفقية للصورة ويدمج الصور المحسوبة للعثور على حواف الصورة. تتمثل الطريقة التي ننفذ بها فلتر Sobel هنا في تكبير الصورة أولاً، ثم أخذ التدرجات الأفقية والرأسية وأخيرًا دمج صور التدرج لتكوين الصورة النهائية.

بخصوص المصطلحات، يعني "التدرج" هنا التغيير في قيمة البكسل في موضع صورة. إذا كان للبكسل جار أيسر بقيمة 20 وجارٍ أيمن بقيمة 50، فسيكون التدرج الأفقي عند البكسل 30. للتدرج الرأسي نفس الفكرة ولكنه يستخدم الجارين أعلاه وأسفل.

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
}

هناك مجموعة كاملة من فلاتر الالتفاف الرائعة الأخرى في انتظارك لاكتشافها. على سبيل المثال، جرب تنفيذ فلتر لابلاس في لعبة الالتفاف أعلاه وشاهد ما يفعله.

الخلاصة

آمل أن تكون هذه المقالة الصغيرة مفيدة في تقديم المفاهيم الأساسية لكتابة فلاتر الصور في JavaScript باستخدام علامة لوحة HTML. أنصحكم بتنفيذ بعض فلاتر الصور، إنه أمر ممتع للغاية!

إذا كنت بحاجة إلى أداء أفضل من الفلاتر، فيمكنك عادةً نقلها لاستخدام أدوات تظليل أجزاء WebGL لإجراء معالجة الصور. باستخدام أدوات التظليل، يمكنك تشغيل معظم الفلاتر البسيطة في الوقت الفعلي، مما يسمح لك باستخدامها مع مقاطع الفيديو والصور المتحركة بعد المعالجة.