Bildfilter mit Canvas

Ilmari Heikkinen

Einführung

Mit dem HTML5-Canvas-Element können Bildfilter geschrieben werden. Sie müssen ein Bild auf eine Leinwand zeichnen, die Leinwandpixel zurücklesen und den Filter darauf anwenden. Sie können das Ergebnis dann in einen neuen Canvas schreiben oder einfach den alten wiederverwenden.

Klingt einfach? Gut. Los geht's!

Das ursprüngliche Testbild
Originaltestbild

Pixel verarbeiten

Rufen Sie zuerst die Bildpixel ab:

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

Als Nächstes benötigen wir eine Möglichkeit, Bilder zu filtern. Wie wäre es mit einer filterImage-Methode, die einen Filter und ein Bild annimmt und die gefilterten Pixel zurückgibt?

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

Einfache Filter ausführen

Nachdem wir die Pixelverarbeitungspipeline zusammengestellt haben, ist es an der Zeit, einige einfache Filter zu schreiben. Zuerst konvertieren wir das Bild in Graustufen.

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

Sie können die Helligkeit anpassen, indem Sie den Pixeln einen festen Wert hinzufügen:

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

Das Festlegen eines Schwellenwerts für ein Bild ist ebenfalls recht einfach. Sie vergleichen einfach den Graustufenwert eines Pixels mit dem Schwellenwert und legen die Farbe entsprechend fest:

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

Bilder konvolvieren

Faltungsfilter sind sehr nützliche generische Filter für die Bildverarbeitung. Dabei wird die gewichtete Summe eines Pixels aus dem Quellbild als Ausgabewert verwendet. Mit Konvolutionsfiltern können Bilder unscharf gestellt, geschärft, geprägt, Kanten erkannt und vieles mehr werden.

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

Hier ist ein 3 × 3-Schärfungsfilter. Sie sehen, dass der Schwerpunkt auf dem mittleren Pixel liegt. Damit die Helligkeit des Bildes erhalten bleibt, sollte die Summe der Matrixwerte 1 betragen.

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

Hier ist ein weiteres Beispiel für einen Faltungsfilter, das Weichzeichnen des Rechtecks. Die Box-Unschärfe gibt den Durchschnitt der Pixelwerte innerhalb der Faltungsmatrix aus. Dazu erstellen Sie eine Convolutionsmatrix der Größe N × N, bei der jedes Gewicht 1 ÷ (N × N) beträgt. So trägt jedes Pixel in der Matrix gleichmäßig zum Ausgabebild bei und die Summe der Gewichte ist 1.

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

Wir können komplexere Bildfilter erstellen, indem wir vorhandene Filter kombinieren. Schreiben wir beispielsweise einen Sobel-Filter. Ein Sobel-Filter berechnet die vertikalen und horizontalen Gradienten des Bildes und kombiniert die berechneten Bilder, um Kanten im Bild zu finden. Wir implementieren den Sobel-Filter hier so, dass wir das Bild zuerst in Graustufen umwandeln, dann die horizontalen und vertikalen Farbverläufe erfassen und schließlich die Farbverlaufsbilder zum Endbild kombinieren.

Der Begriff „Gradient“ bezieht sich hier auf die Änderung des Pixelwerts an einer Bildposition. Wenn ein Pixel links einen Nachbarn mit dem Wert 20 und rechts einen Nachbarn mit dem Wert 50 hat, beträgt der horizontale Farbverlauf bei diesem Pixel 30. Beim vertikalen Farbverlauf wird dasselbe Prinzip angewendet, aber die Farben werden von den Nachbarn über und unter dem Pixel abgeleitet.

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
}

Es gibt noch viele weitere coole Faltungsfilter, die darauf warten, von dir entdeckt zu werden. Implementieren Sie beispielsweise einen Laplace-Filter in der obigen Faltungsvorlage und sehen Sie sich die Auswirkungen an.

Fazit

Ich hoffe, dass dieser kleine Artikel Ihnen dabei geholfen hat, die grundlegenden Konzepte zum Erstellen von Bildfiltern in JavaScript mit dem HTML-Canvas-Tag kennenzulernen. Ich empfehle Ihnen, noch weitere Bildfilter zu implementieren. Es macht wirklich Spaß!

Wenn Sie eine bessere Leistung von Ihren Filtern benötigen, können Sie sie in der Regel so portieren, dass für die Bildverarbeitung WebGL-Fragment-Shader verwendet werden. Mit Shadern können Sie die meisten einfachen Filter in Echtzeit ausführen, sodass Sie sie für die Nachbearbeitung von Videos und Animationen verwenden können.