From a59cba010b9c2b85644c8df69c39d12bcadcf4d6 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 7 Feb 2023 13:58:49 +0000 Subject: [PATCH] ColourGradient: Create lookup tables using non-premultiplied colours The OpenGL renderer uses ColourGradient::createLookupTable to generate gradient textures. However, the tweening method used was different to the tweening used by CoreGraphics gradients, and by the software renderer. Gradient tweening is now computed using non-premultiplied colours, to ensure consistency between gradients rendered using OpenGL, and with other renderers. --- BREAKING-CHANGES.txt | 25 +++++++++++++++++ modules/juce_graphics/colour/juce_Colour.cpp | 7 ++++- modules/juce_graphics/colour/juce_Colour.h | 6 ++++- .../colour/juce_ColourGradient.cpp | 27 +++++++++---------- .../colour/juce_ColourGradient.h | 11 ++++++++ .../opengl/juce_OpenGLGraphicsContext.cpp | 2 +- 6 files changed, 61 insertions(+), 17 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index d2f7c04e75..edbdf6aef9 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,31 @@ JUCE breaking changes develop ======= +Change +------ +The implementation of ColourGradient::createLookupTable has been updated to use +non-premultiplied colours. + +Possible Issues +--------------- +Programs that draw transparent gradients using the OpenGL or software +renderers, or that use lookup tables generated from transparent gradients for +other purposes, may now produce different results. + +Workaround +---------- +For gradients to render the same as they did previously, transparent colour +stops should be un-premultiplied. For colours with an alpha component of 0, it +may be necessary to specify appropriate RGB components. + +Rationale +--------- +Previously, transparent gradients rendered using CoreGraphics looked different +to the same gradients drawn using OpenGL or the software renderer. This change +updates the OpenGL and software renderers, so that they produce the same +results as CoreGraphics. + + Change ------ Projucer-generated MSVC projects now build VST3s as bundles, rather than as diff --git a/modules/juce_graphics/colour/juce_Colour.cpp b/modules/juce_graphics/colour/juce_Colour.cpp index 38d2d8b04f..572084c3dc 100644 --- a/modules/juce_graphics/colour/juce_Colour.cpp +++ b/modules/juce_graphics/colour/juce_Colour.cpp @@ -291,13 +291,18 @@ Colour::Colour (PixelAlpha alpha) noexcept } //============================================================================== -const PixelARGB Colour::getPixelARGB() const noexcept +PixelARGB Colour::getPixelARGB() const noexcept { PixelARGB p (argb); p.premultiply(); return p; } +PixelARGB Colour::getNonPremultipliedPixelARGB() const noexcept +{ + return argb; +} + uint32 Colour::getARGB() const noexcept { return argb.getInARGBMaskOrder(); diff --git a/modules/juce_graphics/colour/juce_Colour.h b/modules/juce_graphics/colour/juce_Colour.h index 3077addde6..2e1e4ee3d0 100644 --- a/modules/juce_graphics/colour/juce_Colour.h +++ b/modules/juce_graphics/colour/juce_Colour.h @@ -194,7 +194,11 @@ public: /** Returns a premultiplied ARGB pixel object that represents this colour. */ - const PixelARGB getPixelARGB() const noexcept; + PixelARGB getPixelARGB() const noexcept; + + /** Returns an ARGB pixel object that represents this colour. + */ + PixelARGB getNonPremultipliedPixelARGB() const noexcept; /** Returns a 32-bit integer that represents this colour. diff --git a/modules/juce_graphics/colour/juce_ColourGradient.cpp b/modules/juce_graphics/colour/juce_ColourGradient.cpp index a9dc3af681..4a5444e4ad 100644 --- a/modules/juce_graphics/colour/juce_ColourGradient.cpp +++ b/modules/juce_graphics/colour/juce_ColourGradient.cpp @@ -201,29 +201,28 @@ void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int jassert (numEntries > 0); jassert (colours.getReference(0).position == 0.0); // The first colour specified has to go at position 0 - auto pix1 = colours.getReference (0).colour.getPixelARGB(); int index = 0; - for (int j = 1; j < colours.size(); ++j) + for (int j = 0; j < colours.size() - 1; ++j) { - auto& p = colours.getReference (j); - auto numToDo = roundToInt (p.position * (numEntries - 1)) - index; - auto pix2 = p.colour.getPixelARGB(); + const auto& o = colours.getReference (j + 0); + const auto& p = colours.getReference (j + 1); + const auto numToDo = roundToInt (p.position * (numEntries - 1)) - index; + const auto pix1 = o.colour.getNonPremultipliedPixelARGB(); + const auto pix2 = p.colour.getNonPremultipliedPixelARGB(); - for (int i = 0; i < numToDo; ++i) + for (auto i = 0; i < numToDo; ++i) { - jassert (index >= 0 && index < numEntries); + auto blended = pix1; + blended.tween (pix2, (uint32) ((i << 8) / numToDo)); + blended.premultiply(); - lookupTable[index] = pix1; - lookupTable[index].tween (pix2, (uint32) ((i << 8) / numToDo)); - ++index; + jassert (0 <= index && index < numEntries); + lookupTable[index++] = blended; } - - pix1 = pix2; } - while (index < numEntries) - lookupTable [index++] = pix1; + std::fill (lookupTable + index, lookupTable + numEntries, colours.getLast().colour.getPixelARGB()); } int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock& lookupTable) const diff --git a/modules/juce_graphics/colour/juce_ColourGradient.h b/modules/juce_graphics/colour/juce_ColourGradient.h index c38f5251f7..6a15d95f17 100644 --- a/modules/juce_graphics/colour/juce_ColourGradient.h +++ b/modules/juce_graphics/colour/juce_ColourGradient.h @@ -185,6 +185,17 @@ public: */ void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; + /** Creates a set of interpolated premultiplied ARGB values. + This will fill an array of a user-specified size with the gradient, interpolating to fit. + When calling this, the ColourGradient must have at least 2 colour stops specified. + */ + template + void createLookupTable (PixelARGB (&resultLookupTable)[NumEntries]) const noexcept + { + static_assert (NumEntries != 0); + createLookupTable (resultLookupTable, NumEntries); + } + /** Returns true if all colours are opaque. */ bool isOpaque() const noexcept; diff --git a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp index 1ec0f17fc7..38074a69bc 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp @@ -1139,7 +1139,7 @@ struct StateHelpers JUCE_CHECK_OPENGL_ERROR; PixelARGB lookup[gradientTextureSize]; - gradient.createLookupTable (lookup, gradientTextureSize); + gradient.createLookupTable (lookup); gradientTextures.getUnchecked (activeGradientIndex)->loadARGB (lookup, gradientTextureSize, 1); }