Loading...
Logo
Processing Forum
So I've written this piece of code to shift the hues in a specific part of the spectrum. 

I tried my best to optimize it as much as possible: used LUT, downsampled image for the preview and limited the color operations to the parts where needed.

I get pretty good results on the PC, with ~1ms for the shiftHue() function on the downsampled image and ~12ms for the original image. The problem is when I run it on my android phone I only get ~150ms and ~800ms respectively. Is there anything I am missing which can be further optimized? or should I just downsample further for the preview?

( note: I think my question is a general programming question and not android specific, hence posting it here. Am I right?)

here is the code:

Copy code
  1. PImage bigImg;
  2. PImage smallImg;

  3. float hueLUT[];
  4. float hueShift;

  5. void setup() {
  6.   size(480, 854);
  7.   colorMode(HSB, 1);
  8.   imageMode(CENTER);
  9.   
  10.   initHueLUT();
  11.   
  12.   bigImg = loadImage("big.png");
  13.   smallImg = loadImage("small.png");
  14.   image(bigImg, width/2, height/2);  
  15. }

  16. void draw() {
  17.   if (mousePressed) {
  18.     hueShift = map((mouseX), 0, width, -3, 3);
  19.     image(shiftHue(smallImg), width/2, height/2, bigImg.width, bigImg.height);
  20.   }
  21. }

  22. void mouseReleased() {
  23.   image(shiftHue(bigImg), width/2, height/2);
  24. }

  25. PImage shiftHue(PImage in) {
  26.   int w = in.width;
  27.   int h = in.height;
  28.   PImage out = createImage(w, h, HSB);

  29.   in.loadPixels();

  30.   for (int i = 0; i < in.width; i++) {
  31.     for (int j = 0; j < in.height; j++) {
  32.       int index = i + j*in.width;
  33.       color c = in.pixels[index];
  34.       float H = hue(c);
  35.       int hueIndex = int(H*256);

  36.       if (hueLUT[hueIndex]>0) {
  37.         float S = saturation(c);
  38.         float B = brightness(c);     
  39.         H = (hueShift * hueLUT[hueIndex]+H);
  40.         if (H<0) {
  41.           if (H<-1) H = 2+H;
  42.           else H = 1+H;
  43.         }
  44.         else {
  45.           if (H>2) H = H-2;
  46.           else {
  47.             if (H>1) H = H-1;
  48.           }
  49.         }
  50.         out.pixels[index] = color(H, S, B);
  51.       }
  52.       else {
  53.         out.pixels[index] = in.pixels[index];
  54.       }

  55.     }
  56.   }
  57.   out.updatePixels();
  58.   return out;
  59. }

  60. void initHueLUT() {
  61.   hueLUT = new float[256];
  62.   for (int i = 0; i<256; i++) {
  63.     hueLUT[i] = 1.1*pow(sin(PI*(i/256f)-HALF_PI-.12), 60)-.1;
  64.   }
  65. }
Thanks in advance!

EDIT: Replaced the double for loops up there with " for (int i = 0; i < in.pixels.length; i++) " and it got ~30ms faster. anything else?

Replies(17)

For testing purposes I used an online image and did the hueShift on the big image each draw() loop.

I started at about 34 fps and ended up with about 71 fps. More than a 100% speed increase!

Also tested my code in Processing 2.0b8 and it ran at about 95 fps, so even faster.

List of further possible improvements:
  • Don't create images over and over. Instead use input and output images that are created ONCE.
  • LUTs are goooood. Use more LUTs. For example for H, S, B and hueIndex.
  • Don't use superfluous if-else statements like if < 0 and then if < 1.
  • Don't do unnecessary lookups and calculations. See my adapted code within the for loop.
All in all the biggest speed improvement comes from using more LUTs.

