ladybird/Userland/Libraries/LibGfx/Color.cpp
Timothy Flynn 9b483625e6 LibIPC+Everywhere: Change IPC decoders to construct values in-place
Currently, the generated IPC decoders will default-construct the type to
be decoded, then pass that value by reference to the concrete decoder.
This, of course, requires that the type is default-constructible. This
was an issue for decoding Variants, which had to require the first type
in the Variant list is Empty, to ensure it is default constructible.

Further, this made it possible for values to become uninitialized in
user-defined decoders.

This patch makes the decoder interface such that the concrete decoders
themselves contruct the decoded type upon return from the decoder. To do
so, the default decoders in IPC::Decoder had to be moved to the IPC
namespace scope, as these decoders are now specializations instead of
overloaded methods (C++ requires specializations to be in a namespace
scope).
2022-12-26 09:36:16 +01:00

386 lines
12 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/DeprecatedString.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/Optional.h>
#include <AK/Vector.h>
#include <LibGfx/Color.h>
#include <LibGfx/SystemTheme.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <ctype.h>
#include <stdlib.h>
namespace Gfx {
DeprecatedString Color::to_deprecated_string() const
{
return DeprecatedString::formatted("#{:02x}{:02x}{:02x}{:02x}", red(), green(), blue(), alpha());
}
DeprecatedString Color::to_deprecated_string_without_alpha() const
{
return DeprecatedString::formatted("#{:02x}{:02x}{:02x}", red(), green(), blue());
}
static Optional<Color> parse_rgb_color(StringView string)
{
VERIFY(string.starts_with("rgb("sv, CaseSensitivity::CaseInsensitive));
VERIFY(string.ends_with(')'));
auto substring = string.substring_view(4, string.length() - 5);
auto parts = substring.split_view(',');
if (parts.size() != 3)
return {};
auto r = parts[0].to_uint().value_or(256);
auto g = parts[1].to_uint().value_or(256);
auto b = parts[2].to_uint().value_or(256);
if (r > 255 || g > 255 || b > 255)
return {};
return Color(r, g, b);
}
static Optional<Color> parse_rgba_color(StringView string)
{
VERIFY(string.starts_with("rgba("sv, CaseSensitivity::CaseInsensitive));
VERIFY(string.ends_with(')'));
auto substring = string.substring_view(5, string.length() - 6);
auto parts = substring.split_view(',');
if (parts.size() != 4)
return {};
auto r = parts[0].to_int().value_or(256);
auto g = parts[1].to_int().value_or(256);
auto b = parts[2].to_int().value_or(256);
double alpha = 0;
char const* start = parts[3].characters_without_null_termination();
auto alpha_result = parse_first_floating_point(start, start + parts[3].length());
if (alpha_result.parsed_value())
alpha = alpha_result.value;
unsigned a = alpha * 255;
if (r > 255 || g > 255 || b > 255 || a > 255)
return {};
return Color(r, g, b, a);
}
Optional<Color> Color::from_string(StringView string)
{
if (string.is_empty())
return {};
struct ColorAndWebName {
constexpr ColorAndWebName(ARGB32 c, char const* n)
: color(c)
, name(n != nullptr ? StringView { n, __builtin_strlen(n) } : StringView {})
{
}
ARGB32 color;
StringView name;
};
constexpr ColorAndWebName web_colors[] = {
// CSS Level 1
{ 0x000000, "black" },
{ 0xc0c0c0, "silver" },
{ 0x808080, "gray" },
{ 0xffffff, "white" },
{ 0x800000, "maroon" },
{ 0xff0000, "red" },
{ 0x800080, "purple" },
{ 0xff00ff, "fuchsia" },
{ 0x008000, "green" },
{ 0x00ff00, "lime" },
{ 0x808000, "olive" },
{ 0xffff00, "yellow" },
{ 0x000080, "navy" },
{ 0x0000ff, "blue" },
{ 0x008080, "teal" },
{ 0x00ffff, "aqua" },
// CSS Level 2 (Revision 1)
{ 0xffa500, "orange" },
// CSS Color Module Level 3
{ 0xf0f8ff, "aliceblue" },
{ 0xfaebd7, "antiquewhite" },
{ 0x7fffd4, "aquamarine" },
{ 0xf0ffff, "azure" },
{ 0xf5f5dc, "beige" },
{ 0xffe4c4, "bisque" },
{ 0xffebcd, "blanchedalmond" },
{ 0x8a2be2, "blueviolet" },
{ 0xa52a2a, "brown" },
{ 0xdeb887, "burlywood" },
{ 0x5f9ea0, "cadetblue" },
{ 0x7fff00, "chartreuse" },
{ 0xd2691e, "chocolate" },
{ 0xff7f50, "coral" },
{ 0x6495ed, "cornflowerblue" },
{ 0xfff8dc, "cornsilk" },
{ 0xdc143c, "crimson" },
{ 0x00ffff, "cyan" },
{ 0x00008b, "darkblue" },
{ 0x008b8b, "darkcyan" },
{ 0xb8860b, "darkgoldenrod" },
{ 0xa9a9a9, "darkgray" },
{ 0x006400, "darkgreen" },
{ 0xa9a9a9, "darkgrey" },
{ 0xbdb76b, "darkkhaki" },
{ 0x8b008b, "darkmagenta" },
{ 0x556b2f, "darkolivegreen" },
{ 0xff8c00, "darkorange" },
{ 0x9932cc, "darkorchid" },
{ 0x8b0000, "darkred" },
{ 0xe9967a, "darksalmon" },
{ 0x8fbc8f, "darkseagreen" },
{ 0x483d8b, "darkslateblue" },
{ 0x2f4f4f, "darkslategray" },
{ 0x2f4f4f, "darkslategrey" },
{ 0x00ced1, "darkturquoise" },
{ 0x9400d3, "darkviolet" },
{ 0xff1493, "deeppink" },
{ 0x00bfff, "deepskyblue" },
{ 0x696969, "dimgray" },
{ 0x696969, "dimgrey" },
{ 0x1e90ff, "dodgerblue" },
{ 0xb22222, "firebrick" },
{ 0xfffaf0, "floralwhite" },
{ 0x228b22, "forestgreen" },
{ 0xdcdcdc, "gainsboro" },
{ 0xf8f8ff, "ghostwhite" },
{ 0xffd700, "gold" },
{ 0xdaa520, "goldenrod" },
{ 0xadff2f, "greenyellow" },
{ 0x808080, "grey" },
{ 0xf0fff0, "honeydew" },
{ 0xff69b4, "hotpink" },
{ 0xcd5c5c, "indianred" },
{ 0x4b0082, "indigo" },
{ 0xfffff0, "ivory" },
{ 0xf0e68c, "khaki" },
{ 0xe6e6fa, "lavender" },
{ 0xfff0f5, "lavenderblush" },
{ 0x7cfc00, "lawngreen" },
{ 0xfffacd, "lemonchiffon" },
{ 0xadd8e6, "lightblue" },
{ 0xf08080, "lightcoral" },
{ 0xe0ffff, "lightcyan" },
{ 0xfafad2, "lightgoldenrodyellow" },
{ 0xd3d3d3, "lightgray" },
{ 0x90ee90, "lightgreen" },
{ 0xd3d3d3, "lightgrey" },
{ 0xffb6c1, "lightpink" },
{ 0xffa07a, "lightsalmon" },
{ 0x20b2aa, "lightseagreen" },
{ 0x87cefa, "lightskyblue" },
{ 0x778899, "lightslategray" },
{ 0x778899, "lightslategrey" },
{ 0xb0c4de, "lightsteelblue" },
{ 0xffffe0, "lightyellow" },
{ 0x32cd32, "limegreen" },
{ 0xfaf0e6, "linen" },
{ 0xff00ff, "magenta" },
{ 0x66cdaa, "mediumaquamarine" },
{ 0x0000cd, "mediumblue" },
{ 0xba55d3, "mediumorchid" },
{ 0x9370db, "mediumpurple" },
{ 0x3cb371, "mediumseagreen" },
{ 0x7b68ee, "mediumslateblue" },
{ 0x00fa9a, "mediumspringgreen" },
{ 0x48d1cc, "mediumturquoise" },
{ 0xc71585, "mediumvioletred" },
{ 0x191970, "midnightblue" },
{ 0xf5fffa, "mintcream" },
{ 0xffe4e1, "mistyrose" },
{ 0xffe4b5, "moccasin" },
{ 0xffdead, "navajowhite" },
{ 0xfdf5e6, "oldlace" },
{ 0x6b8e23, "olivedrab" },
{ 0xff4500, "orangered" },
{ 0xda70d6, "orchid" },
{ 0xeee8aa, "palegoldenrod" },
{ 0x98fb98, "palegreen" },
{ 0xafeeee, "paleturquoise" },
{ 0xdb7093, "palevioletred" },
{ 0xffefd5, "papayawhip" },
{ 0xffdab9, "peachpuff" },
{ 0xcd853f, "peru" },
{ 0xffc0cb, "pink" },
{ 0xdda0dd, "plum" },
{ 0xb0e0e6, "powderblue" },
{ 0xbc8f8f, "rosybrown" },
{ 0x4169e1, "royalblue" },
{ 0x8b4513, "saddlebrown" },
{ 0xfa8072, "salmon" },
{ 0xf4a460, "sandybrown" },
{ 0x2e8b57, "seagreen" },
{ 0xfff5ee, "seashell" },
{ 0xa0522d, "sienna" },
{ 0x87ceeb, "skyblue" },
{ 0x6a5acd, "slateblue" },
{ 0x708090, "slategray" },
{ 0x708090, "slategrey" },
{ 0xfffafa, "snow" },
{ 0x00ff7f, "springgreen" },
{ 0x4682b4, "steelblue" },
{ 0xd2b48c, "tan" },
{ 0xd8bfd8, "thistle" },
{ 0xff6347, "tomato" },
{ 0x40e0d0, "turquoise" },
{ 0xee82ee, "violet" },
{ 0xf5deb3, "wheat" },
{ 0xf5f5f5, "whitesmoke" },
{ 0x9acd32, "yellowgreen" },
// CSS Color Module Level 4
{ 0x663399, "rebeccapurple" },
// (Fallback)
{ 0x000000, nullptr }
};
if (string.equals_ignoring_case("transparent"sv))
return Color::from_argb(0x00000000);
for (size_t i = 0; !web_colors[i].name.is_null(); ++i) {
if (string.equals_ignoring_case(web_colors[i].name))
return Color::from_rgb(web_colors[i].color);
}
if (string.starts_with("rgb("sv, CaseSensitivity::CaseInsensitive) && string.ends_with(')'))
return parse_rgb_color(string);
if (string.starts_with("rgba("sv, CaseSensitivity::CaseInsensitive) && string.ends_with(')'))
return parse_rgba_color(string);
if (string[0] != '#')
return {};
auto hex_nibble_to_u8 = [](char nibble) -> Optional<u8> {
if (!isxdigit(nibble))
return {};
if (nibble >= '0' && nibble <= '9')
return nibble - '0';
return 10 + (tolower(nibble) - 'a');
};
if (string.length() == 4) {
Optional<u8> r = hex_nibble_to_u8(string[1]);
Optional<u8> g = hex_nibble_to_u8(string[2]);
Optional<u8> b = hex_nibble_to_u8(string[3]);
if (!r.has_value() || !g.has_value() || !b.has_value())
return {};
return Color(r.value() * 17, g.value() * 17, b.value() * 17);
}
if (string.length() == 5) {
Optional<u8> r = hex_nibble_to_u8(string[1]);
Optional<u8> g = hex_nibble_to_u8(string[2]);
Optional<u8> b = hex_nibble_to_u8(string[3]);
Optional<u8> a = hex_nibble_to_u8(string[4]);
if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
return {};
return Color(r.value() * 17, g.value() * 17, b.value() * 17, a.value() * 17);
}
if (string.length() != 7 && string.length() != 9)
return {};
auto to_hex = [&](char c1, char c2) -> Optional<u8> {
auto nib1 = hex_nibble_to_u8(c1);
auto nib2 = hex_nibble_to_u8(c2);
if (!nib1.has_value() || !nib2.has_value())
return {};
return nib1.value() << 4 | nib2.value();
};
Optional<u8> r = to_hex(string[1], string[2]);
Optional<u8> g = to_hex(string[3], string[4]);
Optional<u8> b = to_hex(string[5], string[6]);
Optional<u8> a = string.length() == 9 ? to_hex(string[7], string[8]) : Optional<u8>(255);
if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
return {};
return Color(r.value(), g.value(), b.value(), a.value());
}
Color Color::mixed_with(Color other, float weight) const
{
if (alpha() == other.alpha() || with_alpha(0) == other.with_alpha(0)) {
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)),
};
}
// 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),
};
}
Vector<Color> Color::shades(u32 steps, float max) const
{
float shade = 1.f;
float step = max / steps;
Vector<Color> shades;
for (u32 i = 0; i < steps; i++) {
shade -= step;
shades.append(this->darkened(shade));
}
return shades;
}
Vector<Color> Color::tints(u32 steps, float max) const
{
float shade = 1.f;
float step = max / steps;
Vector<Color> tints;
for (u32 i = 0; i < steps; i++) {
shade += step;
tints.append(this->lightened(shade));
}
return tints;
}
}
template<>
bool IPC::encode(Encoder& encoder, Color const& color)
{
encoder << color.value();
return true;
}
template<>
ErrorOr<Gfx::Color> IPC::decode(Decoder& decoder)
{
auto rgba = TRY(decoder.decode<u32>());
return Gfx::Color::from_argb(rgba);
}
ErrorOr<void> AK::Formatter<Gfx::Color>::format(FormatBuilder& builder, Gfx::Color value)
{
return Formatter<StringView>::format(builder, value.to_deprecated_string());
}