James James - 2 years ago 193
Java Question

JavaScript filterRGB

I'm currently in the process of converting Java to JavaScript and need to change the colour of some images.

Right now each image is loaded within an

Image
class, an image looks like this:

Example image

It's a PNG which works as a character set, the data sent through is mapped to each character in the image.

The existing Java code looks like this:

class VDColorFilter extends RGBImageFilter
{
int fg;
int bg;
final int[] colors;

public VDColorFilter(final int fgc, final int bgc) {
super();
this.colors = new int[] { 0, 16711680, 65280, 16776960, 255, 16711935, 65535, 16777215 };
this.fg = fgc;
this.bg = bgc;
this.canFilterIndexColorModel = true;
}

public int filterRGB(final int x, final int y, int rgb) {
if (rgb == -1) {
rgb = (0xFF000000 | this.colors[this.bg]);
}
else if (rgb == -16777216) {
rgb = (0xFF000000 | this.colors[this.fg]);
}
return rgb;
}
}


I want to be able to do the same thing to my images, but in JavaScript. I don't have much experience with Java, so I'm unsure on how the
filterRGB
actually applies the RGB result, against the
colors
array.

Of course, this is only tinting the black of the image, not the white.

Are there any libraries out there which mimic this? If not, what is my best way of achieving the same result?

K3N K3N
Answer Source

You can filter an image using getImageData() and putImageData(). This will require cross-origin resource sharing (CORS) to be fulfilled, e.g. the image comes from the same server as the page (a security mechanism in the browser).

If that part is OK, lets do an example using your image -

The best would be if your images had an alpha channel instead of white background. This would allow you to use composite operators to change the colors directly without having to parse the pixels.

You can do this two ways:

  1. Punch out the background once and for all, then use composite operator (recommended)
  2. Replace all black pixels with the color

With the first approach you only have to parse the pixels once. Every time you need to change the colors just use a composite operator (see demo 2 below).

Using Composite Operator

Here is a way to punch out the background first. We will be using a unsigned 32-bit buffer for this as this is faster than using a byte-array.

We can convert the byte-buffer by using the view's buffer and create a second view for it:

var data32 = new Uint32Array(idata.data.buffer);

See code below for details:

var img = new Image();
img.crossOrigin = "";
img.onload = punchOut;
img.src = "//i.imgur.com/8NWz72w.png";

function punchOut() {
  
  var canvas = document.createElement("canvas"),
      ctx = canvas.getContext("2d");

  document.body.appendChild(this);
  document.body.appendChild(canvas);
  
  // set canvas size = image size
  canvas.width = this.naturalWidth; 
  canvas.height = this.naturalHeight;
  
  // draw in image
  ctx.drawImage(this, 0, 0);
  
  // get pixel data
  var idata = ctx.getImageData(0, 0, canvas.width, canvas.height),
      data32 = new Uint32Array(idata.data.buffer),  // create a uint32 buffer
      i = 0, len = data32.length;

  while(i < len) {
    if (data32[i] !== 0xff000000) data32[i] = 0;    // if not black, set transparent
    i++
  }
  
  ctx.putImageData(idata, 0, 0);                    // put pixels back on canvas
}
body {background:#aaa}

Now that we have a transparent image we can use composite modes to alter its colors. The mode we need to use is "source-atop":

var img = new Image();
img.crossOrigin = ""; img.onload = punchOut;
img.src = "//i.imgur.com/8NWz72w.png";
function punchOut() {
  var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d");
  canvas.width = this.naturalWidth; 
  canvas.height = this.naturalHeight;  
  ctx.drawImage(this, 0, 0);
  var idata = ctx.getImageData(0, 0, canvas.width, canvas.height),
      data32 = new Uint32Array(idata.data.buffer), i = 0, len = data32.length;
  while(i < len) {if (data32[i] !== 0xff000000) data32[i] = 0;  i++}
  ctx.putImageData(idata, 0, 0);
  
  // NEW PART --------------- (see previous demo for detail of the code above)
  
  // alter color using composite mode
  // This will replace existing non-transparent pixels with the next drawn object
  ctx.globalCompositeOperation = "source-atop";
  
  function setColor() {
    for (var y = 0; y < 16; y++) {
      for (var x = 0; x < 6; x++) {
        var cw = (canvas.width - 1) / 6,
            ch = (canvas.height - 1) / 16,
            cx = cw * x,
            cy = ch * y;
        
        // set the desired color using fillStyle, here: using HSL just to make cycle
        ctx.fillStyle = "hsl(" + (Math.random() * 360) + ", 100%, 80%)";
    
        // fill the area with the new color, due to comp. mode only existing pixels
        // will be changed
        ctx.fillRect(cx+1, cy+1, cw-1, ch-1);
        }
      }
  }
  setInterval(setColor, 100);
  
  // to reset comp. mode, use:
  //ctx.globalCompositeOperation = "source-over";
}
body {background:#333}
<canvas></canvas>

And finally, use drawImage() to pick each letter based on mapping and cell calculations for each char (see for example the previous answer I gave you for drawImage usage).

  • Define a char map using a string
  • Find the letter using the map and indexOf()
  • Calculate the index of the map to x and y in the image
  • Use drawImage() to draw that letter to the x/y position in the output canvas

Random letters

var img = new Image();
img.crossOrigin = ""; img.onload = punchOut;
img.src = "http://i.imgur.com/8NWz72w.png";
function punchOut() {
  var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d");
  canvas.width = this.naturalWidth; 
  canvas.height = this.naturalHeight;  
  ctx.drawImage(this, 0, 0);
  var idata = ctx.getImageData(0, 0, canvas.width, canvas.height),
      data32 = new Uint32Array(idata.data.buffer), i = 0, len = data32.length;
  while(i < len) {if (data32[i] !== 0xff000000) data32[i] = 0;  i++}
  ctx.putImageData(idata, 0, 0);
  ctx.globalCompositeOperation = "source-atop";  

  function setColor() {
    for (var y = 0; y < 16; y++) {
      for (var x = 0; x < 6; x++) {
        var cw = (canvas.width - 1) / 6,
            ch = (canvas.height - 1) / 16,
            cx = cw * x,
            cy = ch * y;
        ctx.fillStyle = "hsl(" + (Math.random() * 360) + ", 100%, 80%)";
        ctx.fillRect(cx+1, cy+1, cw-1, ch-1);
        }
      }
  }
  setColor();

  // NEW PART --------------- (see previous demo for detail of the code above)
  
  var dcanvas = document.createElement("canvas"), xpos = 0;
  ctx = dcanvas.getContext("2d");

  document.body.appendChild(dcanvas);
  
  for(var i = 0; i < 16; i++) {
    var cw = (canvas.width - 1) / 6,
        ch = (canvas.height - 1) / 16,
        cx = cw * ((Math.random() * 6)|0),  // random x
        cy = ch * ((Math.random() * 16)|0); // random y
    ctx.drawImage(canvas, cx+1, cy+1, cw-1, ch-1, xpos, 0, cw-1, ch-1);
    xpos += 16;
  }
  
}
body {background:#333}
<canvas></canvas>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download