Adapted Code
Copy code
  1. PImage input, output;
  2. float[] hueLUT, HL, SL, BL;
  3. int[] hueIndex;
  4.  
  5. void setup() {
  6.   size(800, 600);
  7.   frameRate(1000);
  8.   colorMode(HSB, 1);
  9.   input = loadImage("http://imgs.mi9.com/uploads/cartoon/506/colorful-balloons-cartoon-wallpaper_800x600_7775.jpg");
  10.   output = input.get();
  11.   initLUTs();
  12. }
  13.  
  14. void draw() {
  15.   float hueShift = map(mouseX, 0, width, -3, 3);
  16.   shiftHue(hueShift);
  17.   image(output, 0, 0);
  18.   frame.setTitle(int(frameRate) + " fps");
  19. }
  20.  
  21. void shiftHue(float hueShift) {
  22.   for (int i=0; i<output.pixels.length; i++) {
  23.     float lut = hueLUT[hueIndex[i]];
  24.     if (lut>0) {
  25.       float H = HL[i];
  26.       float S = SL[i];
  27.       float B = BL[i];
  28.       H = hueShift * lut + H;
  29.       if (H<-1) { H += 2; }
  30.       else if (H<0) { H += 1; }
  31.       else if (H>2) { H -= 2; }
  32.       else if (H>1) { H -= 1; }
  33.       output.pixels[i] = color(H, S, B);
  34.     } else {
  35.       output.pixels[i] = input.pixels[i];
  36.     }
  37.   }
  38.   output.updatePixels();
  39. }
  40.  
  41. void initLUTs() {
  42.   hueLUT = new float[256];
  43.   for (int i=0; i<hueLUT.length; i++) {
  44.     hueLUT[i] = 1.1 * pow(sin(PI*(i/256f)-HALF_PI-0.12), 60)-0.1;
  45.   }
  46.   HL = new float[input.pixels.length];
  47.   SL = new float[input.pixels.length];
  48.   BL = new float[input.pixels.length];
  49.   hueIndex = new int[input.pixels.length];
  50.   for (int i=0; i<input.pixels.length; i++) {
  51.     color c = input.pixels[i];
  52.     HL[i] = hue(c);
  53.     SL[i] = saturation(c);
  54.     BL[i] = brightness(c);
  55.     hueIndex[i] = int(HL[i]*256);
  56.   }
  57. }
Amazing! Loads of thanks , I learned a lot. :)
Hi,

I updated the code a little bit. You have to test if the code is faster. I also added a color picker.

Copy code
  1. PImage out, output, cpImage;
  2. PImage bigImg = loadImage("http://farm9.staticflickr.com/8101/8532786267_d653ec298f_b.jpg", "jpg");
  3. float hueLUT[];
  4. float hueShift;
  5. void setup() {  
  6.   size(bigImg.width, bigImg.height);
  7.   output = bigImg.get(); 
  8.   colorMode(HSB, 1);
  9.   initHueLUT();
  10.   ColorPicker();
  11.   out = createImage(bigImg.width, bigImg.height, HSB);
  12.   out.loadPixels();
  13.   image(output, 0, 0);
  14. }
  15. void draw() {
  16.   frame.setTitle( "ShiftHue  ---  FPS: " + round(frameRate));
  17.   if (mousePressed) {
  18.     hueShift = map((mouseX), 0, width, -3, 3);
  19.     image(shiftHue(cpImage), 0, 0);
  20.   }
  21. }
  22. void mouseReleased() {
  23.   image(shiftHue(bigImg), 0, 0);
  24. }
  25. PImage shiftHue(PImage in) {  
  26.   int index = 0;
  27.   for (color c : in.pixels) {
  28.     float H = hue(c);
  29.     int hueIndex = (int) H*256;
  30.     if (hueLUT[hueIndex]>0) {
  31.       float S = saturation(c);
  32.       float B = brightness(c);    
  33.       H = (hueShift * hueLUT[hueIndex]+H);
  34.       if (H<-1) H += 2;     
  35.       else if (H<0) H++;
  36.       else if (H>2) H -= 2;
  37.       else if (H>1) H--;
  38.       out.pixels[index] = color(H, S, B);
  39.     }
  40.     else out.pixels[index] = in.pixels[index];
  41.     index++;
  42.   }
  43.   out.updatePixels();
  44.   return out;
  45. }
  46. void initHueLUT() {
  47.   hueLUT = new float[256];
  48.   for (int i = 0; i<256; i++)
  49.     hueLUT[i] = 1.1*pow(sin(PI*(i/256f)-HALF_PI-.12), 60)-.1;
  50. }
  51. void ColorPicker() { 
  52.   cpImage = new PImage( width, height );
  53.   // draw colors 
  54.   for ( int i=0; i<width; i++ ) {
  55.     float nColorPercent = i / (float)width;
  56.     float rad = (-360 * nColorPercent) * (PI / 180);
  57.     int nR = (int)(cos(rad) * 127 + 128) << 16;
  58.     int nG = (int)(cos(rad + 2 * PI / 3) * 127 + 128) << 8;
  59.     int nB = (int)(cos(rad + 4 * PI / 3) * 127 + 128);
  60.     int nColor = nR | nG | nB;            
  61.     setGradient( i, 0, 1, height/2, 0xFFFFFF, nColor );
  62.     setGradient( i, (height/2), 1, height/2, nColor, 0x000000 );
  63.   }
  64. }
  65. void setGradient(int x, int y, float w, float h, int c1, int c2 ) {
  66.   float deltaR = red(c2) - red(c1);
  67.   float deltaG = green(c2) - green(c1);
  68.   float deltaB = blue(c2) - blue(c1);
  69.   for (int j = y; j<(y+h); j++)     
  70.     cpImage.set( x, j, color( red(c1)+(j-y)*(deltaR/h), green(c1)+(j-y)*(deltaG/h), blue(c1)+(j-y)*(deltaB/h) ) );
  71. }

