开发者

Image editing with javascript

In the creation of my html5 game engine I've been able to do some nice things and get some cool features. On a contract to make a game I've been asked to see if I can remove the background color from sprite images. And I see t开发者_如何学JAVAhe pluses with this since we could use jpgs instead on pngs and decrease the size of the images.

Is there any way I can do this with pure javascript? I'd like to be able to do this without using the a canvas element so it can be faster, but if I have to that's okay.

If I have to do that I have another question, I don't want the canvas object to show that I use, can I use a canvas object with document.createElement without applying it to the document? That would be nice since it wouldn't have to be rendered to the webpage. If not I guess I can just move the canvas object to the left out of view.

Lastly do you think a good way to preprocess the images be to send them to a server cgi script and have it return a json pixel array?


Here is the function for floodfill algorithm, it removed the background from an image which is already drawn on the canvas.

In the following code canvas is the HTML5 canvas element and context it canvas.getContext("2d"). You can change the value of colorRange and try the function with different colors. The last line of the function

 imageElement.src=canvas.toDataURL("image/png");

is to show the image inside an img tag. So you need an img and a canvas on your page. If you don't want to show the image in img element just remove the last line.

// Remove backgroud without ajax call, can be used in non IE browsers.
function RemoveBackground(){

    var startR,startG,startB;
    var canvasData;

    var canvasWidth=canvas.width;
    var canvasHeight=canvas.height;
    canvasData=mainContext.getImageData(0,0,canvasWidth,canvasHeight);
    startR = canvasData.data[0];
    startG = canvasData.data[1];
    startB = canvasData.data[2];
    if(startR==0&& startG==0 && startR==0) return;
    var pixelStack = [[0, 0]];  
    while(pixelStack.length)
    {
      var newPos, x, y, pixelPos, reachLeft, reachRight;
      newPos = pixelStack.pop();
      x = newPos[0];
      y = newPos[1];

      pixelPos = (y*canvasWidth + x) * 4;
      while(y-- >= 0 && matchStartColor(pixelPos,canvasData,startR,startG,startB)){
        pixelPos -= canvasWidth * 4;
    }
      pixelPos += canvasWidth * 4;
      ++y;
      reachLeft = false;
      reachRight = false;
      while(y++ < canvasHeight-1 && matchStartColor(pixelPos,canvasData,startR,startG,startB))
      {
        colorPixel(pixelPos,canvasData);
        if(x > 0)
        {
          if(matchStartColor(pixelPos-4,canvasData,startR,startG,startB))
          {
            if(!reachLeft){
              pixelStack.push([x - 1, y]);
              reachLeft = true;
            }
          }
          else if(reachLeft)
          {
            reachLeft = false;
          }
        }

        if(x < canvasWidth-1)
        {
          if(matchStartColor(pixelPos+4,canvasData,startR,startG,startB))
          {
            if(!reachRight)
            {
              pixelStack.push([x + 1, y]);
              reachRight = true;
            }
          }
          else if(reachRight)
          {
            reachRight = false;
          }
        }   
        pixelPos += canvasWidth * 4;
      }
    }
    context.putImageData(canvasData, 0, 0);
    imageElement.src=canvas.toDataURL("image/png");

}

// Helper function for remove background color.
function matchStartColor(pixelPos,canvasData,startR,startG,startB)
{
  var r = canvasData.data[pixelPos];    
  var g = canvasData.data[pixelPos+1];  
  var b = canvasData.data[pixelPos+2];
  var colorRange=8;

  return ((r >= startR-colorRange && r<=startR+colorRange) 
        &&( g >= startG-colorRange && g<=startG+colorRange)
        &&( b >= startB-colorRange && b<= startB+colorRange));
}
// Helper function for remove background color.
function colorPixel(pixelPos,canvasData)
{
  canvasData.data[pixelPos] = 255;
  canvasData.data[pixelPos+1] = 255;
  canvasData.data[pixelPos+2] = 255;
}


Removing background without choppy borders isn't a trivial task, even by hand in image-editing programs. You'll have to implement some sort of antialiasing, at least.

Moreover, it's not a good idea to manipulate an image compressed into a lossy format.

PNG compression is superior (in terms of size) to JPG on simpler images with continuous fill of the same color and certain types of gradients. JPG is only good for heterogeneous images with lots of different colors mixed in unpredictable manner. Like photos. Which one would not expect in game sprites, I guess. And again – JPG is a lossy format.

