ladybird/Libraries/LibGfx/Color.h

639 lines
19 KiB
C
Raw Permalink Normal View History

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
2018-10-10 11:49:36 -03:00
#pragma once
#include <math.h>
#include <AK/Assertions.h>
2020-10-26 12:47:38 -03:00
#include <AK/Format.h>
#include <AK/Forward.h>
#include <AK/Math.h>
#include <AK/StdLibExtras.h>
#include <LibGfx/ColorConversion.h>
#include <LibIPC/Forward.h>
2018-10-10 11:49:36 -03:00
namespace Gfx {
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
// Named after in memory-order (little-endian)
// e.g. 0xAARRGGBB
using BGRA8888 = u32;
enum class AlphaType {
Premultiplied,
Unpremultiplied,
};
inline bool is_valid_alpha_type(u32 alpha_type)
{
switch (alpha_type) {
case (u32)AlphaType::Premultiplied:
case (u32)AlphaType::Unpremultiplied:
return true;
}
return false;
}
2020-02-07 14:29:52 -03:00
struct HSV {
double hue { 0 };
double saturation { 0 };
double value { 0 };
};
struct YUV {
float y { 0 };
float u { 0 };
float v { 0 };
};
struct Oklab {
float L { 0 };
float a { 0 };
float b { 0 };
};
2018-10-10 11:49:36 -03:00
class Color {
public:
enum class NamedColor {
Transparent,
Black,
White,
Red,
Green,
2019-05-05 19:59:33 -03:00
Cyan,
Blue,
2019-01-12 01:02:36 -02:00
Yellow,
Magenta,
2018-10-12 18:02:23 -03:00
DarkGray,
MidGray,
LightGray,
WarmGray,
DarkCyan,
DarkGreen,
DarkBlue,
DarkRed,
MidCyan,
MidGreen,
MidRed,
MidBlue,
MidMagenta,
LightBlue,
};
using enum NamedColor;
enum class BrandedColor {
Indigo10,
Indigo20,
Indigo30,
Indigo40,
Indigo50,
Indigo60,
Indigo80,
Indigo100,
Indigo300,
Indigo500,
Indigo900,
Violet10,
Violet20,
Violet30,
Violet40,
Violet50,
Violet60,
Violet80,
Violet100,
Violet300,
Violet500,
Violet900,
SlateBlue10,
SlateBlue20,
SlateBlue30,
SlateBlue40,
SlateBlue50,
SlateBlue60,
SlateBlue80,
SlateBlue100,
SlateBlue300,
SlateBlue500,
SlateBlue900,
Violet = Violet100,
Indigo = Indigo100,
SlateBlue = SlateBlue100,
};
constexpr Color() = default;
constexpr Color(NamedColor);
constexpr Color(u8 r, u8 g, u8 b)
: m_value(0xff000000 | (r << 16) | (g << 8) | b)
{
}
constexpr Color(u8 r, u8 g, u8 b, u8 a)
: m_value((a << 24) | (r << 16) | (g << 8) | b)
{
}
static constexpr Color branded_color(BrandedColor);
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
static constexpr Color from_bgrx(unsigned bgrx) { return Color(bgrx | 0xff000000); }
static constexpr Color from_bgra(unsigned bgra) { return Color(bgra); }
static constexpr Color from_rgba(unsigned rgba)
{
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
unsigned bgra = (rgba & 0xff00ff00) | ((rgba & 0xff0000) >> 16) | ((rgba & 0xff) << 16);
return Color::from_bgra(bgra);
}
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
static constexpr Color from_rgbx(unsigned rgbx) { return Color::from_rgba(rgbx | 0xff000000); }
2018-10-10 11:49:36 -03:00
static constexpr Color from_yuv(YUV const& yuv) { return from_yuv(yuv.y, yuv.u, yuv.v); }
static constexpr Color from_yuv(float y, float u, float v)
{
auto srgb = yuv_to_srgb({ y, u, v });
return {
static_cast<u8>(floorf(srgb[0] * 255.0f)),
static_cast<u8>(floorf(srgb[1] * 255.0f)),
static_cast<u8>(floorf(srgb[2] * 255.0f)),
};
}
constexpr YUV to_yuv() const
{
auto yuv = srgb_to_yuv({ red() / 255.0f, green() / 255.0f, blue() / 255.0f });
return { yuv[0], yuv[1], yuv[2] };
}
static constexpr Color from_hsl(float h_degrees, float s, float l) { return from_hsla(h_degrees, s, l, 1.0); }
static constexpr Color from_hsla(float h_degrees, float s, float l, float a)
{
auto srgb = hsl_to_srgb({ h_degrees, clamp(s, 0.0f, 1.0f), clamp(l, 0.0f, 1.0f), a });
u8 r_u8 = clamp(lroundf(srgb[0] * 255.0f), 0, 255);
u8 g_u8 = clamp(lroundf(srgb[1] * 255.0f), 0, 255);
u8 b_u8 = clamp(lroundf(srgb[2] * 255.0f), 0, 255);
u8 a_u8 = clamp(lroundf(srgb.alpha() * 255.0f), 0, 255);
return Color(r_u8, g_u8, b_u8, a_u8);
}
static Color from_a98rgb(float r, float g, float b, float alpha = 1.0f);
static Color from_display_p3(float r, float g, float b, float alpha = 1.0f);
LibGfx+LibWeb/CSS: Add support for the `lab()` color function The support in LibWeb is quite easy as the previous commits introduced helpers to support lab-like colors. Now for the methods in Color: - The formulas in `from_lab()` are derived from the CIEXYZ to CIELAB formulas the "Colorimetry" paper published by the CIE. - The conversion in `from_xyz50()` can be decomposed in multiple steps XYZ D50 -> XYZ D65 -> Linear sRGB -> sRGB. The two first conversion are done with a singular matrix operation. This matrix was generated with a Python script [1]. This commit makes us pass all the `css/css-color/lab-00*.html` WPT tests (0 to 7 at the time of writing). [1] Python script used to generate the XYZ D50 -> Linear sRGB conversion: ```python import numpy as np # http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html # First let's convert from D50 to D65 using the Bradford method. m_a = np.array([ [0.8951000, 0.2664000, -0.1614000], [-0.7502000, 1.7135000, 0.0367000], [0.0389000, -0.0685000, 1.0296000] ]) # D50 chromaticities_source = np.array([0.96422, 1, 0.82521]) # D65 chromaticities_destination = np.array([0.9505, 1, 1.0890]) cone_response_source = m_a @ chromaticities_source cone_response_destination = m_a @ chromaticities_destination cone_response_ratio = cone_response_destination / cone_response_source m = np.linalg.inv(m_a) @ np.diagflat(cone_response_ratio) @ m_a D50_to_D65 = m # https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB # Then, the matrix to convert to linear sRGB. xyz_65_to_srgb = np.array([ [3.2406, - 1.5372, - 0.4986], [-0.9689, + 1.8758, 0.0415], [0.0557, - 0.2040, 1.0570] ]) # Finally, let's retrieve the final transformation. xyz_50_to_srgb = xyz_65_to_srgb @ D50_to_D65 print(xyz_50_to_srgb) ```
2024-10-26 18:16:13 -03:00
static Color from_lab(float L, float a, float b, float alpha = 1.0f);
static Color from_linear_display_p3(float r, float g, float b, float alpha = 1.0f);
static Color from_linear_srgb(float r, float g, float b, float alpha = 1.0f);
static Color from_pro_photo_rgb(float r, float g, float b, float alpha = 1.0f);
static Color from_rec2020(float r, float g, float b, float alpha = 1.0f);
LibGfx+LibWeb/CSS: Add support for the `lab()` color function The support in LibWeb is quite easy as the previous commits introduced helpers to support lab-like colors. Now for the methods in Color: - The formulas in `from_lab()` are derived from the CIEXYZ to CIELAB formulas the "Colorimetry" paper published by the CIE. - The conversion in `from_xyz50()` can be decomposed in multiple steps XYZ D50 -> XYZ D65 -> Linear sRGB -> sRGB. The two first conversion are done with a singular matrix operation. This matrix was generated with a Python script [1]. This commit makes us pass all the `css/css-color/lab-00*.html` WPT tests (0 to 7 at the time of writing). [1] Python script used to generate the XYZ D50 -> Linear sRGB conversion: ```python import numpy as np # http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html # First let's convert from D50 to D65 using the Bradford method. m_a = np.array([ [0.8951000, 0.2664000, -0.1614000], [-0.7502000, 1.7135000, 0.0367000], [0.0389000, -0.0685000, 1.0296000] ]) # D50 chromaticities_source = np.array([0.96422, 1, 0.82521]) # D65 chromaticities_destination = np.array([0.9505, 1, 1.0890]) cone_response_source = m_a @ chromaticities_source cone_response_destination = m_a @ chromaticities_destination cone_response_ratio = cone_response_destination / cone_response_source m = np.linalg.inv(m_a) @ np.diagflat(cone_response_ratio) @ m_a D50_to_D65 = m # https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB # Then, the matrix to convert to linear sRGB. xyz_65_to_srgb = np.array([ [3.2406, - 1.5372, - 0.4986], [-0.9689, + 1.8758, 0.0415], [0.0557, - 0.2040, 1.0570] ]) # Finally, let's retrieve the final transformation. xyz_50_to_srgb = xyz_65_to_srgb @ D50_to_D65 print(xyz_50_to_srgb) ```
2024-10-26 18:16:13 -03:00
static Color from_xyz50(float x, float y, float z, float alpha = 1.0f);
static Color from_xyz65(float x, float y, float z, float alpha = 1.0f);
LibGfx+LibWeb/CSS: Add support for the `lab()` color function The support in LibWeb is quite easy as the previous commits introduced helpers to support lab-like colors. Now for the methods in Color: - The formulas in `from_lab()` are derived from the CIEXYZ to CIELAB formulas the "Colorimetry" paper published by the CIE. - The conversion in `from_xyz50()` can be decomposed in multiple steps XYZ D50 -> XYZ D65 -> Linear sRGB -> sRGB. The two first conversion are done with a singular matrix operation. This matrix was generated with a Python script [1]. This commit makes us pass all the `css/css-color/lab-00*.html` WPT tests (0 to 7 at the time of writing). [1] Python script used to generate the XYZ D50 -> Linear sRGB conversion: ```python import numpy as np # http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html # First let's convert from D50 to D65 using the Bradford method. m_a = np.array([ [0.8951000, 0.2664000, -0.1614000], [-0.7502000, 1.7135000, 0.0367000], [0.0389000, -0.0685000, 1.0296000] ]) # D50 chromaticities_source = np.array([0.96422, 1, 0.82521]) # D65 chromaticities_destination = np.array([0.9505, 1, 1.0890]) cone_response_source = m_a @ chromaticities_source cone_response_destination = m_a @ chromaticities_destination cone_response_ratio = cone_response_destination / cone_response_source m = np.linalg.inv(m_a) @ np.diagflat(cone_response_ratio) @ m_a D50_to_D65 = m # https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB # Then, the matrix to convert to linear sRGB. xyz_65_to_srgb = np.array([ [3.2406, - 1.5372, - 0.4986], [-0.9689, + 1.8758, 0.0415], [0.0557, - 0.2040, 1.0570] ]) # Finally, let's retrieve the final transformation. xyz_50_to_srgb = xyz_65_to_srgb @ D50_to_D65 print(xyz_50_to_srgb) ```
2024-10-26 18:16:13 -03:00
// https://bottosson.github.io/posts/oklab/
static constexpr Color from_oklab(float L, float a, float b, float alpha = 1.0f)
{
auto linear = oklab_to_linear_srgb({ L, a, b, alpha });
return from_linear_srgb(linear[0], linear[1], linear[2], linear.alpha());
}
constexpr Oklab to_premultiplied_oklab()
{
auto oklab = to_oklab();
return {
oklab.L * alpha() / 255,
oklab.a * alpha() / 255,
oklab.b * alpha() / 255,
};
}
// https://bottosson.github.io/posts/oklab/
constexpr Oklab to_oklab()
{
auto linear = srgb_to_linear_srgb({ red() / 255.0f, green() / 255.0f, blue() / 255.0f });
auto oklab = linear_srgb_to_oklab(linear);
return { oklab[0], oklab[1], oklab[2] };
}
constexpr u8 red() const { return (m_value >> 16) & 0xff; }
constexpr u8 green() const { return (m_value >> 8) & 0xff; }
constexpr u8 blue() const { return m_value & 0xff; }
constexpr u8 alpha() const { return (m_value >> 24) & 0xff; }
constexpr void set_alpha(u8 value, AlphaType alpha_type = AlphaType::Unpremultiplied)
{
switch (alpha_type) {
case AlphaType::Premultiplied:
m_value = value << 24
| (red() * value / 255) << 16
| (green() * value / 255) << 8
| blue() * value / 255;
break;
case AlphaType::Unpremultiplied:
m_value = (m_value & 0x00ffffff) | value << 24;
break;
default:
VERIFY_NOT_REACHED();
}
}
constexpr void set_red(u8 value)
{
m_value &= 0xff00ffff;
m_value |= value << 16;
}
constexpr void set_green(u8 value)
{
m_value &= 0xffff00ff;
m_value |= value << 8;
}
constexpr void set_blue(u8 value)
{
m_value &= 0xffffff00;
m_value |= value;
}
constexpr Color with_alpha(u8 alpha, AlphaType alpha_type = AlphaType::Unpremultiplied) const
{
Color color_with_alpha = Color(m_value);
color_with_alpha.set_alpha(alpha, alpha_type);
return color_with_alpha;
}
constexpr Color blend(Color source) const
{
if (alpha() == 0 || source.alpha() == 255)
return source;
if (source.alpha() == 0)
return *this;
int const d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha();
u8 r = (red() * alpha() * (255 - source.alpha()) + source.red() * 255 * source.alpha()) / d;
u8 g = (green() * alpha() * (255 - source.alpha()) + source.green() * 255 * source.alpha()) / d;
u8 b = (blue() * alpha() * (255 - source.alpha()) + source.blue() * 255 * source.alpha()) / d;
u8 a = d / 255;
return Color(r, g, b, a);
}
2021-04-20 16:39:48 -03:00
ALWAYS_INLINE Color mixed_with(Color other, float weight) const
{
if (alpha() == other.alpha() || with_alpha(0) == other.with_alpha(0))
return interpolate(other, weight);
// Fallback to slower, but more visually pleasing premultiplied alpha mix.
// This is needed for linear-gradient()s in LibWeb.
auto mixed_alpha = mix<float>(alpha(), other.alpha(), weight);
auto premultiplied_mix_channel = [&](float channel, float other_channel, float weight) {
return round_to<u8>(mix<float>(channel * alpha(), other_channel * other.alpha(), weight) / mixed_alpha);
};
return Gfx::Color {
premultiplied_mix_channel(red(), other.red(), weight),
premultiplied_mix_channel(green(), other.green(), weight),
premultiplied_mix_channel(blue(), other.blue(), weight),
round_to<u8>(mixed_alpha),
};
}
ALWAYS_INLINE Color interpolate(Color other, float weight) const noexcept
2021-04-20 16:39:48 -03:00
{
return Gfx::Color {
round_to<u8>(mix<float>(red(), other.red(), weight)),
round_to<u8>(mix<float>(green(), other.green(), weight)),
round_to<u8>(mix<float>(blue(), other.blue(), weight)),
round_to<u8>(mix<float>(alpha(), other.alpha(), weight)),
};
2021-04-20 16:39:48 -03:00
}
constexpr Color multiply(Color other) const
{
return Color(
red() * other.red() / 255,
green() * other.green() / 255,
blue() * other.blue() / 255,
alpha() * other.alpha() / 255);
}
constexpr float distance_squared_to(Color other) const
{
int delta_red = other.red() - red();
int delta_green = other.green() - green();
int delta_blue = other.blue() - blue();
int delta_alpha = other.alpha() - alpha();
auto rgb_distance = (delta_red * delta_red + delta_green * delta_green + delta_blue * delta_blue) / (3.0f * 255 * 255);
return delta_alpha * delta_alpha / (2.0f * 255 * 255) + rgb_distance * alpha() * other.alpha() / (255 * 255);
}
double relative_luminance() const;
double contrast_ratio(Color other) const;
2022-05-08 20:07:24 -03:00
constexpr Color sepia(float amount = 1.0f) const
{
auto blend_factor = 1.0f - amount;
auto r1 = 0.393f + 0.607f * blend_factor;
auto r2 = 0.769f - 0.769f * blend_factor;
auto r3 = 0.189f - 0.189f * blend_factor;
auto g1 = 0.349f - 0.349f * blend_factor;
auto g2 = 0.686f + 0.314f * blend_factor;
auto g3 = 0.168f - 0.168f * blend_factor;
auto b1 = 0.272f - 0.272f * blend_factor;
auto b2 = 0.534f - 0.534f * blend_factor;
auto b3 = 0.131f + 0.869f * blend_factor;
auto r = red();
auto g = green();
auto b = blue();
return Color(
clamp(lroundf(r * r1 + g * r2 + b * r3), 0, 255),
clamp(lroundf(r * g1 + g * g2 + b * g3), 0, 255),
clamp(lroundf(r * b1 + g * b2 + b * b3), 0, 255),
alpha());
}
constexpr Color with_opacity(float opacity) const
{
VERIFY(opacity >= 0 && opacity <= 1);
return with_alpha(static_cast<u8>(round(alpha() * opacity)));
}
constexpr Color darkened(float amount = 0.5f) const
{
return Color(red() * amount, green() * amount, blue() * amount, alpha());
}
constexpr Color lightened(float amount = 1.2f) const
{
return Color(min(255, (int)((float)red() * amount)), min(255, (int)((float)green() * amount)), min(255, (int)((float)blue() * amount)), alpha());
}
Vector<Color> shades(u32 steps, float max = 1.f) const;
Vector<Color> tints(u32 steps, float max = 1.f) const;
constexpr Color saturated_to(float saturation) const
{
auto hsv = to_hsv();
auto alpha = this->alpha();
auto color = Color::from_hsv(hsv.hue, static_cast<double>(saturation), hsv.value);
color.set_alpha(alpha);
return color;
}
constexpr Color inverted() const
{
return Color(~red(), ~green(), ~blue(), alpha());
}
constexpr Color xored(Color other) const
{
return Color(((other.m_value ^ m_value) & 0x00ffffff) | (m_value & 0xff000000));
}
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
constexpr BGRA8888 value() const { return m_value; }
2018-10-10 11:49:36 -03:00
constexpr bool operator==(Color other) const
2019-06-14 14:10:43 -03:00
{
return m_value == other.m_value;
}
enum class HTMLCompatibleSerialization {
No,
Yes,
};
[[nodiscard]] String to_string(HTMLCompatibleSerialization = HTMLCompatibleSerialization::No) const;
String to_string_without_alpha() const;
Utf16String to_utf16_string_without_alpha() const;
void serialize_a_srgb_value(StringBuilder&) const;
String serialize_a_srgb_value() const;
ByteString to_byte_string() const;
ByteString to_byte_string_without_alpha() const;
static Optional<Color> from_string(StringView);
2025-07-22 12:25:56 -03:00
static Optional<Color> from_utf16_string(Utf16View const&);
static Optional<Color> from_named_css_color_string(StringView);
constexpr HSV to_hsv() const
2020-02-07 14:29:52 -03:00
{
auto hsv = srgb_to_hsv({ red() / 255.0f, green() / 255.0f, blue() / 255.0f });
VERIFY(hsv[0] >= 0.0f && hsv[0] < 360.0f);
VERIFY(hsv[1] >= 0.0f && hsv[1] <= 1.0f);
VERIFY(hsv[2] >= 0.0f && hsv[2] <= 1.0f);
return { hsv[0], hsv[1], hsv[2] };
2020-02-07 14:29:52 -03:00
}
static constexpr Color from_hsv(double hue, double saturation, double value)
2020-02-07 14:29:52 -03:00
{
return from_hsv({ hue, saturation, value });
}
2021-10-24 11:22:26 -03:00
static constexpr Color from_hsv(HSV const& hsv)
2020-02-07 14:29:52 -03:00
{
VERIFY(hsv.hue >= 0.0 && hsv.hue < 360.0);
VERIFY(hsv.saturation >= 0.0 && hsv.saturation <= 1.0);
VERIFY(hsv.value >= 0.0 && hsv.value <= 1.0);
auto srgb = hsv_to_srgb({ static_cast<float>(hsv.hue), static_cast<float>(hsv.saturation), static_cast<float>(hsv.value) });
auto out_r = static_cast<u8>(round(srgb[0] * 255));
auto out_g = static_cast<u8>(round(srgb[1] * 255));
auto out_b = static_cast<u8>(round(srgb[2] * 255));
2020-02-07 14:29:52 -03:00
return Color(out_r, out_g, out_b);
}
Color suggested_foreground_color() const;
2018-10-10 11:49:36 -03:00
private:
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
constexpr explicit Color(BGRA8888 argb)
2022-05-01 14:02:55 -03:00
: m_value(argb)
{
}
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
BGRA8888 m_value { 0 };
2018-10-10 11:49:36 -03:00
};
constexpr Color::Color(NamedColor named)
{
if (named == Transparent) {
m_value = 0;
return;
}
struct {
u8 r;
u8 g;
u8 b;
} rgb;
switch (named) {
case Black:
rgb = { 0, 0, 0 };
break;
case White:
rgb = { 255, 255, 255 };
break;
case Red:
rgb = { 255, 0, 0 };
break;
case Green:
rgb = { 0, 255, 0 };
break;
case Cyan:
rgb = { 0, 255, 255 };
break;
case DarkCyan:
rgb = { 0, 127, 127 };
break;
case MidCyan:
rgb = { 0, 192, 192 };
break;
case Blue:
rgb = { 0, 0, 255 };
break;
case Yellow:
rgb = { 255, 255, 0 };
break;
case Magenta:
rgb = { 255, 0, 255 };
break;
case DarkGray:
rgb = { 64, 64, 64 };
break;
case MidGray:
rgb = { 127, 127, 127 };
break;
case LightGray:
rgb = { 192, 192, 192 };
break;
case MidGreen:
rgb = { 0, 192, 0 };
break;
case MidBlue:
rgb = { 0, 0, 192 };
break;
case MidRed:
rgb = { 192, 0, 0 };
break;
case MidMagenta:
rgb = { 192, 0, 192 };
break;
case DarkGreen:
rgb = { 0, 128, 0 };
break;
case DarkBlue:
rgb = { 0, 0, 128 };
break;
case DarkRed:
rgb = { 128, 0, 0 };
break;
case WarmGray:
rgb = { 212, 208, 200 };
break;
case LightBlue:
rgb = { 173, 216, 230 };
break;
default:
VERIFY_NOT_REACHED();
break;
}
m_value = 0xff000000 | (rgb.r << 16) | (rgb.g << 8) | rgb.b;
}
constexpr Color Color::branded_color(BrandedColor color)
{
// clang-format off
switch (color) {
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 09:07:38 -03:00
case BrandedColor::Indigo10: return from_bgrx(0xa5'a6'f2);
case BrandedColor::Indigo20: return from_bgrx(0x8a'88'eb);
case BrandedColor::Indigo30: return from_bgrx(0x68'51'd6);
case BrandedColor::Indigo40: return from_bgrx(0x55'3f'c4);
case BrandedColor::Indigo50: return from_bgrx(0x4d'37'b8);
case BrandedColor::Indigo60: return from_bgrx(0x3c'28'a1);
case BrandedColor::Indigo80: return from_bgrx(0x30'1f'82);
case BrandedColor::Indigo100: return from_bgrx(0x2a'13'73);
case BrandedColor::Indigo300: return from_bgrx(0x26'0f'73);
case BrandedColor::Indigo500: return from_bgrx(0x1d'0c'59);
case BrandedColor::Indigo900: return from_bgrx(0x19'0c'4a);
case BrandedColor::Violet10: return from_bgrx(0xe0'd4'ff);
case BrandedColor::Violet20: return from_bgrx(0xca'b5'ff);
case BrandedColor::Violet30: return from_bgrx(0xc3'ab'ff);
case BrandedColor::Violet40: return from_bgrx(0xb4'96'ff);
case BrandedColor::Violet50: return from_bgrx(0xab'8e'f5);
case BrandedColor::Violet60: return from_bgrx(0x9d'7c'f2);
case BrandedColor::Violet80: return from_bgrx(0x93'6f'ed);
case BrandedColor::Violet100: return from_bgrx(0x8a'64'e5);
case BrandedColor::Violet300: return from_bgrx(0x82'57'e6);
case BrandedColor::Violet500: return from_bgrx(0x7a'4c'e6);
case BrandedColor::Violet900: return from_bgrx(0x6a'39'db);
case BrandedColor::SlateBlue10: return from_bgrx(0xcb'e0'f7);
case BrandedColor::SlateBlue20: return from_bgrx(0xc1'd9'f5);
case BrandedColor::SlateBlue30: return from_bgrx(0xb6'd2'f2);
case BrandedColor::SlateBlue40: return from_bgrx(0xa8'c8'ed);
case BrandedColor::SlateBlue50: return from_bgrx(0x97'bc'e6);
case BrandedColor::SlateBlue60: return from_bgrx(0x86'ad'd9);
case BrandedColor::SlateBlue80: return from_bgrx(0x77'a1'd1);
case BrandedColor::SlateBlue100: return from_bgrx(0x6d'98'cc);
case BrandedColor::SlateBlue300: return from_bgrx(0x5c'8e'cc);
case BrandedColor::SlateBlue500: return from_bgrx(0x54'84'bf);
case BrandedColor::SlateBlue900: return from_bgrx(0x48'72'a3);
}
// clang-format on
VERIFY_NOT_REACHED();
}
}
using Gfx::Color;
2020-10-26 12:47:38 -03:00
namespace AK {
2024-05-14 13:21:52 -03:00
template<>
class Traits<Color> : public DefaultTraits<Color> {
public:
static unsigned hash(Color const& color)
{
return u32_hash(color.value());
2024-05-14 13:21:52 -03:00
}
};
2020-10-26 12:47:38 -03:00
template<>
struct Formatter<Gfx::Color> : public Formatter<StringView> {
ErrorOr<void> format(FormatBuilder&, Gfx::Color);
2020-10-26 12:47:38 -03:00
};
template<>
struct Formatter<Gfx::YUV> : public Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder&, Gfx::YUV);
};
template<>
struct Formatter<Gfx::HSV> : public Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder&, Gfx::HSV);
};
template<>
struct Formatter<Gfx::Oklab> : public Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder&, Gfx::Oklab);
};
2020-10-26 12:47:38 -03:00
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, Gfx::Color const&);
template<>
ErrorOr<Gfx::Color> decode(Decoder&);
}