el_dippo I get the same ~60fps with yours and ~170 with amnon's.
I think the most intensive part is getting the HSBs and the trick is to make LUTs as amnon pointed out.
btw you have to change  int hueIndex = (int) H*256; to  int hueIndex = int (H*256); or it will always return  0.
Nice color picker though! and thanks.

edit: or (int)(H*256) probably.
Using it @ v1.5.1 and got about 60 FPS.  ( amnon.owed's version)
However, if I'd change from default Java2D engine to P2D one, I net about 89 FPS!!!
You know, Processing v2.x uses OPENGL by default instead!
size(800, 600, P2D);

AFAIK it uses J2D by default and OPENGL for P2D. (edit: on android)
Unfortunately OPENGL is quite glitchy on the phone so I gave up on that... and it probably work on more devices this way.

It still counts as a good answer though, since this is not the android forum. ;)
A tip to spare our CPUs/GPUs/APUs:

Use noLoop(); in setup() {}, then create:  
void mouseMoved() {
  redraw();
}


Use noLoop(); in setup()
Yes, i already tried that, but it doesn't work (or i did something wrong). What's the case: When the mousebutton is pressed, a new image show up (in my case the colorpicker) with a animation. The trigger for mousePressed is inside of draw(), draw() is not called when noLoop() is active. So, i placed the mouse button trigger in void MousePressed(). After some testing i gave up, because the only way to show the colorpicker, is to move the mouse and press a mousebutton (simultaneous). Now the colorpicker shows up, but the animation is gone.

I also tried it with a lower framerate, with a framerate of 10, the program still response very good.

Btw, good tip about P2D, i didn't know that.

Greetings, Dippo



@Dippo, maybe you should use void mouseDragged() ?
@GoToLoop, will try it. btw is there a difference between redraw() and calling draw() ?
if performance really matters, you could make your own implementation of color(h,s,b).
not that i reccomend to do so, because its simpler to just use processings color() and also saves coding, but in some cases, and if you know how the code works, it might help.



Copy code
  1.   int HSB_to_RGB(float h, float s, float b){
  2.     // normalize if color mode != 1
  3. //    h /= this.g.colorModeX;  
  4. //    s /= this.g.colorModeY;  
  5. //    b /= this.g.colorModeZ;  
  6.     
  7.     // limit 0 < hsb > 1
  8.     if (h < 0) h = 0; else if(h>1) h = 1;
  9.     if (s < 0) s = 0; else if(s>1) s = 1;
  10.     if (b < 0) b = 0; else if(b>1) b = 1;
  11.     if (s == 0) {
  12.       int B = (int)(255*b);
  13.       return 0xFF000000 | B << 16 | B << 8 | B;
  14.     } else {
  15.  
  16.       int   H = (int)(h*=6);
  17.       float f = h - H;
  18.       float p = b * (1f - s);
  19.       float q = b * (1f - s * f);
  20.       float t = b * (1f - (s * (1f - f)));
  21.       
  22.       int R = 0;
  23.       int G = 0;
  24.       int B = 0;
  25.       SWITCH:
  26.       {
  27.         if(H==0){ R=(int)(b*255); G=(int)(t*255); B=(int)(p*255); break SWITCH; }
  28.         if(H==1){ R=(int)(q*255); G=(int)(b*255); B=(int)(p*255); break SWITCH; }
  29.         if(H==2){ R=(int)(p*255); G=(int)(b*255); B=(int)(t*255); break SWITCH; }
  30.         if(H==3){ R=(int)(p*255); G=(int)(q*255); B=(int)(b*255); break SWITCH; }
  31.         if(H==4){ R=(int)(t*255); G=(int)(p*255); B=(int)(b*255); break SWITCH; }
  32.         if(H==5){ R=(int)(b*255); G=(int)(p*255); B=(int)(q*255); break SWITCH; }
  33.       }
  34.       return 0xFF000000 | R << 16 | G << 8 | B;
  35.     }
  36.   }

