Loading...
Logo
Processing Forum
Hi, 

I was playing with this code. Basically a ellipse that search and changes grey pixels to red pixels. I got some questions in the code itself, but the main is: why moving line 127 to inside draw() i only see one object? As commented in the code i think i'm calling image to many times which i assume is making things slower than needed. Press 'z' to dismiss zoom frame. (it's hard to see the pixels changes without zoom).
      Another strange thing. If i increase the size() to 1000x1000 the sketch goes much slower. Eveb with the same amount of followers created...
      What am i missing?
thanks.



Copy code
  1. Folower[] f = new Folower[10];
  2. // drawing folower in layer1
  3. PGraphics layer1;
  4. //layer0 is bg
  5. PGraphics layer0;
  6. PFont font;
  7. boolean show = true;// show or not the folowers press s to tooglle
  8. boolean zoom = true;// show or not zoom under mouse press z

  9. void setup()
  10. {
  11.   size(500, 500, JAVA2D);
  12.   smooth();
  13.   layer1 = createGraphics(width, height, JAVA2D);
  14.   layer0 = createGraphics(width, height, JAVA2D);
  15.   
  16.   // do i need beginDraw here? Or Why i don't need it :)
  17.   layer0.loadPixels();
  18.   for (int i=0; i < layer0.pixels.length; i++)
  19.   {
  20.     int luck = (int) random(0, 5);
  21.     layer0.pixels[i] = (luck == 0)? color(200):color(255);
  22.   }
  23.   layer0.updatePixels();
  24.   
  25.   //random initial position and target
  26.   for (int i=0; i < f.length; i++)
  27.   {
  28.     PVector posi = new PVector (random(width), random(height));
  29.     PVector tar = new PVector (random(width), random(height));
  30.     f[i] = new Folower(posi, tar);
  31.   }
  32.   font = createFont("arial", 10);

  33. }

  34. void draw()
  35. {
  36.   image(layer0, 0, 0);
  37.   
  38.   if(show)
  39.   {
  40.     for (int i=0; i < f.length; i++)
  41.   {
  42.     f[i].display();
  43.   }
  44.   }
  45.   else
  46.   {
  47.      for (int i=0; i < f.length; i++)
  48.   {
  49.     f[i].update();
  50.   }
  51.   }
  52.   
  53.   // if i call this here i can only see one follower
  54.   //they all exixts and acts in the bg though..
  55.   //image(layer1, 0, 0, width, height);
  56.     
  57.     if(zoom)
  58.     copy(mouseX-25, mouseY-25, 50,50, mouseX-75, mouseY-75, 150,150);
  59. }




  60. void keyTyped()

  61. {
  62.   if(key == 'z' || key == 'Z')
  63.   zoom = !zoom;
  64.   
  65.   if(key == 's' || key == 's')
  66.   show= !show;
  67. }





  68. ////// Folower class

  69. class Folower {

  70.   PVector pos; 
  71.   PVector target;
  72.   float curvex, curvey;
  73.   int bellySize = 10;


  74.   Folower(PVector _pos, PVector _target)
  75.   {
  76.     pos = _pos;
  77.     target = _target;
  78.     curvex = random (0.5, 3);
  79.     curvey = random (0.5, 3);
  80.   } 



  81.   void setTarget(PVector t)
  82.   {
  83.     target = t;
  84.     curvex = random (1, 3);
  85.     curvey = random (1, 3);
  86.   }




  87.   void display()
  88.   {
  89.     update();
  90.     layer1.beginDraw();
  91.     layer1.background(0, 0);
  92.     layer1.noFill();
  93.     layer1.ellipse(target.x, target.y, 3, 3);
  94.     layer1.fill(255, 0, 0, 30);
  95.     layer1.ellipse( pos.x, pos.y, bellySize, bellySize);
  96.     layer1.point( pos.x, pos.y);
  97.     layer1.textFont(font, 10);
  98.     layer1.fill(0);
  99.     layer1.text(nf(bellySize-10, 2), pos.x, pos.y + 10 + bellySize/2);
  100.     layer1.endDraw();
  101.     //* if i move this to draw, wont work,*/
  102.     //* but i think it is dumb here calling same thing to much times...*/
  103.     image(layer1, 0, 0, width, height);
  104.   }
  105.   
  106.   void update()
  107.   {
  108.     if (!found())
  109.     {
  110.       pos.x = pos.x + (((target.x - pos.x)*curvex)*0.2) ;
  111.       pos.y = pos.y + (((target.y - pos.y)*curvey)*0.2) ;
  112.     }
  113.     else
  114.     {
  115.       if (isFood())
  116.       {
  117.         bellySize++;
  118.         layer0.beginDraw();
  119.         layer0.loadPixels();
  120.         layer0.pixels[int(pos.y*width+pos.x)] = color(255,0,0);
  121.         layer0.updatePixels();
  122.         
  123.         layer0.endDraw();
  124.       }
  125.       PVector newTarget = new PVector(random(0, width-1), random(0, height-1));
  126.       setTarget(newTarget);
  127.     }
  128.   }
  129.   
  130.   

  131.   boolean isFood()
  132.   {

  133.     color c = layer0.pixels[int(pos.y* width + pos.x)]; //layer0.get((int)pos.x, (int)pos.y);
  134.     return (c >> 16 & 0xFF) == 200;
  135.   }



  136.   boolean found()
  137.   {

  138.     return pos.dist(target) < 0.1;
  139.   }
  140. }//eoc 

