Blending mode fixes


#1

The blend functions in juce_PixelFormat.h do not work properly. The colors in some bitmaps come out wrong. I tracked this down to lack of component clamping in the blend functions used when drawing.

The good news is, here are the fixes!

This is a unified patch for modules\juce_graphics\colour\juce_PixelFormats.h

[code]@@ -87,27 +87,31 @@
forcedinline uint8& getAlpha() noexcept { return components.a; }
forcedinline uint8& getRed() noexcept { return components.r; }
forcedinline uint8& getGreen() noexcept { return components.g; }
forcedinline uint8& getBlue() noexcept { return components.b; }
#endif

+#define CMASK 0x00ff00ff
+
+#define CLAMP(_x) \

  • (((_x) & CMASK) | (((_x) >> 8) & CMASK) * 0xff)

  • /** Blends another pixel onto this one.

       This takes into account the opacity of the pixel being overlaid, and blends
       it accordingly.
    

    */
    template
    forcedinline void blend (const Pixel& src) noexcept
    {

  •    uint32 sargb = src.getARGB();
    
  •    const uint32 alpha = 0x100 - (sargb >> 24);
    
  •    sargb += 0x00ff00ff & ((getRB() * alpha) >> 8);
    
  •    sargb += 0xff00ff00 & (getAG() * alpha);
    
  •    argb = sargb;
    
  •    const uint32 alpha = 0x100 - src.getAlpha();
    
  •    uint32 rb = src.getRB() + ((getRB() * alpha >>8) & CMASK);
    
  •    uint32 ag = src.getAG() + ((getAG() * alpha >>8) & CMASK);
    
  •    argb = CLAMP(rb) + (CLAMP(ag)<<8);
    

    }

    /** Blends another pixel onto this one.

       This takes into account the opacity of the pixel being overlaid, and blends
       it accordingly.
    

@@ -120,23 +124,21 @@
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
being used, so this can blend semi-transparently from a PixelRGB argument.
*/
template
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
{

  •    ++extraAlpha;
    
  •    //++extraAlpha;
    
  •    uint32 sargb = ((extraAlpha * src.getAG()) & 0xff00ff00)
    
  •                     | (((extraAlpha * src.getRB()) >> 8) & 0x00ff00ff);
    
  •    const uint32 alpha = 0x100 - (sargb >> 24);
    
  •    sargb += 0x00ff00ff & ((getRB() * alpha) >> 8);
    
  •    sargb += 0xff00ff00 & (getAG() * alpha);
    
  •    uint32 ag = (extraAlpha*src.getAG() >> 8) & CMASK;
    
  •    const uint32 alpha = 0x100 - (ag >> 16);
    
  •    ag += (getAG()*alpha >> 8) & CMASK;
    
  •    uint32 rb = ((extraAlpha*src.getRB() >> 8) & CMASK) + ((getRB()*alpha >> 8) & CMASK);
    
  •    argb = sargb;
    
  •    argb = CLAMP(rb) + (CLAMP(ag)<<8);
    

    }

    /** Blends another pixel with this one, creating a colour that is somewhere
    between the two, as specified by the amount.
    */
    template
    @@ -344,21 +346,23 @@
    This takes into account the opacity of the pixel being overlaid, and blends
    it accordingly.
    */
    template
    forcedinline void blend (const Pixel& src) noexcept
    {

  •    uint32 sargb = src.getARGB();
    
  •    const uint32 alpha = 0x100 - (sargb >> 24);
    
  •    const uint32 alpha = 0x100 - src.getAlpha();
    
  •    sargb += 0x00ff00ff & ((getRB() * alpha) >> 8);
    
  •    sargb += 0x0000ff00 & (g * alpha);
    
  •    uint32 rb = src.getRB() + ((getRB()*alpha >> 8) & CMASK);
    
  •    uint32 ag = src.getAG() + (g*alpha >> 8);
    
  •    rb = CLAMP(rb);
    
  •    ag = CLAMP(ag);
    
  •    r = (uint8) (sargb >> 16);
    
  •    g = (uint8) (sargb >> 8);
    
  •    b = (uint8) sargb;
    
  •    r = (uint8) (rb >> 16);
    
  •    g = (uint8) ag;
    
  •    b = (uint8) rb;
    

    }

    forcedinline void blend (const PixelRGB src) noexcept
    {
    set (src);
    }
    @@ -368,25 +372,25 @@
    The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
    being used, so this can blend semi-transparently from a PixelRGB argument.
    */
    template
    forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
    {

  •    ++extraAlpha;
    
  •    const uint32 srb = (extraAlpha * src.getRB()) >> 8;
    
  •    const uint32 sag = extraAlpha * src.getAG();
    
  •    uint32 sargb = (sag & 0xff00ff00) | (srb & 0x00ff00ff);
    
  •   // ++extraAlpha;
    
  •    uint32 ag = (extraAlpha*src.getAG() >> 8) & CMASK;
    
  •    const uint32 alpha = 0x100 - (ag >> 16);
    
  •    ag += g*alpha >> 8;
    
  •    const uint32 alpha = 0x100 - (sargb >> 24);
    
  •    uint32 rb = ((extraAlpha*src.getRB() >> 8) & CMASK) + ((getRB()*alpha >> 8) & CMASK);
    
  •    sargb += 0x00ff00ff & ((getRB() * alpha) >> 8);
    
  •    sargb += 0x0000ff00 & (g * alpha);
    
  •    rb = CLAMP(rb);
    
  •    ag = CLAMP(ag);
    
  •    b = (uint8) sargb;
    
  •    g = (uint8) (sargb >> 8);
    
  •    r = (uint8) (sargb >> 16);
    
  •    b = (uint8) rb;
    
  •    g = (uint8) ag;
    
  •    r = (uint8) (rb >> 16);
    

    }

    /** Blends another pixel with this one, creating a colour that is somewhere
    between the two, as specified by the amount.
    */
    template
    [/code]


#2

Wow, that code’s been there for years and I’ve never spotted any mistakes!

Ta for the fixes! But does the clamping slow things down at all?


#3

Yeah well, over here in my world, simple red bitmaps come out black :slight_smile:

The clamping is needed, but some of the blends might be relaxed. Aalpha + B(1-alpha) should not overflow, but the blends appears to use `extraAlpha’, and another alpha value derived from the existing one. As such they do not follow the above constraints. However, they might be fixable without clamping by careful analysis of these alpha values. Whereas some of the blends, where you’re adding A + Balpha, definitely needs overflow clamping and did go wrong in my code (red 0xff overflowed into the alpha channel and wound up 0x00; why my red picture came out black).

Regarding performance hit. Im not seeing it look much different here, but i’ve not tried mobiles yet. You’ll see the clamp function is very cunning and does not make any branches. Its possible we might be able to lose the multiply too. There might be a case for SSE instructions here if you want to enter a world of hurt.


#4

Thanks, I’ll roll out these changes.

SSE in those methods is something I’ve often thought about but never quite had the energy to attempt.


#5

Ok, give this a try now…


#6

got the tip code. works.

red bitmaps are now red bitmaps :slight_smile:


#7

here’s a slightly better version of `clampPixelComponents’.

inline uint32 clampPixelComponents (uint32 x) noexcept { return (x | (0x01000100 - maskPixelComponents(x))) & 0x00ff00ff; }


#8

Cunning! Thanks!