Buggy color() function?

edited April 2014 in Questions about Code

Hello all,

I think I ran into a bug with the color primitive or color() function. When I pass a primitive color value into my fill() the triangle's opacity freaks out when they are removed from the sketch. I can avoid this if I just declare three ints and then pass those ints as the RGB values of my fill. You are welcome to take a look at my code. I have commented out the lines that solve the issue. (Please click in the Processing sketch to see anything happen)

ArrayList<Particle> particles;

void setup() {
  size(640,360);
  particles = new ArrayList<Particle>();
}

void draw() {
  background(255);
  if (mousePressed == true) {
  particles.add(new Particle(new PVector(mouseX,mouseY)));
  }
  // Looping through backwards to delete
  for (int i = particles.size()-1; i >= 0; i--) {
    Particle p = particles.get(i);
    p.run();
    if (p.isDead()) {
      particles.remove(i);
    }
  }
}

class Particle {
  PVector location;
  PVector velocity;
  PVector acceleration;
  float lifespan;
  float radius = 12;
  float xl = radius*cos(PI/6);
  float yl = radius*sin(PI/6);
  int a;
  int b;
  int c;
  color C;

  Particle(PVector l) {
    acceleration = new PVector(0, 0.05);
    velocity = new PVector(random(-1, 1), random(-2, 0));
    location = l.get();
    lifespan = 255.0;
    a = int(random(0,200));
    b = int(random(200,255));
    c = int(random(0,255));
    C = color(a,b,c);
  }

  void run() {
    update();
    display();
  }

  // Method to update location
  void update() {
    velocity.add(acceleration);
    location.add(velocity);
    lifespan -= 2.0;
  }

  // Method to display
  void display() {
    noStroke();
    fill(C, lifespan);
    //fill(a,b,c, lifespan);
    triangle(location.x-xl, location.y+yl, location.x+xl, location.y+yl, location.x, location.y-radius);
  }

  // Is the particle still useful?
  boolean isDead() {
    if (lifespan < 0.0) {
      return true;
    } 
    else {
      return false;
    }
  }
}
Tagged:

Answers

  • Either declare or cast lifespan as a whole type? ^#(^

  • I think I ran into a bug with the color primitive or color() function.

    99 times out of 100, if you think you found a bug, what's really going on is you've misunderstood the API or your own code.

    I don't see what your code is doing wrong. What exactly do you mean when you say the alpha value "freaks out"?

    Can you provide a sketch that just shows a single triangle with the incorrect alpha value? You don't need any of that extra logic for moving or clicking.

    Hint: I will say that your code will draw triangles with a negative opacity, which I'm not sure is what you want.

  • I have updated my code below to better communicate what I mean by "freaks out." The triangles flash when the particle returns false for the "Is the particle still useful?" test. When I change over from using color() to ints it seems to work fine.

    ArrayList<Particle> particles;
    
    void setup() {
      size(640,360);
      particles = new ArrayList<Particle>();
    }
    
    void draw() {
      background(255);
      if (mousePressed == false) {
      particles.add(new Particle(new PVector(width/2,height/6)));
      }
      // Looping through backwards to delete
      for (int i = particles.size()-1; i >= 0; i--) {
        Particle p = particles.get(i);
        p.run();
        if (p.isDead()) {
          particles.remove(i);
        }
      }
    }
    
    class Particle {
      PVector location;
      PVector velocity;
      PVector acceleration;
      float lifespan;
      float radius = 12;
      float xl = radius*cos(PI/6);
      float yl = radius*sin(PI/6);
      int a;
      int b;
      int c;
      color C;
    
      Particle(PVector l) {
        acceleration = new PVector(0, 0.05);
        velocity = new PVector(random(-1, 1), random(-2, 0));
        location = l.get();
        lifespan = 255.0;
        a = int(random(0,200));
        b = int(random(200,255));
        c = int(random(0,255));
        C = color(a,b,c);
      }
    
      void run() {
        update();
        display();
      }
    
      // Method to update location
      void update() {
        velocity.add(acceleration);
        location.add(velocity);
        lifespan -= 2.0;
      }
    
      // Method to display
      void display() {
        noStroke();
        fill(C, lifespan);
        //fill(a,b,c, lifespan);
        triangle(location.x-xl, location.y+yl, location.x+xl, location.y+yl, location.x, location.y-radius);
      }
    
      // Is the particle still useful?
      boolean isDead() {
        if (lifespan < 0.0) {
          return true;
        } 
        else {
          return false;
        }
      }
    }
    
  • *Oops didn't mean to "accept" the answer just yet.

  • This is an interesting question.

    The main problem is caused by you giving the alpha value a negative number. Here is the small example I was talking about:

    float alpha = 255;
    float stepSize = 2;
    
    void setup() {
      size(200, 100);
    }
    
    void draw() {
      background(255);
    
      if (mousePressed) {
        alpha -= stepSize;
      }
    
      //green
      fill(0, 255, 0, alpha);
      ellipse(150, 50, 100, 100);
    
      //blue
      color c = color(0, 0, 255);
      fill(c, alpha);
      ellipse(50, 50, 100, 100);
    }
    

    This demonstrates the difference in behavior between the two fill() functions.

    Digging through the Processing code, you can see that the green fill (the one with separate r,g,b values) eventually leads to an automatic cap in the PGraphics class:

    protected void colorCalc(float x, float y, float z, float a) {
        if (x > colorModeX) x = colorModeX;
        if (y > colorModeY) y = colorModeY;
        if (z > colorModeZ) z = colorModeZ;
        if (a > colorModeA) a = colorModeA;
    
        if (x < 0) x = 0;
        if (y < 0) y = 0;
        if (z < 0) z = 0;
        if (a < 0) a = 0;
    

    Whereas the blue fill (the one with a color and an alpha) eventually leads to something a little more convoluted:

    protected void colorCalcARGB(int argb, float alpha) {
        if (alpha == colorModeA) {
              calcAi = (argb >> 24) & 0xff;
              calcColor = argb;
            } else {
              calcAi = (int) (((argb >> 24) & 0xff) * (alpha / colorModeA));
              calcColor = (calcAi << 24) | (argb & 0xFFFFFF);
            }
            calcRi = (argb >> 16) & 0xff;
            calcGi = (argb >> 8) & 0xff;
            calcBi = argb & 0xff;
            calcA = calcAi / 255.0f;
    

    Basically this uses bit shifting to unpack the color variable and recalculate the alpha. And interestingly enough, this function has this comment:

    //Note, no need for a bounds check since it's a 32 bit number.

    In other words, I think you might have found the 1/100 case where this is actually a bug!

    The fix for it on your end is to just not feed the fill() function a negative number!

    //blue
      color c = color(0, 0, 255);
      fill(c, alpha < 0 ? 0 : alpha);
      ellipse(50, 50, 100, 100);
    
  • Btw, I reported this on GitHub: https://github.com/processing/processing/issues/2439 Maybe we'll get an answer as to whether this is a bug or intended behavior.

  • Great! Thanks for the in depth response! I was quite puzzled myself.

  • Turns out it's being fixed for version 2.1.2. Great catch.

    https://github.com/processing/processing/issues/2439

    Kinda fanboying over here that Ben Fry just responded to me on GitHub... :D

  • Haha yeah he's a cool guy. He came to campus to give a talk and I was really inspired by his work.

Sign In or Register to comment.