Replies(12)

Without running your code (no time right now), I wonder why you call background() for each follower. I see it is with full transparency, so I am not sure of the purpose of the call. It won't reset the transparency of the PGraphics, and even if it would, it would ruin the drawings of the previous followers in the same frame... So, you shouldn't need beginDraw in update() too.

To answer a question in the code, loadPixels doesn't need beginDraw, as acting on the pixels array isn't really drawing.

The bigger the sketch, the slower, because that's more pixels to update and to copy to the sketch area. The number of followers isn't really relevant here, as you update only a small part of the layer.
Note that loadPixels can be quite slow, here, to update only one pixel, you can use set() or point(), it might be faster... You can also try and do a beginDraw only once (display() makes it called twice). Or perhaps even do it in draw(), for all follower updates / displays. Not sure if it does a difference in Java2D mode, but worth a try.

" why moving line 127 to inside draw() i only see one object?"
Where do you put it in draw()?
Hi PhiLho, weird call isn't it? background(0,0), that is the way i found to "erase" previous drawn followers, if i take out this line the drawn is not erased at the beginning of new frame, same as if i took out the "background(0)" at the beginning of draw() in a regular sketch. If you can try, you will see... The fact is, it is doing something.

"loadPixels doesn't need beginDraw,"

The strange thing is if i take out the beginDraw()/endDrae() from update code (as below), the pixels are not changed to red... even though it is a pixel operation... But in setup those were not needed...

Copy code
  1.         layer0.beginDraw();
  2.         layer0.loadPixels();
  3.         layer0.pixels[int(pos.y*width+pos.x)] = color(255,0,0);
  4.         layer0.updatePixels();
  5.        layer0.endDraw();
I aways understood that direct access to pixels should be faster than set or point. I tried both but could not note a significant difference.

" Where do you put it in draw()? "
I tried it at the end of draw, just before the "zoom" copy() at line 58 above it is there commented out.

thanks

" that is the way i found to "erase" previous drawn followers"
Yes, but notice I wrote " for each follower".
If you erase the graphics on each follower, but move the image() call to draw(), then indeed you will draw only the last one. But if you keep it in the display() function, since they are drawn with transparency, you keep them all.
Or something like that...

" I aways understood that direct access to pixels should be faster than set or point"
It is true if you change lot of pixels at once, or all of them. But for one pixel to change, here you render the PGraphics to a pixels array, then render back the whole array. Lot of work to change only one pixel...
"Or something like that..." :)
So this is a correct approach to draw in layers from a class? Calling a background(0,0)?

I'll change it to point.

What about the beginDraw() being need with pixels? That is weird isn't it?

Thanks PhiLho


If you want to improve speed, you should erase the background in draw only once, then each follower draws on the PGraphics, then you can call image() in draw().
Hummm, i see so I moved layer1.backgrond(0,0); to draw() and it worked. Still looking strange to me calling a transparent background to erase things... But indeed it worked. 
Thanks once more :)

With regard to speed, I made a few changes/optimizations...

Adapted Code
Copy code
  1. Follower[] f = new Follower[100];
  2. PImage layer0;
  3. PGraphics layer1;
  4. boolean show = true;
  5. boolean zoom = true;
  6. color food = color(200);
  7. color notFood = color(255);
  8. color leftovers = color(255, 0, 0);
  9.  
  10. void setup() {
  11.   size(1000, 1000, JAVA2D);
  12.   layer0 = createImage(width, height, RGB);
  13.   layer1 = createGraphics(width, height, JAVA2D);
  14.  
  15.   layer0.loadPixels();
  16.   for (int i=0; i < layer0.pixels.length; i++) {
  17.     int luck = (int) random(0, 5);
  18.     layer0.pixels[i] = (luck == 0)? food:notFood;
  19.   }
  20.   layer0.updatePixels();
  21.  
  22.   for (int i=0; i < f.length; i++) {
  23.     PVector posi = new PVector (random(width), random(height));
  24.     PVector tar = new PVector (random(width), random(height));
  25.     f[i] = new Follower(posi, tar);
  26.   }
  27.  
  28.   layer1.beginDraw();
  29.   layer1.textFont(createFont("arial", 10));
  30.   layer1.smooth();
  31.   layer1.endDraw();
  32. }
  33.  
  34. void draw() {
  35.   layer0.loadPixels();
  36.   for (Follower folly : f) {
  37.     folly.update();
  38.   }
  39.   layer0.updatePixels();
  40.   image(layer0, 0, 0);
  41.  
  42.   layer1.beginDraw();
  43.   layer1.background(0, 0);
  44.   if (show) {
  45.     for (Follower folly : f) {
  46.       folly.display();
  47.     }
  48.   }
  49.   layer1.endDraw();
  50.   image(layer1, 0, 0);
  51.  
  52.   if (zoom) {
  53.     copy(mouseX-25, mouseY-25, 50, 50, mouseX-75, mouseY-75, 150, 150);
  54.   }
  55. }
  56.  
  57. void keyPressed() {
  58.   if (key == 'z' || key == 'Z') { zoom = !zoom; }
  59.   if (key == 's' || key == 's') { show= !show; }
  60. }
  61.  
  62. class Follower {
  63.   PVector pos, target;
  64.   float curvex, curvey;
  65.   int bellySize = 10;
  66.  
  67.   Follower(PVector _pos, PVector _target) {
  68.     pos = _pos;
  69.     target = _target;
  70.     curvex = random (0.5, 3);
  71.     curvey = random (0.5, 3);
  72.   }
  73.  
  74.   void update() {
  75.     if (!found()) {
  76.       pos.x = pos.x + (((target.x - pos.x)*curvex)*0.2) ;
  77.       pos.y = pos.y + (((target.y - pos.y)*curvey)*0.2) ;
  78.     } else {
  79.       int index = min(int(pos.y*width+pos.x), layer0.pixels.length-1);
  80.       if (isFood(index)) {
  81.         bellySize++;
  82.         layer0.pixels[index] = leftovers;
  83.       }
  84.       target.set(random(0, width-1), random(0, height-1), 0);
  85.       curvex = random (1, 3);
  86.       curvey = random (1, 3);
  87.     }
  88.   }
  89.  
  90.   void display() {
  91.     layer1.noFill();
  92.     layer1.ellipse(target.x, target.y, 3, 3);
  93.     layer1.fill(255, 0, 0, 30);
  94.     layer1.ellipse(pos.x, pos.y, bellySize, bellySize);
  95.     layer1.point(pos.x, pos.y);
  96.     layer1.fill(0);
  97.     layer1.text(nf(bellySize-10, 2), pos.x, pos.y + 10 + bellySize/2);
  98.   }
  99.  
  100.   boolean isFood(int index) {
  101.     return layer0.pixels[index] == food;
  102.   }
  103.  
  104.   boolean found() {
  105.     return pos.dist(target) < 0.1;
  106.   }
  107. }
