1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Fixed hue wrap-around in HSV/HSL colours, added some more unit tests and added methods to distinguish between HSV and HSL saturation

This commit is contained in:
ed 2020-04-29 09:42:11 +01:00
parent 3bca5221ff
commit eeff1e6174
2 changed files with 109 additions and 115 deletions

View file

@ -96,7 +96,7 @@ namespace ColourHelpers
auto min = (2.0f * l) - v;
auto sv = (v - min) / v;
h = jlimit (0.0f, 360.0f, h * 360.0f) / 60.0f;
h = jlimit (0.0f, 360.0f, std::fmod (h, 1.0f) * 360.0f) / 60.0f;
auto f = h - std::floor (h);
auto vsf = v * sv * f;
auto mid1 = min + vsf;
@ -152,7 +152,7 @@ namespace ColourHelpers
return PixelARGB (alpha, intV, intV, intV);
s = jmin (1.0f, s);
h = jlimit (0.0f, 360.0f, h * 360.0f) / 60.0f;
h = jlimit (0.0f, 360.0f, std::fmod (h, 1.0f) * 360.0f) / 60.0f;
auto f = h - std::floor (h);
auto x = (uint8) roundToInt (v * (1.0f - s));
@ -301,14 +301,14 @@ bool Colour::isOpaque() const noexcept
return getAlpha() == 0xff;
}
Colour Colour::withAlpha (const uint8 newAlpha) const noexcept
Colour Colour::withAlpha (uint8 newAlpha) const noexcept
{
PixelARGB newCol (argb);
newCol.setAlpha (newAlpha);
return Colour (newCol);
}
Colour Colour::withAlpha (const float newAlpha) const noexcept
Colour Colour::withAlpha (float newAlpha) const noexcept
{
jassert (newAlpha >= 0 && newAlpha <= 1.0f);
@ -317,7 +317,7 @@ Colour Colour::withAlpha (const float newAlpha) const noexcept
return Colour (newCol);
}
Colour Colour::withMultipliedAlpha (const float alphaMultiplier) const noexcept
Colour Colour::withMultipliedAlpha (float alphaMultiplier) const noexcept
{
jassert (alphaMultiplier >= 0);
@ -391,13 +391,15 @@ float Colour::getHue() const noexcept { return ColourHelpers::HSB (*th
float Colour::getSaturation() const noexcept { return ColourHelpers::HSB (*this).saturation; }
float Colour::getBrightness() const noexcept { return ColourHelpers::HSB (*this).brightness; }
float Colour::getSaturationHSL() const noexcept { return ColourHelpers::HSL (*this).saturation; }
float Colour::getLightness() const noexcept { return ColourHelpers::HSL (*this).lightness; }
Colour Colour::withHue (float h) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue = h; return hsb.toColour (*this); }
Colour Colour::withSaturation (float s) const noexcept { ColourHelpers::HSB hsb (*this); hsb.saturation = s; return hsb.toColour (*this); }
Colour Colour::withBrightness (float v) const noexcept { ColourHelpers::HSB hsb (*this); hsb.brightness = v; return hsb.toColour (*this); }
Colour Colour::withLightness (float l) const noexcept { ColourHelpers::HSL hsl (*this); hsl.lightness = l; return hsl.toColour (*this); }
Colour Colour::withSaturationHSL (float s) const noexcept { ColourHelpers::HSL hsl (*this); hsl.saturation = s; return hsl.toColour (*this); }
Colour Colour::withLightness (float l) const noexcept { ColourHelpers::HSL hsl (*this); hsl.lightness = l; return hsl.toColour (*this); }
float Colour::getPerceivedBrightness() const noexcept
{
@ -407,28 +409,35 @@ float Colour::getPerceivedBrightness() const noexcept
}
//==============================================================================
Colour Colour::withRotatedHue (const float amountToRotate) const noexcept
Colour Colour::withRotatedHue (float amountToRotate) const noexcept
{
ColourHelpers::HSB hsb (*this);
hsb.hue += amountToRotate;
return hsb.toColour (*this);
}
Colour Colour::withMultipliedSaturation (const float amount) const noexcept
Colour Colour::withMultipliedSaturation (float amount) const noexcept
{
ColourHelpers::HSB hsb (*this);
hsb.saturation = jmin (1.0f, hsb.saturation * amount);
return hsb.toColour (*this);
}
Colour Colour::withMultipliedBrightness (const float amount) const noexcept
Colour Colour::withMultipliedSaturationHSL (float amount) const noexcept
{
ColourHelpers::HSL hsl (*this);
hsl.saturation = jmin (1.0f, hsl.saturation * amount);
return hsl.toColour (*this);
}
Colour Colour::withMultipliedBrightness (float amount) const noexcept
{
ColourHelpers::HSB hsb (*this);
hsb.brightness = jmin (1.0f, hsb.brightness * amount);
return hsb.toColour (*this);
}
Colour Colour::withMultipliedLightness (const float amount) const noexcept
Colour Colour::withMultipliedLightness (float amount) const noexcept
{
ColourHelpers::HSL hsl (*this);
hsl.lightness = jmin (1.0f, hsl.lightness * amount);
@ -457,14 +466,14 @@ Colour Colour::darker (float amount) const noexcept
}
//==============================================================================
Colour Colour::greyLevel (const float brightness) noexcept
Colour Colour::greyLevel (float brightness) noexcept
{
auto level = ColourHelpers::floatToUInt8 (brightness);
return Colour (level, level, level);
}
//==============================================================================
Colour Colour::contrasting (const float amount) const noexcept
Colour Colour::contrasting (float amount) const noexcept
{
return overlaidWith ((getPerceivedBrightness() >= 0.5f
? Colours::black
@ -542,168 +551,137 @@ public:
void runTest() override
{
auto testColour = [this] (Colour colour,
uint8 expectedRed, uint8 expectedGreen, uint8 expectedBlue,
uint8 expectedAlpha = 255, float expectedFloatAlpha = 1.0f)
{
expectEquals (colour.getRed(), expectedRed);
expectEquals (colour.getGreen(), expectedGreen);
expectEquals (colour.getBlue(), expectedBlue);
expectEquals (colour.getAlpha(), expectedAlpha);
expectEquals (colour.getFloatAlpha(), expectedFloatAlpha);
};
beginTest ("Constructors");
{
Colour c1;
expectEquals (c1.getRed(), (uint8) 0);
expectEquals (c1.getGreen(), (uint8) 0);
expectEquals (c1.getBlue(), (uint8) 0);
expectEquals (c1.getAlpha(), (uint8) 0);
expectEquals (c1.getFloatAlpha(), 0.0f);
testColour (c1, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c2 ((uint32) 0);
expectEquals (c2.getRed(), (uint8) 0);
expectEquals (c2.getGreen(), (uint8) 0);
expectEquals (c2.getBlue(), (uint8) 0);
expectEquals (c2.getAlpha(), (uint8) 0);
expectEquals (c2.getFloatAlpha(), 0.0f);
testColour (c2, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c3 ((uint32) 0xffffffff);
expectEquals (c3.getRed(), (uint8) 255);
expectEquals (c3.getGreen(), (uint8) 255);
expectEquals (c3.getBlue(), (uint8) 255);
expectEquals (c3.getAlpha(), (uint8) 255);
expectEquals (c3.getFloatAlpha(), 1.0f);
testColour (c3, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
Colour c4 (0, 0, 0);
expectEquals (c4.getRed(), (uint8) 0);
expectEquals (c4.getGreen(), (uint8) 0);
expectEquals (c4.getBlue(), (uint8) 0);
expectEquals (c4.getAlpha(), (uint8) 255);
expectEquals (c4.getFloatAlpha(), 1.0f);
testColour (c4, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 255, 1.0f);
Colour c5 (255, 255, 255);
expectEquals (c5.getRed(), (uint8) 255);
expectEquals (c5.getGreen(), (uint8) 255);
expectEquals (c5.getBlue(), (uint8) 255);
expectEquals (c5.getAlpha(), (uint8) 255);
expectEquals (c5.getFloatAlpha(), 1.0f);
testColour (c5, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
Colour c6 ((uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0);
expectEquals (c6.getRed(), (uint8) 0);
expectEquals (c6.getGreen(), (uint8) 0);
expectEquals (c6.getBlue(), (uint8) 0);
expectEquals (c6.getAlpha(), (uint8) 0);
expectEquals (c6.getFloatAlpha(), 0.0f);
testColour (c6, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c7 ((uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255);
expectEquals (c7.getRed(), (uint8) 255);
expectEquals (c7.getGreen(), (uint8) 255);
expectEquals (c7.getBlue(), (uint8) 255);
expectEquals (c7.getAlpha(), (uint8) 255);
expectEquals (c7.getFloatAlpha(), 1.0f);
testColour (c7, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
Colour c8 ((uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
expectEquals (c8.getRed(), (uint8) 0);
expectEquals (c8.getGreen(), (uint8) 0);
expectEquals (c8.getBlue(), (uint8) 0);
expectEquals (c8.getAlpha(), (uint8) 0);
expectEquals (c8.getFloatAlpha(), 0.0f);
testColour (c8, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c9 ((uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
expectEquals (c9.getRed(), (uint8) 255);
expectEquals (c9.getGreen(), (uint8) 255);
expectEquals (c9.getBlue(), (uint8) 255);
expectEquals (c9.getAlpha(), (uint8) 255);
expectEquals (c9.getFloatAlpha(), 1.0f);
testColour (c9, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
}
beginTest ("HSV");
{
auto testHSV = [this] (int hueDegrees, int saturationPercentage, int brightnessPercentage,
uint8 expectedRed, uint8 expectedGreen, uint8 expectedBlue)
{
auto testColour = Colour::fromHSV (hueDegrees / 360.0f,
saturationPercentage / 100.0f,
brightnessPercentage / 100.0f,
1.0f);
expectEquals (testColour.getRed(), expectedRed);
expectEquals (testColour.getGreen(), expectedGreen);
expectEquals (testColour.getBlue(), expectedBlue);
};
// black
testHSV (0, 0, 0, 0, 0, 0);
testColour (Colour::fromHSV (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0);
// white
testHSV (0, 0, 100, 255, 255, 255);
testColour (Colour::fromHSV (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255);
// red
testHSV (0, 100, 100, 255, 0, 0);
testColour (Colour::fromHSV (0.0f, 1.0f, 1.0f, 1.0f), 255, 0, 0);
testColour (Colour::fromHSV (1.0f, 1.0f, 1.0f, 1.0f), 255, 0, 0);
// lime
testHSV (120, 100, 100, 0, 255, 0);
testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 0);
// blue
testHSV (240, 100, 100, 0, 0, 255);
testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 0, 255);
// yellow
testHSV (60, 100, 100, 255, 255, 0);
testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 255, 0);
// cyan
testHSV (180, 100, 100, 0, 255, 255);
testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 255);
// magenta
testHSV (300, 100, 100, 255, 0, 255);
testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 0, 255);
// silver
testHSV (0, 0, 75, 191, 191, 191);
testColour (Colour::fromHSV (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191);
// grey
testHSV (0, 0, 50, 128, 128, 128);
testColour (Colour::fromHSV (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128);
// maroon
testHSV (0, 100, 50, 128, 0, 0);
testColour (Colour::fromHSV (0.0f, 1.0f, 0.5f, 1.0f), 128, 0, 0);
// olive
testHSV (60, 100, 50, 128, 128, 0);
testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 128, 0);
// green
testHSV (120, 100, 50, 0, 128, 0);
testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 0);
// purple
testHSV (300, 100, 50, 128, 0, 128);
testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 0, 128);
// teal
testHSV (180, 100, 50, 0, 128, 128);
testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 128);
// navy
testHSV (240, 100, 50, 0, 0, 128);
testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 128);
}
beginTest ("HSL");
{
auto testHSL = [this] (int hueDegrees, int saturationPercentage, int lightnessPercentage,
uint8 expectedRed, uint8 expectedGreen, uint8 expectedBlue)
{
auto testColour = Colour::fromHSL (hueDegrees / 360.0f,
saturationPercentage / 100.0f,
lightnessPercentage / 100.0f,
1.0f);
expectEquals (testColour.getRed(), expectedRed);
expectEquals (testColour.getGreen(), expectedGreen);
expectEquals (testColour.getBlue(), expectedBlue);
};
// black
testHSL (0, 0, 0, 0, 0, 0);
testColour (Colour::fromHSL (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0);
// white
testHSL (0, 0, 100, 255, 255, 255);
testColour (Colour::fromHSL (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255);
// red
testHSL (0, 100, 50, 255, 0, 0);
testColour (Colour::fromHSL (0.0f, 1.0f, 0.5f, 1.0f), 255, 0, 0);
testColour (Colour::fromHSL (1.0f, 1.0f, 0.5f, 1.0f), 255, 0, 0);
// lime
testHSL (120, 100, 50, 0, 255, 0);
testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 0);
// blue
testHSL (240, 100, 50, 0, 0, 255);
testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 255);
// yellow
testHSL (60, 100, 50, 255, 255, 0);
testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 255, 0);
// cyan
testHSL (180, 100, 50, 0, 255, 255);
testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 255);
// magenta
testHSL (300, 100, 50, 255, 0, 255);
testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 0, 255);
// silver
testHSL (0, 0, 75, 191, 191, 191);
testColour (Colour::fromHSL (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191);
// grey
testHSL (0, 0, 50, 128, 128, 128);
testColour (Colour::fromHSL (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128);
// maroon
testHSL (0, 100, 25, 128, 0, 0);
testColour (Colour::fromHSL (0.0f, 1.0f, 0.25f, 1.0f), 128, 0, 0);
// olive
testHSL (60, 100, 25, 128, 128, 0);
testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 128, 0);
// green
testHSL (120, 100, 25, 0, 128, 0);
testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 0);
// purple
testHSL (300, 100, 25, 128, 0, 128);
testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 0, 128);
// teal
testHSL (180, 100, 25, 0, 128, 128);
testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 128);
// navy
testHSL (240, 100, 25, 0, 0, 128);
testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 0, 128);
}
beginTest ("Modifiers");
{
Colour red (255, 0, 0);
testColour (red, 255, 0, 0);
testColour (red.withHue (120.0f / 360.0f), 0, 255, 0);
testColour (red.withSaturation (0.5f), 255, 128, 128);
testColour (red.withSaturationHSL (0.5f), 191, 64, 64);
testColour (red.withBrightness (0.5f), 128, 0, 0);
testColour (red.withLightness (1.0f), 255, 255, 255);
testColour (red.withRotatedHue (120.0f / 360.0f), 0, 255, 0);
testColour (red.withRotatedHue (480.0f / 360.0f), 0, 255, 0);
testColour (red.withMultipliedSaturation (0.0f), 255, 255, 255);
testColour (red.withMultipliedSaturationHSL (0.0f), 128, 128, 128);
testColour (red.withMultipliedBrightness (0.5f), 128, 0, 0);
testColour (red.withMultipliedLightness (2.0f), 255, 255, 255);
}
}
};

View file

@ -255,6 +255,11 @@ public:
*/
float getSaturation() const noexcept;
/** Returns the colour's saturation component as represented in the HSL colour space.
The value returned is in the range 0.0 to 1.0
*/
float getSaturationHSL() const noexcept;
/** Returns the colour's brightness component.
The value returned is in the range 0.0 to 1.0
*/
@ -292,6 +297,9 @@ public:
/** Returns a copy of this colour with a different saturation. */
Colour withSaturation (float newSaturation) const noexcept;
/** Returns a copy of this colour with a different saturation in the HSL colour space. */
Colour withSaturationHSL (float newSaturation) const noexcept;
/** Returns a copy of this colour with a different brightness.
@see brighter, darker, withMultipliedBrightness
*/
@ -314,6 +322,14 @@ public:
*/
Colour withMultipliedSaturation (float multiplier) const noexcept;
/** Returns a copy of this colour with its saturation multiplied by the given value.
The new colour's saturation is (this->getSaturation() * multiplier)
(the result is clipped to legal limits).
This will be in the HSL colour space.
*/
Colour withMultipliedSaturationHSL (float multiplier) const noexcept;
/** Returns a copy of this colour with its brightness multiplied by the given value.
The new colour's brightness is (this->getBrightness() * multiplier)
(the result is clipped to legal limits).