processing / processing-website

Repository for the processing.org website
https://processing.org
GNU General Public License v2.0
68 stars 95 forks source link

blendMode BLEND is wrong #386

Open jbeda opened 5 years ago

jbeda commented 5 years ago

Issue description

The docs state:

BLEND - linear interpolation of colours: C = A*factor + B. This is the default blending mode.

But linear interpolation would be C = A*factor + B*(1-factor). As documented it is more like ADD without clamping.

URL(s) of affected page(s)

https://processing.org/reference/blendMode_.html https://processing.org/reference/blend_.html

Proposed fix

BLEND - linear interpolation of colours: C = A*factor + B*(1-factor). This is the default blending mode.
REAS commented 5 years ago

I poked through the source for this in PImage.java and I wasn't able to reach a conclusion. I'm not sure if the language is wrong or the math. @benfry, what do you think?

  /**
   * Blend
   * O = S
   */
  private static int blend_blend(int dst, int src) {
    int a = src >>> 24;

    int s_a = a + (a >= 0x7F ? 1 : 0);
    int d_a = 0x100 - s_a;

    return min((dst >>> 24) + a, 0xFF) << 24 |
        ((dst & RB_MASK) * d_a + (src & RB_MASK) * s_a) >>> 8 & RB_MASK |
        ((dst & GN_MASK) * d_a + (src & GN_MASK) * s_a) >>> 8 & GN_MASK;
  }
stefnotch commented 4 years ago

I stumbled upon this issue and here are my 2 cents:

The first issue is that the documentation doesn't make it entirely clear what factor refers to. (Judging from the source code, it refers to the alpha channel of the source pixels.)

And, I can confirm that the documentation is indeed wrong about BLEND - linear interpolation of colors: C = A*factor + B. This is the default.. The slightly arcane code basically does the following

private static final int RB_MASK = 0x00FF00FF;
private static final int GN_MASK = 0x0000FF00;

private static int blend_blend(int dst, int src) {
    // Get the alpha channel from the source
    int a = src >>> 24;

    // Probably to fix some off by one errors
    int s_a = a + (a >= 0x7F ? 1 : 0);

    // 256 - alpha! This is the part that's missing from the documentation
    int d_a = 0x100 - s_a;

    return 
        // Not entirely sure what this exactly does, but it's responsible for setting the alpha channel. It usually sets it to 255
        min((dst >>> 24) + a, 0xFF) << 24 |
        // Then, we're interpolating the colors. 
        // Since the color is stored in a single integer (ARGB), a simple lerp like (dst * d_a) + (src * s_a)
        //   could cause color components to overflow or underflow into the surrounding bytes/components
        // So, the RB_MASK masks the red and blue components. (a funky little optimisation)
        // Then they're lerped and overflows are taken care of. Afterwards, the green component gets masked and lerped.
        // Lastly, the results are joined together.
        ((dst & RB_MASK) * d_a + (src & RB_MASK) * s_a) >>> 8 & RB_MASK |
        ((dst & GN_MASK) * d_a + (src & GN_MASK) * s_a) >>> 8 & GN_MASK;
  }