how can I speed up this basic image processing code?

Hi,

I want to search an image for a color and replace that pixel with another color.

This is my test code so far.

function setup() {
    img = loadImage("../test5.jpg");
    var c = createCanvas(600, 600);
    background(250);
}

function draw() {

    mask();
    image(img,0,0);
    //noLoop();
}

function mask() {
    var tempMe;
    img.loadPixels();

    //print("wow");
    for (var i = 0; i < img.width; i++) {
        for (var j = 0; j < img.height; j++) {
            tempMe = img.get(i,j);
            if (  tempMe[0] == 255 )
            {
                img.set(i, j, color(100, 50, 100));
            }
        }
    }

    img.updatePixels();

}

It runs crazy crazy slow even with small images. Any ideas? cheers

Answers

  • Dunno. This is fast.

    String http_part = "http://";
    PImage img = loadImage(http_part + "www.tfguy44.com/people/i/Tr.jpg");
    
    void setup() {
      size(img.width, img.height);
      pixel_swap();
    }
    
    void draw() {
      image(img, 0, 0);
    }
    
    void pixel_swap() {
      img.loadPixels();
      int i=0;
      for (int y=0; y<img.height; y++) {
        for (int x=0; x<img.width; x++) {
          if (img.pixels[i]==color(0))img.pixels[i] = color(255, 255, 0);
          i++;
        }
      }
      img.updatePixels();
    }
    
  • edited July 2015

    Alternative version based on "SetAllColorAlpha II" : :D
    http://forum.Processing.org/two/discussion/10291/issue-with-color-alpha

    // forum.Processing.org/two/discussion/11880/
    // how-can-i-speed-up-this-basic-image-processing-code
    
    // 2015-Jul-29
    
    static final String URL = "http://" + "www.tfguy44.com/people/i/Tr.jpg";
    
    PImage original, swapped;
    boolean isSwapped;
    
    void setup() {
      if (original == null)  original = loadImage(URL);
      size(original.width, original.height, JAVA2D);
      noLoop();
    
      swapped = swapPixelColor(original.get(), 0, #FFFF00);
    }
    
    void draw() {
      background(isSwapped? swapped : original);
    }
    
    void mousePressed() {
      isSwapped ^= redraw = true;
    }
    
    void keyPressed() {
      mousePressed();
    }
    
    static final PImage swapPixelColor(PImage img, color old, color now) {
      old &= ~#000000;
      now &= ~#000000;
    
      img.loadPixels();
      color p[] = img.pixels, i = p.length;
    
      while (i-- != 0)  if ((p[i] & ~#000000) == old)  p[i] = p[i] & #000000 | now;
    
      img.updatePixels();
      return img;
    }
    
  • edited July 2015

    Avoid using the get() and set() methods because they are SLOW.

    Instead use the pixel array as shown by TfGuy44. This version is slightly quicker because it does not use the double loop and the repeated calls to color(...).

    String http_part = "http://";
    PImage img = loadImage(http_part + "www.tfguy44.com/people/i/Tr.jpg");
    
    void setup() {
      size(img.width, img.height);
      pixel_swap();
    }
    
    void draw() {
      image(img, 0, 0);
    }
    
    void pixel_swap() {
      img.loadPixels();
      int orgCol = color(0);
      int newCol = color(255, 255, 0);
      for (int i=0; i < img.pixels.length; i++) {
        if (img.pixels[i] == orgCol) img.pixels[i] = newCol;
      }
      img.updatePixels();
    }
    
  • Sorry. My bad with the double loop. I was tired last night. :-/

  • edited July 2015

    This is the exact code I have now:

    var img;
    function setup() 
    {
        img = loadImage("../Tr.jpg");
        var c = createCanvas(600, 600);
        background(250);
        pixel_swap();
    }
    
    function draw() 
    {
        image(img,0,0);
    }
    
    function pixel_swap() 
    {
      img.loadPixels();
      var orgCol = color(0);
      var newCol = color(255, 255, 255);
      for (var i=0; i < img.pixels.length; i++) 
      {
        print("swapped "+i);
        if (img.pixels[i] == orgCol) img.pixels[i] = newCol;
      }
      img.updatePixels();
      return null;
    }

    Just modified a few things above. But now the output is:

    swapped 0 swapped 1 swapped 2 swapped 3

    it thinks pixel length is 4 :(

    then it just loads the original unchanged image.... using the test image you linked to now..

    I believe this is because my server takes a second to load the image. If I put the call to pixel_swap() in the draw function it will, after a couple of draw rounds, get the proper size of the image. It still doesn't replace the pixels in the image though it will draw the image to the canvas.

    if I print img.pixels[i] it just shows undefined and not as a p5.color

    Maybe this is a processing versus p5 thing?

    Could it be because I don't have a type PImage? (p5 produces an error if I try to use a type beside var)

    thanks!

  • edited July 2015

    Sorry my bad! I've failed to realize this is about p5.js framework. Following the other answerers! :-\"
    And I've ended up pushing a "Java Mode" example for swapping! X_X

    Indeed the way p5.js stores p5.Image's pixels[] is more complex.
    Rather each index being 1 color, we need to access a sequence of 4 in order to get a full RGBa value.

    Here's the same Java code adapted for p5.js. Any doubts about it just ask further: O:-)

    // forum.Processing.org/two/discussion/11880/
    // how-can-i-speed-up-this-basic-image-processing-code
    
    // 2015-Jul-29
    
    const URL = 'https:/' + '/farm6.StaticFlickr.com/5609/15295609879_930639f4c7_m.jpg';
    var original, swapped, isSwapped;
    
    function preload() {
      original = loadImage(URL);
    }
    
    function setup() {
      createCanvas(original.width, original.height);
      noLoop();
    
      swapped = swapPixelColor(original.get(), 0xb, '#FFFF00');
    }
    
    function draw() {
      background(isSwapped? swapped : original);
    }
    
    function mousePressed() {
      redraw(isSwapped = !isSwapped);
    }
    
    function keyPressed() {
      mousePressed();
    }
    
    function swapPixelColor(img, old, now) {
      old = color(old).rgba, now = color(now).rgba;
    
      img.loadPixels();
      const p = img.pixels;
    
      outer: for (var j = 3, i = 0; i < p.length; i = (j += 4) - 3) {
        while (i < j)  if (p[i] !== old[i++ % 4])  continue outer;
        for (i = j - 3; i < j; p[i] = now[i++ % 4]);
      }
    
      img.updatePixels();
      return img;
    }
    
  • edited July 2015

    thanks, almost there

    I have never seen notation like outer: before so that statement kind of confuses me. Can you explain it a bit more?

    Is there a way do this, but:

    1) color something like every fifth red pixel? (creating a pattern / mask look) and also can I do this with:

    2) multiple colors at once.

    Like say find one yellow color and one red color and replace both with black.

    thanks again!

  • edited July 2015 Answer ✓

    I have never seen notation like outer: before...

    It's not a notation but a label. It can be any name just like a variable.
    It's used w/ continue & break keywords in order to choose which loop it needs to escape into.

    For further details, read continue (or break)'s reference:
    https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
    https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label

    Multiple colors at once.

    Simply re-invokes swapPixelColor() w/ diff. old &/or now color parameters for the same p5.Image.

    Color something like every fifth red pixel?

    I can't pinpoint exactly what you mean by that!
    Assuming you merely wanna change each 5th pixels[]'s color to something else, you just need to skip 5*4 indices.

  • That is awesome. Thanks so much.

    Here is my final code for this in case anyone is looking in the future:

    // forum.Processing.org/two/discussion/11880/
    // how-can-i-speed-up-this-basic-image-processing-code
     
    // 2015-Jul-29
     
    //const URL2 = 'https:/' + '/farm6.StaticFlickr.com/5609/15295609879_930639f4c7_m.jpg';
    const URL2 = "../test3.jpg";
    var reColorOffset = 0;
    var original, swapped, isSwapped;
     
    function preload() {
      original = loadImage(URL2);
    }
     
    function setup() {
      createCanvas(original.width, original.height);
      //noLoop();
      print(rgbToHex(0,0,0));
      //swapped = swapPixelColor(original.get(), 0xb, '#FFFF00');
      swapped = swapPixelColor(original.get(), rgbToHex(209,27,102), '#FFFF00');
      swapped = swapPixelColor(swapped.get(), rgbToHex(245,239,129), '#000000');
    }
     
    function draw() {
      background(isSwapped? swapped : original);
    }
     
    function mousePressed() {
      redraw(isSwapped = !isSwapped);
    }
     
    function keyPressed() {
      mousePressed();
    }
     
    function swapPixelColor(img, old, now) {
     
      old = color(old).rgba, now = color(now).rgba;
      
      img.loadPixels();
      const p = img.pixels;
     
      outer: for (var j = 3, i = 0; i < p.length; i = (j += 5*4) - 3) {
        while (i < j)  if (p[i] !== old[i++ % 4])  continue outer;
        for (i = j - 3; i < j; p[i] = now[i++ % 4]);
      }
     
      img.updatePixels();
      return img;
    
    }
    
    function rgbToHex(R,G,B) {return "#"+toHex(R)+toHex(G)+toHex(B)}
    function toHex(n) {
     n = parseInt(n,10);
     if (isNaN(n)) return "00";
     n = Math.max(0,Math.min(n,255));
     return "0123456789ABCDEF".charAt((n-n%16)/16)
          + "0123456789ABCDEF".charAt(n%16);
    }
    
  • edited July 2015

    Congratz! And here are some extra advanced tips: >-)

    • @jonhaber, you don't really need your custom rgbToHex() util function.
    • p5.js's color() function is impressively flexible already w/ various color type parameters:
      http://p5js.org/reference/#/p5/color
    • Although they've purposely censored this, we can pass an RGB or RGBa array of decimal values.
    • So instead of rgbToHex(209, 27, 102), go w/ [209, 27, 102]. Or even color(209, 27, 102) :ar!
    • Other styles: since '#000000' is considered gray scale, a simple 0 does the work!
    • And '#FFFF00' can be replaced w/ a more expressive 'yellow'! :-bd
    • And an important 1: get() w/o any parameters clones the whole p5.Image:
      http://p5js.org/reference/#/p5.Image/get
    • You see, I wanted to keep the original's p5.Image, so I've passed original.get() to swapPixelColor(); since the function modifies the passed array in situ!
    • However, there's no need to clone swapped w/ get() over & over. Since the intention is to keep changing its pixels[] w/ other colors after all.
    • And since it modifies swapped in situ, no need to re-assign it back to swapped either! :-\"
    • Therefore swapped = swapPixelColor(swapped.get(), rgbToHex(245, 239, 129), '#000000');
    • can be fearlessly replaced w/: swapPixelColor(swapped, [245, 239, 129], 0);
Sign In or Register to comment.