Blur mit dem HTML5-Element <canvas>
JavaScript, HTML5

Das HTML5-Element <canvas> enthält keine Methode zum Blurren. Soll die Funktionalität hinzugefügt werden, muss hierfür eine eigene Routine implementiert werden.

Voraussetzung für das Funktionieren der blur-Funktion ist das Vorhandensein der Methoden getImageData und putImageData.

Das nachstehende Script enthält eine solche Implementierung.

Beispiel

Initialisierung

Der Funktion kann ein Objekt zum Initialisieren übergeben werden, das folgende Optionen enthalten kann.

  1. id - (string) ID des canvas-Elements
  2. w - (integer) gewünschte Breite des canvas-Elements
  3. h - (integer) gewünschte Höhe des canvas-Elements
  4. init - (boolean) true = es werden die Optionen zum Initialisieren des canvas-Elements verwendet (w, h),
    false = es werden externe Eigenschaften verwendet (CSS, Attribute width/height usw.)

API

Das Objekt bietet eine API mit folgenden Methoden:

  1. blur() - zum Blurren des canvas-Inhalts
  2. insertImg(strSrc, x, y) - Einfügen eines Bildes in den Kontext, dessen Quelle in strSrc übergeben wird. Ausserdem werden die Startkoordinaten in x und y übergeben.
  3. getPixel(x, y) - Ermitteln eines Pixels mit den Koordinaten (x,y).
    Rückgabe ist ein Array mit [0]:rot, [1]:grün, [2]:blau, [3]:alpha
  4. restoreCtx() - Macht schrittweise die Blur-Schritte rückgängig

Beispiel: Initialisieren des Objekts

Der nachstehende Quellcode zeigt, wie das Objekt im onload-Event initialisiert wird.

JavaScript:
window.onload = function(){
  objAPI = qpCanvas({
    id: 'canvas_id',
    w: 100,
    h: 100
  }).insertImg("distel.jpg", 0, 0);
}

Über die zurückgegebene Referenz objAPI kann später auf die API zugegriffen werden, zum Beispiel:

JavaScript:
objAPI.blur();

Quellcode des Scripts

Kurze Anmerkung zum Blurren:
Es werden zu jedem Pixel für jeden Farbkanal (rot, grün, blau) die Farbwerte der umliegenden Pixel addiert und der arithmetische Mittelwert gebildet (Division durch Acht). Der berechnet Wert wird dem jeweils zugehörigen Farbkanal im Ausgangspixel zugewiesen.

Der folgende Quellcode zeigt die oben vorgestellte Routine.