As for the Canvas element, it doesn't have to be added to the DOM tree at all.

The most naïve algorithm to make a given color transparent would be such: draw the image, get its pixel data, iterate over the data and compare every pixel color with your given color. If it matches, set the alpha to 0.

Canvas API methods you'll need:

  • drawImage

  • getImageData

The somewhat tricky in it's simplicity part is the CanvasPixelArray. To check each pixel in such arrays, you do something like that:

for (var i = 0; i < pixelAr.length; i += 4) {
    var r = pixelAr[i];
    var g = pixelAr[i + 1];
    var b = pixelAr[i + 2];
    var alpha = pixelAr[i + 3];
}


Personally I would not go down this path. JPEG images are compressed, which means that whatever you define as a background color may change slightly in the compressed file (ie. you'll get the classic JPEG artifacting). Furthermore, you won't be able to support partial transparency unless you define a range for your background color, which in turn makes the editing more complicated. The tradeoff between file size and performance/quality is nowhere near worth it here, in my opinion.

Having said that, the only way you can access the pixel data from an image is by placing it on a canvas first. You can, as you mentioned, work with the canvas off-screen in memory without having to append it to the document.

If I understand your last question correctly, you cannot work with a canvas element on the server side. To work with pixel data on your server, you'd have to use something like PHP's image library.

If all of that doesn't sway you in favor of just using PNG images, here's some sample code that will remove a specified background color from a loaded image:

$(document).ready(function() {
    var matte_color = [0, 255, 0, 255]; // rgba: [0, 255];

    // Handles the loaded image element by erasing the matte color
    // and appending the transformed image to the document.
    function handleLoadedImage() {
        eraseMatte(); // Eliminate the matte.

        // Append the canvas element to the document where it is needed.
        document.getElementById("parent_container").appendChild(canvas);
    }

    // Given the matte color defined at the top, clear all pixels to 100% transparency
    // within the loaded sprite.
    function eraseMatte() {
        canvas.width = sprite.width;
        canvas.height = sprite.height;
        context.drawImage(sprite, 0, 0); // Draw the image on the canvas so we can read the pixels.

        // Get the pixel data from the canvas.
        var image_data = context.getImageData(0, 0, sprite.width, sprite.height);
        var data = image_data.data; // Obtaining a direct handle is a huge performance boost.
        var end = sprite.width * sprite.height * 4; // W x H x RGBA

        // Loop over each pixel from the image and clear matte pixels as needed.
        for (var i = 0; i < end; i += 4) {
            if (data[i] == matte_color[0] && data[i + 1] == matte_color[1] &&
                data[i + 2] == matte_color[2] && data[i + 3] == matte_color[3]) { // Color match.
                data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0; // Set pixel to transparent.
            }
        }

        // Put the transformed image data back on the canvas.
        context.putImageData(image_data, 0, 0);
    }

    var canvas = document.createElement("canvas");
    var context = canvas.getContext("2d");
    var sprite = new Image();
    sprite.onload = handleLoadedImage;
    sprite.src = "sprite.jpg";
});


You can do that using a canvas, don't know if it is possible without it. An easy way to achieve what you are trying to do is using the getImageData on your canvas's context:

imgData = myCanvasContext.getImageData(x1, y1, x2, y2);

x1, y1, x2, y2 are the coordenates of the area you want to get data, for the whole use 0, 0, width, height image. The getImageData will return you an ImageData, wich contains an array with rgba values from each pixel. The values will be ordered like this: http://i.stack.imgur.com/tdHNJ.png

You can manipulate the array imgData.data[index], editing it values and, consequently, editing the image.

Here is a good article about editing images on html5 with canvas: http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/


To doesn't show what you are doing, just create the canvas with the css command display:none;


(...)if I can remove the background color from sprite images. And I see the pluses with this since we could use jpgs instead on pngs(...)

I really recommend you to not do that. The jpg compression of the image can make image editing very hard. Removing the background of a jpg image isn't is easy, and it gets harder with the amount of borders on the image. I'm don't think the size that you will economize will compensate the hard work to remove a background from a jpg image.


Not exactly the same, but you can achieve that. I can give you an headstart on this - checkout this jsFiddle. I built this editor using FabricJS.

var canvas = new fabric.Canvas('c');
var imgInstance = new fabric.Image(imgElement); 
canvas.add(imgInstance);//initialize the Canvas with the image
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