Vinnovera

Bildmanipulering i JavaScript

Alexandra Bjelkholm | | JavaScript, Filter

När det läggs ett digitalt filter över en bild manipuleras pixeldatan i bilden. Färgerna i bilden räknas om för varje pixel och sedan visas en ny bild med de nya värderna. I fortsättningen kommer jag att använda termen filter för att beskriva manipuleringen av bildens data med hjälp av Javascript.

Några vanligt förekommande filter är inverterade färger, sepia, blur, kontrastökning, gråskala med mera.

I dagsläget är det vanligast att manipulera bilderna i t.ex. Photoshop innan de används på en hemsida, men det finns alternativ och ett av dom är Javascript.

Varför ska man använda filter?

De som underhåller webbplatser är oftast inte själva utvecklare eller grafiker. Vill de placera samma bild på flera olika ställen men med olika filter behöver de inte själva editera bilden med hjälp av externa verktyg.

Om en sida har flera olika instanser av samma bild med olika filter, t.ex. porträttbilder av Andy Warhol, behöver browsern endast hämta bilden en gång. Det kan leda till en snabbare site med färre anrop.

Kan man använda filter?

Om browsern stödjer canvas-objektet kan man använda filter. Idag har följande browsers stöd för canvas:

  • Internet Explorer 9+
  • Firefox
  • Opera
  • Chrome
  • Safari
  • Mobile browsers

I Android 4.0 ursprungs browser finns det, vad som antas vara, en bugg som gör att anges alpha i något annat än 0 eller 255 ändras pixelns färg istället.

Prestandan för canvas skiljer sig åt på olika plattformar. Desktop har generellt bra prestanda men mobila browsers kan vara långsammare.

Hur fungerar filter?

Som nämnt tidigare handlar det om att räkna om värden i bildens pixeldata. För att kunna manipulera bilder behövs ett canvas att rita på och ett ImageData-objekt.

Ett ImageData-objekt är en del av ett canvas, inte en bild eller en form. Objektet innehåller information för varje pixel inom den delen. Informationen består av fyra delar per pixel, RGBA:

R - Röd (0-255)
G - Grön (0-255)
B - Blå (0-255)
A - Alpha (0-255, 0 = helt osynlig - 255 = helt synlig)

Är en pixel röd och synlig har den värdet (255,0,0,255) och det är de värden som lagras i data-egenskapen hos ImageData-objektet. Principen för att rita på ett canvas är samma för bilder som för andra objekt, därför beskrivs först hur man ritar en simpel form.

Form

Koden nedan skapar ett objekt som är 100 x 100 px, där varje pixel är blå, och placerar objektet på ett canvas 10px från kanterna.

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

//Skapa ett objekt att arbeta med
var imageData = ctx.createImageData(100,100);

//Loopa igenom pixlarna
for (var i=0; i<imageData.data.length; i+=4) {
    imageData.data[i+0] = 0;      //red
    imageData.data[i+1] = 0;      //green
    imageData.data[i+2] = 255;    //blue
    imageData.data[i+3] = 255;    //alpha
}

//Skriv ut objektet på canvaset
ctx.putImageData(imageData,10,10);

Resultat:

Funktioner

createImageData():

Skapar ett nytt tomt ImageData-objekt. Objektets pixelvärden är ursprungligen (0,0,0,0), svarta och genomskinliga. Metoden används om det inte redan innan finns ett objekt att arbeta med, t.ex en bild, eller om ett objekt ska kopieras.

Syntax:

Det finns två versioner av metoden,

var imgData = ctx.createImageData(width,height);

  • width = Bredden på det nya ImageData-objektet i px.
  • height = Höjden på det nya ImageData-objektet i px.

var imgData = ctx.createImageData(imageData)

  • imageData = ett annat ImageData-objekt.

putImageData():

Skriver ut pixeldatan från ett specificerat ImageData-objekt på canvaset.

Syntax:

