/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { namespace ColourHelpers { static uint8 floatToUInt8 (float n) noexcept { return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : (uint8) roundToInt (n * 255.0f)); } static float getHue (Colour col) { auto r = (int) col.getRed(); auto g = (int) col.getGreen(); auto b = (int) col.getBlue(); auto hi = jmax (r, g, b); auto lo = jmin (r, g, b); float hue = 0.0f; if (hi > 0) { auto invDiff = 1.0f / (hi - lo); auto red = (hi - r) * invDiff; auto green = (hi - g) * invDiff; auto blue = (hi - b) * invDiff; if (r == hi) hue = blue - green; else if (g == hi) hue = 2.0f + red - blue; else hue = 4.0f + green - red; hue *= 1.0f / 6.0f; if (hue < 0.0f) hue += 1.0f; } return hue; } //============================================================================== struct HSL { HSL (Colour col) noexcept { auto r = (int) col.getRed(); auto g = (int) col.getGreen(); auto b = (int) col.getBlue(); auto hi = jmax (r, g, b); auto lo = jmin (r, g, b); if (hi > 0) { lightness = ((hi + lo) / 2.0f) / 255.0f; if (lightness > 0.0f) hue = getHue (col); saturation = (hi - lo) / (1.0f - std::abs ((2.0f * lightness) - 1.0f)); } } Colour toColour (Colour original) const noexcept { return Colour::fromHSL (hue, saturation, lightness, original.getAlpha()); } static PixelARGB toRGB (float h, float s, float l, uint8 alpha) noexcept { auto v = l < 0.5f ? l * (1.0f + s) : l + s - (l * s); if (approximatelyEqual (v, 0.0f)) return PixelARGB (alpha, 0, 0, 0); auto min = (2.0f * l) - v; auto sv = (v - min) / v; h = ((h - std::floor (h)) * 360.0f) / 60.0f; auto f = h - std::floor (h); auto vsf = v * sv * f; auto mid1 = min + vsf; auto mid2 = v - vsf; if (h < 1.0f) return PixelARGB (alpha, floatToUInt8 (v), floatToUInt8 (mid1), floatToUInt8 (min)); else if (h < 2.0f) return PixelARGB (alpha, floatToUInt8 (mid2), floatToUInt8 (v), floatToUInt8 (min)); else if (h < 3.0f) return PixelARGB (alpha, floatToUInt8 (min), floatToUInt8 (v), floatToUInt8 (mid1)); else if (h < 4.0f) return PixelARGB (alpha, floatToUInt8 (min), floatToUInt8 (mid2), floatToUInt8 (v)); else if (h < 5.0f) return PixelARGB (alpha, floatToUInt8 (mid1), floatToUInt8 (min), floatToUInt8 (v)); else if (h < 6.0f) return PixelARGB (alpha, floatToUInt8 (v), floatToUInt8 (min), floatToUInt8 (mid2)); return PixelARGB (alpha, 0, 0, 0); } float hue = 0.0f, saturation = 0.0f, lightness = 0.0f; }; //============================================================================== struct HSB { HSB (Colour col) noexcept { auto r = (int) col.getRed(); auto g = (int) col.getGreen(); auto b = (int) col.getBlue(); auto hi = jmax (r, g, b); auto lo = jmin (r, g, b); if (hi > 0) { saturation = (hi - lo) / (float) hi; if (saturation > 0.0f) hue = getHue (col); brightness = hi / 255.0f; } } Colour toColour (Colour original) const noexcept { return Colour (hue, saturation, brightness, original.getAlpha()); } static PixelARGB toRGB (float h, float s, float v, uint8 alpha) noexcept { v = jlimit (0.0f, 255.0f, v * 255.0f); auto intV = (uint8) roundToInt (v); if (s <= 0) return PixelARGB (alpha, intV, intV, intV); s = jmin (1.0f, s); h = ((h - std::floor (h)) * 360.0f) / 60.0f; auto f = h - std::floor (h); auto x = (uint8) roundToInt (v * (1.0f - s)); if (h < 1.0f) return PixelARGB (alpha, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x); if (h < 2.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - s * f)), intV, x); if (h < 3.0f) return PixelARGB (alpha, x, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f))))); if (h < 4.0f) return PixelARGB (alpha, x, (uint8) roundToInt (v * (1.0f - s * f)), intV); if (h < 5.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x, intV); return PixelARGB (alpha, intV, x, (uint8) roundToInt (v * (1.0f - s * f))); } float hue = 0.0f, saturation = 0.0f, brightness = 0.0f; }; //============================================================================== struct YIQ { YIQ (Colour c) noexcept { auto r = c.getFloatRed(); auto g = c.getFloatGreen(); auto b = c.getFloatBlue(); y = 0.2999f * r + 0.5870f * g + 0.1140f * b; i = 0.5957f * r - 0.2744f * g - 0.3212f * b; q = 0.2114f * r - 0.5225f * g - 0.3113f * b; alpha = c.getFloatAlpha(); } Colour toColour() const noexcept { return Colour::fromFloatRGBA (y + 0.9563f * i + 0.6210f * q, y - 0.2721f * i - 0.6474f * q, y - 1.1070f * i + 1.7046f * q, alpha); } float y = 0.0f, i = 0.0f, q = 0.0f, alpha = 0.0f; }; } //============================================================================== bool Colour::operator== (const Colour& other) const noexcept { return argb.getNativeARGB() == other.argb.getNativeARGB(); } bool Colour::operator!= (const Colour& other) const noexcept { return argb.getNativeARGB() != other.argb.getNativeARGB(); } //============================================================================== Colour::Colour (uint32 col) noexcept : argb (static_cast ((col >> 24) & 0xff), static_cast ((col >> 16) & 0xff), static_cast ((col >> 8) & 0xff), static_cast (col & 0xff)) { } Colour::Colour (uint8 red, uint8 green, uint8 blue) noexcept { argb.setARGB (0xff, red, green, blue); } Colour Colour::fromRGB (uint8 red, uint8 green, uint8 blue) noexcept { return Colour (red, green, blue); } Colour::Colour (uint8 red, uint8 green, uint8 blue, uint8 alpha) noexcept { argb.setARGB (alpha, red, green, blue); } Colour Colour::fromRGBA (uint8 red, uint8 green, uint8 blue, uint8 alpha) noexcept { return Colour (red, green, blue, alpha); } Colour::Colour (uint8 red, uint8 green, uint8 blue, float alpha) noexcept { argb.setARGB (ColourHelpers::floatToUInt8 (alpha), red, green, blue); } Colour Colour::fromFloatRGBA (float red, float green, float blue, float alpha) noexcept { return Colour (ColourHelpers::floatToUInt8 (red), ColourHelpers::floatToUInt8 (green), ColourHelpers::floatToUInt8 (blue), alpha); } Colour::Colour (float hue, float saturation, float brightness, float alpha) noexcept : argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, ColourHelpers::floatToUInt8 (alpha))) { } Colour Colour::fromHSV (float hue, float saturation, float brightness, float alpha) noexcept { return Colour (hue, saturation, brightness, alpha); } Colour Colour::fromHSL (float hue, float saturation, float lightness, float alpha) noexcept { Colour hslColour; hslColour.argb = ColourHelpers::HSL::toRGB (hue, saturation, lightness, ColourHelpers::floatToUInt8 (alpha)); return hslColour; } Colour::Colour (float hue, float saturation, float brightness, uint8 alpha) noexcept : argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, alpha)) { } Colour::Colour (PixelARGB argb_) noexcept : argb (argb_) { } Colour::Colour (PixelRGB rgb) noexcept : argb (Colour (rgb.getInARGBMaskOrder()).argb) { } Colour::Colour (PixelAlpha alpha) noexcept : argb (Colour (alpha.getInARGBMaskOrder()).argb) { } //============================================================================== const PixelARGB Colour::getPixelARGB() const noexcept { PixelARGB p (argb); p.premultiply(); return p; } uint32 Colour::getARGB() const noexcept { return argb.getInARGBMaskOrder(); } //============================================================================== bool Colour::isTransparent() const noexcept { return getAlpha() == 0; } bool Colour::isOpaque() const noexcept { return getAlpha() == 0xff; } Colour Colour::withAlpha (uint8 newAlpha) const noexcept { PixelARGB newCol (argb); newCol.setAlpha (newAlpha); return Colour (newCol); } Colour Colour::withAlpha (float newAlpha) const noexcept { jassert (newAlpha >= 0 && newAlpha <= 1.0f); PixelARGB newCol (argb); newCol.setAlpha (ColourHelpers::floatToUInt8 (newAlpha)); return Colour (newCol); } Colour Colour::withMultipliedAlpha (float alphaMultiplier) const noexcept { jassert (alphaMultiplier >= 0); PixelARGB newCol (argb); newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha()))); return Colour (newCol); } //============================================================================== Colour Colour::overlaidWith (Colour src) const noexcept { auto destAlpha = getAlpha(); if (destAlpha <= 0) return src; auto invA = 0xff - (int) src.getAlpha(); auto resA = 0xff - (((0xff - destAlpha) * invA) >> 8); if (resA <= 0) return *this; auto da = (invA * destAlpha) / resA; return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)), (uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)), (uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)), (uint8) resA); } Colour Colour::interpolatedWith (Colour other, float proportionOfOther) const noexcept { if (proportionOfOther <= 0) return *this; if (proportionOfOther >= 1.0f) return other; PixelARGB c1 (getPixelARGB()); PixelARGB c2 (other.getPixelARGB()); c1.tween (c2, (uint32) roundToInt (proportionOfOther * 255.0f)); c1.unpremultiply(); return Colour (c1); } //============================================================================== float Colour::getFloatRed() const noexcept { return getRed() / 255.0f; } float Colour::getFloatGreen() const noexcept { return getGreen() / 255.0f; } float Colour::getFloatBlue() const noexcept { return getBlue() / 255.0f; } float Colour::getFloatAlpha() const noexcept { return getAlpha() / 255.0f; } //============================================================================== void Colour::getHSB (float& h, float& s, float& v) const noexcept { ColourHelpers::HSB hsb (*this); h = hsb.hue; s = hsb.saturation; v = hsb.brightness; } void Colour::getHSL (float& h, float& s, float& l) const noexcept { ColourHelpers::HSL hsl (*this); h = hsl.hue; s = hsl.saturation; l = hsl.lightness; } float Colour::getHue() const noexcept { return ColourHelpers::HSB (*this).hue; } 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::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 { return std::sqrt (0.241f * square (getFloatRed()) + 0.691f * square (getFloatGreen()) + 0.068f * square (getFloatBlue())); } //============================================================================== Colour Colour::withRotatedHue (float amountToRotate) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue += amountToRotate; return hsb.toColour (*this); } 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::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 (float amount) const noexcept { ColourHelpers::HSL hsl (*this); hsl.lightness = jmin (1.0f, hsl.lightness * amount); return hsl.toColour (*this); } //============================================================================== Colour Colour::brighter (float amount) const noexcept { amount = 1.0f / (1.0f + amount); return Colour ((uint8) (255 - (amount * (255 - getRed()))), (uint8) (255 - (amount * (255 - getGreen()))), (uint8) (255 - (amount * (255 - getBlue()))), getAlpha()); } Colour Colour::darker (float amount) const noexcept { amount = 1.0f / (1.0f + amount); return Colour ((uint8) (amount * getRed()), (uint8) (amount * getGreen()), (uint8) (amount * getBlue()), getAlpha()); } //============================================================================== Colour Colour::greyLevel (float brightness) noexcept { auto level = ColourHelpers::floatToUInt8 (brightness); return Colour (level, level, level); } //============================================================================== Colour Colour::contrasting (float amount) const noexcept { return overlaidWith ((getPerceivedBrightness() >= 0.5f ? Colours::black : Colours::white).withAlpha (amount)); } Colour Colour::contrasting (Colour target, float minContrast) const noexcept { ColourHelpers::YIQ bg (*this); ColourHelpers::YIQ fg (target); if (std::abs (bg.y - fg.y) >= minContrast) return target; auto y1 = jmax (0.0f, bg.y - minContrast); auto y2 = jmin (1.0f, bg.y + minContrast); fg.y = (std::abs (y1 - bg.y) > std::abs (y2 - bg.y)) ? y1 : y2; return fg.toColour(); } Colour Colour::contrasting (Colour colour1, Colour colour2) noexcept { auto b1 = colour1.getPerceivedBrightness(); auto b2 = colour2.getPerceivedBrightness(); float best = 0.0f, bestDist = 0.0f; for (float i = 0.0f; i < 1.0f; i += 0.02f) { auto d1 = std::abs (i - b1); auto d2 = std::abs (i - b2); auto dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2); if (dist > bestDist) { best = i; bestDist = dist; } } return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f)) .withBrightness (best); } //============================================================================== String Colour::toString() const { return String::toHexString ((int) argb.getInARGBMaskOrder()); } Colour Colour::fromString (StringRef encodedColourString) { return Colour ((uint32) CharacterFunctions::HexParser::parse (encodedColourString.text)); } String Colour::toDisplayString (const bool includeAlphaValue) const { return String::toHexString ((int) (argb.getInARGBMaskOrder() & (includeAlphaValue ? 0xffffffff : 0xffffff))) .paddedLeft ('0', includeAlphaValue ? 8 : 6) .toUpperCase(); } //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS class ColourTests : public UnitTest { public: ColourTests() : UnitTest ("Colour", UnitTestCategories::graphics) {} 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; testColour (c1, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f); Colour c2 ((uint32) 0); testColour (c2, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f); Colour c3 ((uint32) 0xffffffff); testColour (c3, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f); Colour c4 (0, 0, 0); testColour (c4, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 255, 1.0f); Colour c5 (255, 255, 255); testColour (c5, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f); Colour c6 ((uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0); testColour (c6, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f); Colour c7 ((uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255); testColour (c7, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f); Colour c8 ((uint8) 0, (uint8) 0, (uint8) 0, 0.0f); testColour (c8, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f); Colour c9 ((uint8) 255, (uint8) 255, (uint8) 255, 1.0f); testColour (c9, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f); } beginTest ("HSV"); { // black testColour (Colour::fromHSV (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0); // white testColour (Colour::fromHSV (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255); // red 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 testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 0); // blue testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 0, 255); // yellow testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 255, 0); // cyan testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 255); // magenta testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 0, 255); // silver testColour (Colour::fromHSV (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191); // grey testColour (Colour::fromHSV (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128); // maroon testColour (Colour::fromHSV (0.0f, 1.0f, 0.5f, 1.0f), 128, 0, 0); // olive testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 128, 0); // green testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 0); // purple testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 0, 128); // teal testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 128); // navy testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 128); } beginTest ("HSL"); { // black testColour (Colour::fromHSL (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0); // white testColour (Colour::fromHSL (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255); // red 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 testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 0); // blue testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 255); // yellow testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 255, 0); // cyan testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 255); // magenta testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 0, 255); // silver testColour (Colour::fromHSL (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191); // grey testColour (Colour::fromHSL (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128); // maroon testColour (Colour::fromHSL (0.0f, 1.0f, 0.25f, 1.0f), 128, 0, 0); // olive testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 128, 0); // green testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 0); // purple testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 0, 128); // teal testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 128); // navy 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.withRotatedHue (-240.0f / 360.0f), 0, 255, 0); testColour (red.withRotatedHue (-600.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); } } }; static ColourTests colourTests; #endif } // namespace juce