JavaScript:
/* ****************************************************************************************** *
 * Das Script kann frei verwendet werden, dieser Kommentar sowie die Nennung des Nicks
 * müssen jedoch erhalten bleiben.
 *
 *                                                               Quaese (www.quaese.de), 2010
 * ****************************************************************************************** */
 function qpCanvas(objOpts){
  var opts = {
    cvs: null,
    ctx: null,
    w: 400,
    h: 300,
    id: null,
    init: true,
    stack: []
  };

  for(strKey in objOpts){
    opts[strKey] = objOpts[strKey];
  }

  var _this = this;

  var init = function(){
    if((typeof opts.id == "undefined") || (opts.id==null) || (document.getElementById(opts.id)==null)){
      alert('keine gültige ID');
      return;
    }

    opts.cvs = document.getElementById(opts.id);
    if(opts.cvs.getContext){
      opts.ctx = opts.cvs.getContext("2d");

      // Falls das canvas-Element über die Optionen initialisiert werden soll
      if(opts.init){
        opts.cvs.style.width = opts.w + "px";
        opts.cvs.style.height = opts.h + "px";
      }else{
        if(window.getComputedStyle){
          opts.w = parseInt(window.getComputedStyle(opts.cvs, null)['width']);
          opts.h = parseInt(window.getComputedStyle(opts.cvs, null)['height']);
        }else if(opts.cvs.currentStyle){
          opts.w = parseInt(opts.cvs.currentStyle['width']);
          opts.h = parseInt(opts.cvs.currentStyle['height']);
        }
      }
      opts.cvs.width = opts.w;
      opts.cvs.height = opts.h;

      // Context bequemer zugänglich machen
      ctx = opts.ctx;
      // Hilfskomponente für Contextinhalt initialisieren
      opts.tmp = null;
    }
  }


  // Methode zum Ermitteln eines Pixels (weniger performant als nachfolgende Variante)
  // x - (integer) Spaltenwert
  // y - (integer) Zeilenwert
  // Rückgabe: (array) Pixel, mit [0]:rot, [1]:grün, [2]:blau, [3]:alpha
  opts.getPixel__ = function(x, y){
    // Falls die benötigte Methode nicht verfügbar ist
    if(typeof ctx.getImageData != "function") return opts;

    var objImageData = ctx.getImageData(x, y, 1, 1);

    return objImageData.data;
  }

  // Methode zum Ermitteln eines Pixels (performanter als obige Variante)
  // x - Spaltenwert
  // y - Zeilenwert
  // Rückgabe: (array) Pixel, mit [0]:rot, [1]:grün, [2]:blau, [3]:alpha
  opts.getPixel = function(x, y){
    var blnNull = false;
    // Falls kein ImageData-Objekt existiert
    if(opts.tmp == null){
      // Falls die benötigte Methode nicht verfügbar ist
      if(typeof ctx.getImageData != "function") return opts;

      // Temp. ImageData-Objekt erstellen
      opts.tmp = ctx.getImageData(0, 0, opts.cvs.width, opts.cvs.height);
      // Markieren, dass ein temp. ImageData-Objekt erstellt wurde
      blnNull = true;
    }

    //var z = y*(opts.w-1)*4;
    var z = y*opts.w*4;
    var s = 4*x;
    var oPxl = [opts.tmp.data[z + s], opts.tmp.data[z + s +1], opts.tmp.data[z + s + 2], opts.tmp.data[z + s + 3]];

    // Falls ein temp. ImageData-Objekt erstellt wurde
    if(blnNull){
      // Temp. ImageData-Objekt wieder zerstören und neu initialiseren
      delete opts.tmp;
      opts.tmp = null;
    }

    return oPxl;
  }

  // Methode zum Berechnen des Blur-Wertes eines Pixels
  // x - (integer) Spaltenwert
  // y - (integer) Zeilenwert
  // Rückgabe: (object) Basisobjekt, um Kette nicht zu unterbrechen
  opts.blurPixel = function(x, y){
    // Umliegende Pixel ermitteln (unter Beachtung der Ränder)
    var oPxl = {
      xminus_yminus: opts.getPixel(((x==0)? x+1 : x-1), ((y==0)? y+1 : y-1)),
      xminus_y: opts.getPixel(((x==0)? x+1 : x-1), y),
      xminus_yplus: opts.getPixel(((x==0)? x+1 : x-1), ((y==opts.h-1)? y-1 : y+1)),
      x_yminus: opts.getPixel(x, ((y==0)? y+1 : y-1)),
      x_yplus: opts.getPixel(x, ((y==opts.h-1)? y-1 : y+1)),
      xplus_yminus: opts.getPixel(((x==opts.w-1)? x-1 : x+1), ((y==0)? y+1 : y-1)),
      xplus_y: opts.getPixel(((x==opts.w-1)? x-1 : x+1), y),
      xplus_yplus: opts.getPixel(((x==opts.w-1)? x-1 : x+1), ((y==opts.h-1)? y-1 : y+1))
    };

    // Umliegende Pixel summieren
    var intRed   = oPxl.xminus_yminus[0]+oPxl.xminus_y[0]+oPxl.xminus_yplus[0]+oPxl.x_yminus[0]+oPxl.x_yplus[0]+oPxl.xplus_yminus[0]+oPxl.xplus_y[0]+oPxl.xplus_yplus[0];
    var intGreen = oPxl.xminus_yminus[1]+oPxl.xminus_y[1]+oPxl.xminus_yplus[1]+oPxl.x_yminus[1]+oPxl.x_yplus[1]+oPxl.xplus_yminus[1]+oPxl.xplus_y[1]+oPxl.xplus_yplus[1];
    var intBlue  = oPxl.xminus_yminus[2]+oPxl.xminus_y[2]+oPxl.xminus_yplus[2]+oPxl.x_yminus[2]+oPxl.x_yplus[2]+oPxl.xplus_yminus[2]+oPxl.xplus_y[2]+oPxl.xplus_yplus[2];

    // Errechnen der mittleren Farben
    pBlurRed = intRed/8;
    pBlurGreen = intGreen/8;
    pBlurBlue = intBlue/8;

    // Zeilen- und Spaltenwert zum Ermitteln des Offsets des aktuellen Pixels im ImageData-Objekt berechnen
    var z = y*opts.w*4;
    var s = 4*x;

    // Pixel setzen
    opts.tmp.data[z + s] = pBlurRed;
    opts.tmp.data[z + s + 1] = pBlurGreen;
    opts.tmp.data[z + s + 2] = pBlurBlue;

    return opts;
  };

  // Methode zum Blurren des Context-Inhaltes
  // Rückgabe: (object) Basisobjekt, um Kette nicht zu unterbrechen
  opts.blur = function(){
    // Falls die benötigte Methode nicht verfügbar ist
    if(typeof ctx.getImageData != "function"){
      alert("Ihr Browser unterstützt die angeforderte Funktion nicht.");
      return opts;
    }

    // Temp. ImageData-Objekt erstellen
    opts.tmp = ctx.getImageData(0, 0, opts.w, opts.h);

    // Zeilen
    for(var m=0; m<opts.h; m++){
      // Spalten
      for(var n=0; n<opts.w; n++){
        // Pixel blurren
        opts.blurPixel(n, m);
      }
    }

    // Aktuellen Kontext speichern
    opts.stack[opts.stack.length] = ctx.getImageData(0, 0, opts.w, opts.h);

    // Kontext leeren
    ctx.clearRect(0, 0, opts.w, opts.h);
    // Temp. ImageData-Objekt in Context rendern
    ctx.putImageData(opts.tmp, 0, 0);

    // Temp. ImageData-Objekt zerstören und neu initialisieren
    delete opts.tmp;
    opts.tmp = null;

    // Kette nicht unterbrechen
    return opts;
  };

  // Undo-Methode
  // Rückgabe: (object) Basisobjekt, um Kette nicht zu unterbrechen
  opts.restoreCtx = function(){
    if(opts.stack.length > 0){
      // Kontext leeren
      ctx.clearRect(0, 0, opts.w, opts.h);
      // Gesichertes ImageData-Objekt in Context rendern
      ctx.putImageData(opts.stack[opts.stack.length-1], 0, 0);
      opts.stack.pop();
    }

    // Kette nicht unterbrechen
    return opts;
  }

  // Methode zum Einfügen eines Bildes
  // strSrc - (string) Bildquelle
  // x      - (integer) Horizontale Startkoordinate
  // y      - (integer) Vertikale Startkoordinate
  // Rückgabe: (object) Basisobjekt, um Kette nicht zu unterbrechen
  opts.insertImg = function(strSrc, x, y){
    var objImg = new Image();
    objImg.src = strSrc;
    objImg.onload = function(){
      ctx.drawImage(this, x, y);
    };

    // Kette nicht unterbrechen
    return opts;
  }

  // Initialisierungsfunktion
  init();

  // Kette nicht unterbrechen
  return opts;
}
© 2010 Quaese