
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).
386 lines
12 KiB
C++
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());
|
|
}
|