ctx.putImageData(imgData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
  • imgData = Det imageData-objekt som ska skrivas ut på canvaset.
  • x = x-koordinaten för det övre vänstra hörnet av ImageData-objektet, angivet i px.
  • y = y-koordinaten för det övre vänstra hörnet av ImageData-objektet, angivet i px.
  • dirtyX = (optional) Det horizontella x-värdet att placera bilden på, angivet i px.
  • dirtyY = (optional) Det horizontella y-värdet att placera bilden på, angivet i px.
  • dirtyWidth = (optional) Bredden som ska användas för att rita ut bilden på canvaset.
  • dirtyHeight =(optional) Höjden som ska användas för att rita ut bilden på canvaset.

Bild

För att manipulera en bild behövs två ytterligare funktioner, drawImage och getImageData. Funktionen createImageData används när man vill kopiera en existerande bild eller för att skapa en tom yta.

Html:

<img id="image" src="src.jpg" alt="" width="220" height="277">
<canvas id="myCanvas" width="220" height="277"></canvas>

Script:

document.getElementById("image").onload = function(){
    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");

    //Hämta bild
    var img = document.getElementById("image");

        //Rita ut bild på canvas
    ctx.drawImage(img,0,0);

    //Hämta bilddata
    var imgData = ctx.getImageData(0,0,c.width,c.height);

    // invertera färgerna i bilden
    for (var i=0; i<imgData.data.length; i+=4) {
        imgData.data[i]=255-imgData.data[i];
        imgData.data[i+1]=255-imgData.data[i+1];
        imgData.data[i+2]=255-imgData.data[i+2];
        imgData.data[i+3]=255;
    }

    //Skriv ut den manipulerade bilden på canvaset
    ctx.putImageData(imgData,0,0);
}

Resultat:

Funktioner

drawImage():

Ritar ut en bild, video eller ett canvas på ett canvas. Metoden kan även rita ut delar av en bild och ändra en bilds eller videos storlek.

Syntax:

context.drawImage(img,x,y);
context.drawImage(img,x,y,width,height);
ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
  • img = Det element som ska användas - bild, canvas eller video.
  • x = x-koordinaten där elementet ska placeras.
  • y = y-koordinaten där elementet ska placeras.
  • width = (optional) vidden av elementet som ska användas (öka el. minska strl på elem).
  • height = (optional) höjden av elementet som ska användas (öka el. minska strl på elem).
  • sx = (optional) x-koordinaten där bilden ska börja skäras.
  • sy = (optional) y-koordinaten där bilden ska börja skäras.
  • swidth = (optional) vidden av den beskurna bilden.
  • sheight = (optional) höjden av den beskurna bilden.
getImageData():

Retunerar ett ImageData-objekt som kopierat pixeldatan från en specificerad del av canvaset.

Syntax:

ctx.getImageData(x, y, width, height)
  • x = x-koordinaten för det övre vänstra hörnet som den ska börja kopiera från, angivet i px.
  • y = y-koordinaten för det övre vänstra hörnet som den ska börja kopiera från, angivet i px.
  • width = Bredden på det du vill kopiera.
  • height =Höjden på det du vill kopiera.

Slutsatser

När jag jämförde två sidor, en där jag hade två bilder och en där jag hade en bild som jag skrev ut två gånger med hjälp av filter-funktioner, fick jag följande resultat:

Javascript två bilder
Antal requests 2 3
kb överfört 149 285
paint (ms) 3.0 39.2
hämta bild/bilder (ms) 23 150
finished loading (ms) 117 198

Jag fick inte de resultat jag hade väntat mig och det beror främst på att jag trodde att paint-tiden skulle vara högre när jag använde canvas-objektet. Tittar man på ‘kb överfört’ tycker jag att man tydligt ser att fördelen men att använda filter är att man bara behöver ladda ner en bild. Tänker man den situationen i större skala med t.ex tio stycken bilder blir filter betydligt snabbare. Javascriptet jag använde var minimalt och använder man ett komplexare skript skulle tiden öka för att exekvera skriptet.

Ett ytterligare alternativ som är värt att nämna är web-kit filter. Varför jag inte anser att det är användbart än är att två av de störta webbläsarna, Firefox och Internet Explorer, inte har stöd för det. Skulle de utveckla stöd kan det bli ett starkt alternativ.

Bibliotek

Jag tror inte att det kommer att vara tidseffektivt att skriva ett eget Javascript-plugin. Uträkningarna är komplexa och det krävs väldigt noga testning för att inte slöa ner webbsidor. Nedan är några av de bibliotek som jag hittat som verkar ha potential.

Det bibliotek som verkar användas mest för tillfället är fabricjs. Det kan bero på att det innehåller väldigt mycket mer än bara filter - t.ex. former, drag-n-drop, resize med flera. Jag har även tittat på följande:

Referenser

Codepen : (Egna bilder behövs för att testa skripten)
Länk till Javascript version: http://codepen.io/Sandra/pen/LjGgh
Länk till version med två bilder : http://codepen.io/Sandra/pen/lrjvJ

Alexandra Bjelkholm

Alexandra Bjelkholm

(alexandra@vinnovera.se)

Alexandra Bjelkholm jobbar som front-end utvecklare på Vinnovera.

Vi är specialister på front-end och ser gränssnitt för desktop, surfplattor och mobiltelefoner som vårt hantverk. Vi vill jobba med kunder som tycker att kvalitet är viktigt, tänker långsiktigt och vill skapa webbupplevelser med det där lilla extra.

Tillbaka till bloggen | Mer om Vinnovera

Kommentera