Hi amnon, that was very kind, thanks. It is muuuuuch faster! Amazing. If i understood what you did, the main change would be calling begin/endDraw() and load/updatePixels() just once at draw() instead of once for each instance of Follower, is that right? I believe also calling smooth only for layer1 should make some difference as well... I mean the code is much more elegant and easy to read, but i'm trying to understand the more important stuff you did speed related.
      Why did you choose a PImage instead of a PGraphics for layer0? 
      Moving isfood() to inside isfound() is faster also i think... Less testing.
Well
thanks for that
:) 
Indeed the begin/endDraw outside the for loops has the biggest effect. Since the PGraphics functionality isn't used for layer0 I switched to PImage, because this also allows to remove the begin/endDraw statements. Calling smooth() on layer1 actually improves the quality, because the global smooth doesn't affect drawing inside a PGraphics.

There are some other changes with a relatively smaller impact: set the textfont once, define the colors globally once, in the isFood() method directly test two colors (ints) against each other without any other calculations, set the target on the same PVector instead of creating a new PVector. But as mentioned, the main impact on fps is the begin/endDraw change.

There are still possibilities for further optimization. Maybe I will give it another shot at a later time again.
More optimizations...

The most important performance increase comes from switching the Follower's display() method to images completely. With a target and position/bellySize image respectively. Also the whole layer1 is dropped, because it's not really needed.

