/* * Copyright (c) 2018-2020, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace Gfx { DeprecatedString Color::to_string() const { return DeprecatedString::formatted("#{:02x}{:02x}{:02x}{:02x}", red(), green(), blue(), alpha()); } DeprecatedString Color::to_string_without_alpha() const { return DeprecatedString::formatted("#{:02x}{:02x}{:02x}", red(), green(), blue()); } static Optional 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 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::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 { if (!isxdigit(nibble)) return {}; if (nibble >= '0' && nibble <= '9') return nibble - '0'; return 10 + (tolower(nibble) - 'a'); }; if (string.length() == 4) { Optional r = hex_nibble_to_u8(string[1]); Optional g = hex_nibble_to_u8(string[2]); Optional 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 r = hex_nibble_to_u8(string[1]); Optional g = hex_nibble_to_u8(string[2]); Optional b = hex_nibble_to_u8(string[3]); Optional 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 { 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 r = to_hex(string[1], string[2]); Optional g = to_hex(string[3], string[4]); Optional b = to_hex(string[5], string[6]); Optional a = string.length() == 9 ? to_hex(string[7], string[8]) : Optional(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 const& other, float weight) const { if (alpha() == other.alpha() || with_alpha(0) == other.with_alpha(0)) { return Gfx::Color { round_to(mix(red(), other.red(), weight)), round_to(mix(green(), other.green(), weight)), round_to(mix(blue(), other.blue(), weight)), round_to(mix(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(alpha(), other.alpha(), weight); auto premultiplied_mix_channel = [&](float channel, float other_channel, float weight) { return round_to(mix(channel * (alpha() / 255.0f), other_channel * (other.alpha() / 255.0f), weight) / (mixed_alpha / 255.0f)); }; 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(mixed_alpha), }; } Vector Color::shades(u32 steps, float max) const { float shade = 1.f; float step = max / steps; Vector shades; for (u32 i = 0; i < steps; i++) { shade -= step; shades.append(this->darkened(shade)); } return shades; } Vector Color::tints(u32 steps, float max) const { float shade = 1.f; float step = max / steps; Vector 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 IPC::decode(Decoder& decoder, Color& color) { u32 rgba; TRY(decoder.decode(rgba)); color = Color::from_argb(rgba); return {}; } ErrorOr AK::Formatter::format(FormatBuilder& builder, Gfx::Color const& value) { return Formatter::format(builder, value.to_string()); }