now, using ammons code, there are three options to convert from hsb to rgb.

  • output.pixels[i] = color(H, S, B);          // processing version
  • output.pixels[i] = HSB_to_RGB(H, S, B);     // selfmade
  • output.pixels[i] = Color.HSBtoRGB(H, S, B); // java version
on my computer, the selfmade version made the framerate go from ~60 to ~70. not much, but something.
using P2D (usually faster for pixel-ops) it changes from ~84 to ~104.

also, consider to give OpenGL/OpenCL a try. i think andres has made some image-processing examples.
doing so, most likely will solve your performance problem.


Nice one thomas. I considered writing my own color(h,s,b) (for other reasons too) but thought it would probably make it slower that the original, so I never even tried! 

Now I definitely will... or will probably use your's. :)
Thanks.

edit: I get a ~50fps increase with your HSB_to_RGB on PC with the default renderer (on 2.0b8).

edit2: very nice. I was downsampling to 1/4 before, but with this I get ~30fps on android with downsampling to 1/2. Thanks again. Any ideas why the processing version is slower? 
So.... I combined amnon's version with my own modifiying the downsampled version during mousePressed and the original on mouseReleased, and I'm getting ~170fps on PC and an awesome ~50fps on android (with a fast enough update of the original on mouseReleased).

edit: This is slower on the PC (I think because of the scaling of the downsampled version). But it's faster on android! dunno why.

Any ideas why the processing version is slower?
here is processing's code for color(...)

https://github.com/processing/processing/blob/master/core/src/processing/core/PGraphics.java#L7178

- it takes a view method calls inbeteen to get  from color(h,s,b) to colorCalc(x, y, z, a).
- it is checking which colorMode is active (RGB, or HSB)
- it normalizes the color, doing a division. IMO it would be (noticeable) faster, to  precompute the inverse once, and then do the multiplication instead of the division.
- also the way you arrange things can sometimes make a difference in performance. Then in the end, you never know (or at least i dont :) ) what things are optimized by the compiler. So, the best optimizations might give different results on different devices.