Adapted Code
Copy code
  1. boolean show = true;
  2. boolean zoom = true;
  3. int numFollowers = 1000;
  4. int maxBellySize = 50;
  5. float foodChance = 0.2;
  6.  
  7. Follower[] f = new Follower[numFollowers];
  8. PImage gFood;
  9. PGraphics gTarget;
  10. PGraphics[] gFollower = new PGraphics[maxBellySize+1];
  11. color food = color(200);
  12. color notFood = color(255);
  13. color leftovers = color(255, 0, 0);
  14.  
  15. void setup() {
  16.   size(1000, 1000, JAVA2D);
  17.   imageMode(CENTER);
  18.  
  19.   for (int i=0; i<f.length; i++) {
  20.     f[i] = new Follower();
  21.   }
  22.  
  23.   gFood = createImage(width, height, RGB);
  24.   gFood.loadPixels();
  25.   for (int i=0; i<gFood.pixels.length; i++) {
  26.     gFood.pixels[i] = (random(1) < foodChance) ? food : notFood;
  27.   }
  28.   gFood.updatePixels();
  29.  
  30.   gTarget = createGraphics(8, 8, JAVA2D);
  31.   gTarget.beginDraw();
  32.   gTarget.smooth();
  33.   gTarget.noFill();
  34.   gTarget.ellipse(gTarget.width/2, gTarget.height/2, 3, 3);
  35.   gTarget.endDraw();
  36.  
  37.   for (int i=0; i<gFollower.length; i++) {
  38.     gFollower[i] = createGraphics(35+i, 35+i, JAVA2D);
  39.     int bellySize = 10+i;
  40.     gFollower[i].beginDraw();
  41.     gFollower[i].textFont(createFont("arial", 10));
  42.     gFollower[i].smooth();
  43.     gFollower[i].translate(gFollower[i].width/2, gFollower[i].height/2);
  44.     gFollower[i].fill(255, 0, 0, 30);
  45.     gFollower[i].ellipse(0, 0, bellySize, bellySize);
  46.     gFollower[i].point(0, 0);
  47.     gFollower[i].fill(0);
  48.     gFollower[i].text(nf(bellySize-10, 2), 0, 10 + bellySize/2);
  49.     gFollower[i].endDraw();
  50.   }
  51. }
  52.  
  53. void draw() {
  54.   gFood.loadPixels();
  55.   for (Follower folly : f) {
  56.     folly.update();
  57.   }
  58.   gFood.updatePixels();
  59.   image(gFood, width/2, height/2);
  60.  
  61.   if (show) {
  62.     for (Follower folly : f) {
  63.       folly.display();
  64.     }
  65.   }
  66.  
  67.   if (zoom) {
  68.     copy(mouseX-50, mouseY-50, 100, 100, mouseX-150, mouseY-150, 300, 300);
  69.   }
  70. }
  71.  
  72. void keyPressed() {
  73.   if (key == 'z' || key == 'Z') { zoom = !zoom; }
  74.   if (key == 's' || key == 's') { show= !show; }
  75. }
  76.  
  77. class Follower {
  78.   PVector pos, target;
  79.   float curvex, curvey;
  80.   int bellySize;
  81.  
  82.   Follower() {
  83.     pos = new PVector(random(width), random(height));
  84.     target = new PVector(random(width), random(height));
  85.     curvex = random (0.5, 3);
  86.     curvey = random (0.5, 3);
  87.     bellySize = 10;
  88.   }
  89.  
  90.   void update() {
  91.     if (!found()) {
  92.       pos.x = pos.x + (((target.x - pos.x)*curvex)*0.2) ;
  93.       pos.y = pos.y + (((target.y - pos.y)*curvey)*0.2) ;
  94.     } else {
  95.       int index = min(int(pos.y*width+pos.x), gFood.pixels.length-1);
  96.       if (isFood(index)) {
  97.         if (bellySize-10 < maxBellySize) {
  98.           bellySize++;
  99.         }
  100.         gFood.pixels[index] = leftovers;
  101.       }
  102.       target.set(random(width), random(height), 0);
  103.       curvex = random (1, 3);
  104.       curvey = random (1, 3);
  105.     }
  106.   }
  107.  
  108.   void display() {
  109.     image(gFollower[min(bellySize-10, gFollower.length-1)], pos.x, pos.y);
  110.     image(gTarget, target.x, target.y);
  111.   }
  112.  
  113.   boolean isFood(int index) {
  114.     return gFood.pixels[index] == food;
  115.   }
  116.  
  117.   boolean found() {
  118.     return pos.dist(target) < 0.1;
  119.   }
  120. }
Hi amnon, that's really amazing, even much faster.I'm learning a lot with this. Thanks once more    I would think that the transparency would not work drawing everything in setup(). Well it works :) But you have to constrain the bellySize with this approach, right? Not a problem...

      Just to let you know, the first idea behind this was a "digital ager" for pictures. This was first studies for the moving and colour testing for my digital fungus... (the name follower is bad). I thought, looking at some old paper photos, that the ageing process was part of the photo, an old image with some fungus that "eats" determinate pigment and not others, shifting the hue and creating artifacts, the fading colours... all this adds to the image it self. And with digital pictures, well... they never get old.
      Of course the pixel changing to red is not what a fungus should do... As i said this is for testing movement... 
      The cool would be if based in the photo creation date it would appear to user more or less aged :) As youu can see, I'm so far...
Here a picture of an old try (left is original):

 
This was an attempt i made some time ago, but it worked especially well for this image.

Well thanks for all your help, it really got me ahead. 
cheers!

Ah cool, that aging effect seems to work pretty nice.

The bellySize is constrained, because I didn't think you would need bigger ones (whatever bellySize is anyway ). However of course it's possible to use the original display method for those infrequent bellySizes that go above the max. That way most of it is optimized, but "in the unlikely case that it goes over the max" it would still be possible to go above and beyond. An adaptation of the code like that would mean optimization up to a point plus no constraint.