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.v7.0.9
@@ -4,6 +4,31 @@ JUCE breaking changes | |||||
develop | 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 | Change | ||||
------ | ------ | ||||
Projucer-generated MSVC projects now build VST3s as bundles, rather than as | Projucer-generated MSVC projects now build VST3s as bundles, rather than as | ||||
@@ -291,13 +291,18 @@ Colour::Colour (PixelAlpha alpha) noexcept | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
const PixelARGB Colour::getPixelARGB() const noexcept | |||||
PixelARGB Colour::getPixelARGB() const noexcept | |||||
{ | { | ||||
PixelARGB p (argb); | PixelARGB p (argb); | ||||
p.premultiply(); | p.premultiply(); | ||||
return p; | return p; | ||||
} | } | ||||
PixelARGB Colour::getNonPremultipliedPixelARGB() const noexcept | |||||
{ | |||||
return argb; | |||||
} | |||||
uint32 Colour::getARGB() const noexcept | uint32 Colour::getARGB() const noexcept | ||||
{ | { | ||||
return argb.getInARGBMaskOrder(); | return argb.getInARGBMaskOrder(); | ||||
@@ -194,7 +194,11 @@ public: | |||||
/** Returns a premultiplied ARGB pixel object that represents this colour. | /** 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. | /** Returns a 32-bit integer that represents this colour. | ||||
@@ -201,29 +201,28 @@ void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int | |||||
jassert (numEntries > 0); | jassert (numEntries > 0); | ||||
jassert (colours.getReference(0).position == 0.0); // The first colour specified has to go at position 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; | 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<PixelARGB>& lookupTable) const | int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& lookupTable) const | ||||
@@ -185,6 +185,17 @@ public: | |||||
*/ | */ | ||||
void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; | 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 <size_t NumEntries> | |||||
void createLookupTable (PixelARGB (&resultLookupTable)[NumEntries]) const noexcept | |||||
{ | |||||
static_assert (NumEntries != 0); | |||||
createLookupTable (resultLookupTable, NumEntries); | |||||
} | |||||
/** Returns true if all colours are opaque. */ | /** Returns true if all colours are opaque. */ | ||||
bool isOpaque() const noexcept; | bool isOpaque() const noexcept; | ||||
@@ -1139,7 +1139,7 @@ struct StateHelpers | |||||
JUCE_CHECK_OPENGL_ERROR; | JUCE_CHECK_OPENGL_ERROR; | ||||
PixelARGB lookup[gradientTextureSize]; | PixelARGB lookup[gradientTextureSize]; | ||||
gradient.createLookupTable (lookup, gradientTextureSize); | |||||
gradient.createLookupTable (lookup); | |||||
gradientTextures.getUnchecked (activeGradientIndex)->loadARGB (lookup, gradientTextureSize, 1); | gradientTextures.getUnchecked (activeGradientIndex)->loadARGB (lookup, gradientTextureSize, 1); | ||||
} | } | ||||