but to get at the core-problem. pixel-operations is highly parallel process. so the consequence would be to use different threads/kernels, ideally one per pixel. doing this in java, using Threads might help here, but starting 1 thread for each pixel wont work :),  ... you could make 4 partitions of the image and run 4 threads then.
But doing this on graphics-hardware, using OpenGL would be the next logical thing, since a fragment shader runs for each fragment (pixel) the same routine at the same time, or at least tries to (there are usually not as many graphic's cores as pixels). An alternative and more general way would be using OpenCL, where, in short, the shader is called Kernel and the Fragment is called Workitem.



@GoToLoop, will try it. btw is there a difference between redraw() and calling draw() ?
If draw() is called forcibly, the screen is NOT refreshed when it reaches its end! 
It only works when Processing itself callbacks it. Function draw() is very faithful! 

What redraw() does is tell Processing that draw() is to be called later.
It doesn't make it happen immediately, but it will on next opportunity.
As noticed, redraw() is to be used when noLoop() is active or looping is true.
No effect otherwise. Just naught!
Here's my refactoring from the codes above:  
Copy code
    /**
     * Selective Hue Shift (v3.65)
     * by  Zahand (2013/Mar)
     * mod Amnon.Owed & Thomas.Diewald
     * refactory GoToLoop
     * 
     * http://forum.processing.org/topic/
     * any-ways-to-further-optimize-this-selective-hue-shift
     */
    
    //import java.awt.Color;
    
    PImage input, output;
    final static float[] hueLUT = new float[256];
    float[] HL, SL, BL;
    short[] hueIdx;
    
    final static boolean isLooping = false;
    final static short FPS = 200;
    
    final static String fileName = 
    //"http://imgs.mi9.com/uploads/cartoon/506/" +
    //"colorful-balloons-cartoon-wallpaper_800x600_7775.jpg";
    
    //"http://farm9.staticflickr.com/8101/" + 
    "8532786267_d653ec298f_b.jpg";
    
    void setup() {
    
      input  = loadImage(fileName);
      output = input.get();
    
      size(input.width, input.height, P2D);
      colorMode(HSB, 1);
      frameRate(FPS);
    
      if (!isLooping)   noLoop();
    
      initLUTs();
    }
    
    void draw() {
    
      shiftHue( map(mouseX, 0, width, -3, 3) );
      image(output, 0, 0);
      frame.setTitle( round(frameRate) + " fps" );
    }
    
    void mouseMoved() {
    
      redraw();
    }
    
    void shiftHue(float hueShift) {
    
      for (int i=output.pixels.length; i!=0;) {
    
        final float lut = hueLUT[ hueIdx[--i] ];
    
        if (lut > 0) {
          float H = hueShift * lut + HL[i];
    
          if (H < 0)        H -= (int) H - 1;
          else if (H > 1)   H -= (int) H;
    
          //output.pixels[i] = color( H, SL[i], BL[i] );
          //output.pixels[i] = Color.HSBtoRGB( H, SL[i], BL[i] );
          output.pixels[i] = HSB_to_RGB( H, SL[i], BL[i] );
        }
    
        else output.pixels[i] = input.pixels[i];
      }
    
      output.updatePixels();
    }
    
    void initLUTs() {
    
      final int len = input.pixels.length;
      final int lut = hueLUT.length;
    
      for ( int i=lut; i!=0; hueLUT[--i] = 
      1.1 * pow( sin(PI*i/lut - HALF_PI - .12), 60 ) - .1 );
    
      HL = new float[len];
      SL = new float[len];
      BL = new float[len];
      hueIdx = new short[len];
    
      for (int i=len; i!=0;) {
        color c = input.pixels[--i];
    
        HL[i] = hue(c);
        SL[i] = saturation(c);
        BL[i] = brightness(c);
    
        hueIdx[i] = (short) (HL[i] * lut);
      }
    }
    
    int HSB_to_RGB(float h, float s, float b) {
    
      final int C = (int) (b * 0xFF);
    
      if (s == 0)   return #000000 | C << 020 | C << 010 | C;
    
      final int   H = (int) (h *= 6);
      final float f = h - H;
    
      final int p = (int) ( C * ( 1 - s ) );
      final int q = (int) ( C * ( 1 - s * f ) );
      final int t = (int) ( C * ( 1 - s * (1 - f)) );
    
      final int R, G, B;
    
      switch (H) {
      case 0:
        R = C;
        G = t;
        B = p;
        break;
    
      case 1:
        R = q;
        G = C;
        B = p;
        break;
    
      case 2:
        R = p;
        G = C;
        B = t;
        break;
    
      case 3:
        R = p;
        G = q;
        B = C;
        break;
    
      case 4:
        R = t;
        G = p;
        B = C;
        break;
    
      default:
        R = C;
        G = p;
        B = q;
      }
    
      return #000000 | R << 020 | G << 010 | B;
    }
    

@GoToLoop
you should check your version of  HSB_to_RGB(), it generates wrong colors. Also you didnt care if h,s,b is within [0,1].

another thing:

Copy code
  1.       if (H<-1) { H += 2; }
  2.       else if (H<0) { H += 1; }
  3.       else if (H>2) { H -= 2; }
  4.       else if (H>1) { H -= 1; }
i guess this should "rotate" the hue (and also clamping it to [0,1]). If so, then it should be:

Copy code
  1.            if( H<0 ) H-=(int)H-1;
  2.       else if( H>1 ) H-=(int)H;
this handles all cases, and also makes the clamping within HSB_to_RGB() obsolete.



Hello Thomas!

I've put myself as responsible on refactoring the code. It wasn't me who did the H rotating thing!
It's zahand's original code! Actually, I've modified very little of it!
Focused more on readability aspect! Also on removing redundancy and performance attempts! 

 * Selective Hue Shift (v3.65)
 * by  Zahand (2013/Mar)
 * mod Amnon.Owed & Thomas.Diewald
 * refactory GoToLoop

Anyways, thx a lot for the compact fixed version!