Overlayed Transparent Shapes Never Become Fully Opaque

https://editor.p5js.org/kweso/sketches/z1uaEDvTk
Why do the overlaying circles in the bg never become the full color of the circle in the middle?
Very low alpha values (<5) even change the saturation of my color…
Is there anything for me to fix this?
Tyvm!

1 Like

It’s hard to say “why” but I can show you how it happens. Let’s simplify to greyscale and you want to paint the background 0 with fill 100 with alpha 5%. You can simulate what happens like this

function setup() {
  noCanvas();
  let v = 0;
  for (let i = 0; i < 200; i++) {
    let V = v * 0.95 + 100 * 0.05;
    V = floor(V)
    console.log(V)
    v = V;
  }
}

I added floor because I’m assuming color depth is 8bit (converted to integer). If you run the code, it will be stuck around 81 because of this rounding (actually I’m not sure if it’s floor or round, but even with round it will be stuck at 91) and won’t reach 100 even if you run it forever.

How can we solve this issue? I cannot think of an elegant solution but hopefully someone continues :slight_smile:

3 Likes

Thank you for your reply!
Yes, it seems the “floor” is the Problem. Maybe it would work better if you could work in 16bit color space.
Anyway… Could you explain, where the 95% is coming from?**

Edit: ** I see: You have to reduce the value of the existing color if you draw over it…

95% is arbitrary. Because you set alpha to <5 (which is ~2% of 255), I thought setting 95% / 5% would demonstrate the artifact.

It’s not 8bit vs 16bit but I guess you need floating point for the canvas. I’m sure with WebGL you can achieve it but not sure how with 2D canvas. But even with floating point depth, in theory you need infinite iteration to have the same color :exploding_head: You can of course increase the alpha as you start drawing, but the effect may become harsh. Maybe you can set the transparent circles’ color a bit brighter so it ends up with similar color to the foreground circle. I think there are different ways to solve it and you have to be creative :slight_smile:

1 Like

I see what you mean. And just out of curiosity I will try what happens in WebGL.
For my current task though, I have an other idea.
My project is simply to take the “loudest” frequency(-band) from the mic at every frame and map that in a polar coordinate system:
p5.js Web Editor (has a lot of issues still, but one step after an other :wink: )
My new approach will be heatmap.js . Will come back with the results. If they are acceptable :wink:

Would this help?:

  blendMode(ADD);
  ellipse(random(width * 0.5) + width * 0.25, random(height * 0.5) + height * 0.25, width * 0.3, height * 0.3);
  blendMode(BLEND);

See p5.js Reference: blendMode();

EDIT (April 21, 2021):

Optionally, you can do this to soften the effect:

  if (frameCount % 5 == 0) { // vary this to vary the effect
      blendMode(ADD);
  }
  ellipse(random(width * 0.5) + width * 0.25, random(height * 0.5) + height * 0.25, width * 0.3, height * 0.3);
  blendMode(BLEND);
1 Like

Thx! I did play with different blendmodes. Didn’t give me what I hoped for.
Will get back to you if and when the heatmap thing is working…
Laters…

Try this in the draw() function:

  blendMode(LIGHTEST);
  let amt = 0.005; // Vary this to control the rate of progress
  lc = lerpColor(color(get(width / 4, height / 4)), color(12, 123, 234), amt);
  fill(ceil(red(lc)), ceil(green(lc)), ceil(blue(lc)));
  ellipse(random(width * 0.5) + width * 0.25, random(height * 0.5) + height * 0.25, width * 0.3, height * 0.3);
  blendMode(BLEND);

It does eventually resolve to color(12, 123, 234).

EDIT(April 22, 2021):

However, the above is troublesome in that within each frame, the progress of the color transition relies heavily upon the rounding up of each color component from the result of the color interpolation. The following maintains the color components as floating point numbers, and rounds these up temporarily only during the process of the setting of the fill color. No alpha component is used.

let font;
let circleColor, bgColor, sampleColor;
let textOverlay;
let lerpAmt = 0.005; // controls rate of transition
let lerpR = 0.0;
let lerpG = 0.0;
let lerpB = 0.0;
function preload() {
  font = loadFont('Barlow-Light.ttf');
}

function setup() {
  createCanvas(500, 500);
  circleColor = color(12, 123, 234);
  bgColor = color(0, 0, 0);
  noStroke();

  textOverlay = createGraphics(width, 60);
  textOverlay.textFont(font);
  textOverlay.textSize(60);
  textOverlay.textAlign(CENTER, CENTER);
  
  background(bgColor);
  fill(circleColor);
  ellipse(width * 0.5, height * 0.5, width * 0.3, height * 0.3);
}

function draw() {
  // prepare and draw ellipse
  blendMode(LIGHTEST); 
  lerpR = lerp(lerpR, red(circleColor), lerpAmt);
  lerpG = lerp(lerpG, green(circleColor), lerpAmt);
  lerpB = lerp(lerpB, blue(circleColor), lerpAmt);
  fill(round(lerpR), round(lerpG), round(lerpB));
  ellipse(random(width * 0.5) + width * 0.25, random(height * 0.5) + height * 0.25, width * 0.3, height * 0.3);
  blendMode(BLEND);
  
  // prepare and draw overlay
  textOverlay.clear();
  textOverlay.fill(0, 50);
  textOverlay.rect(0, 0, width, 60);
  textOverlay.fill(255);
  textOverlay.text(frameCount, width * 0.5, 20);
  image(textOverlay, 0, height * 0.75);
 
  // check for match of colors
  sampleColor = get(width / 4, height/ 4);
  if (red(sampleColor) == red(circleColor) &&
      green(sampleColor) == green(circleColor) &&
      blue(sampleColor) == blue(circleColor)) {
    noLoop();
  }
}

Final frame: