mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 17:27:35 +00:00
LibGfx: Rename from LibDraw :^)
This commit is contained in:
parent
11580babbf
commit
9ac94d393e
215 changed files with 291 additions and 291 deletions
46
Libraries/LibGfx/CharacterBitmap.cpp
Normal file
46
Libraries/LibGfx/CharacterBitmap.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "CharacterBitmap.h"
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
CharacterBitmap::CharacterBitmap(const char* ascii_data, unsigned width, unsigned height)
|
||||
: m_bits(ascii_data)
|
||||
, m_size(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
CharacterBitmap::~CharacterBitmap()
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<CharacterBitmap> CharacterBitmap::create_from_ascii(const char* asciiData, unsigned width, unsigned height)
|
||||
{
|
||||
return adopt(*new CharacterBitmap(asciiData, width, height));
|
||||
}
|
||||
|
||||
}
|
54
Libraries/LibGfx/CharacterBitmap.h
Normal file
54
Libraries/LibGfx/CharacterBitmap.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Size.h"
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class CharacterBitmap : public RefCounted<CharacterBitmap> {
|
||||
public:
|
||||
static NonnullRefPtr<CharacterBitmap> create_from_ascii(const char* asciiData, unsigned width, unsigned height);
|
||||
~CharacterBitmap();
|
||||
|
||||
bool bit_at(unsigned x, unsigned y) const { return m_bits[y * width() + x] == '#'; }
|
||||
const char* bits() const { return m_bits; }
|
||||
|
||||
Size size() const { return m_size; }
|
||||
unsigned width() const { return m_size.width(); }
|
||||
unsigned height() const { return m_size.height(); }
|
||||
|
||||
private:
|
||||
CharacterBitmap(const char* b, unsigned w, unsigned h);
|
||||
|
||||
const char* m_bits { nullptr };
|
||||
Size m_size;
|
||||
};
|
||||
|
||||
}
|
340
Libraries/LibGfx/Color.cpp
Normal file
340
Libraries/LibGfx/Color.cpp
Normal file
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/SystemTheme.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
Color::Color(NamedColor named)
|
||||
{
|
||||
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;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
|
||||
m_value = 0xff000000 | (rgb.r << 16) | (rgb.g << 8) | rgb.b;
|
||||
}
|
||||
|
||||
String Color::to_string() const
|
||||
{
|
||||
return String::format("#%b%b%b%b", red(), green(), blue(), alpha());
|
||||
}
|
||||
|
||||
Optional<Color> Color::from_string(const StringView& string)
|
||||
{
|
||||
if (string.is_empty())
|
||||
return {};
|
||||
|
||||
struct ColorAndWebName {
|
||||
RGBA32 color;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
const 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, "lightgoldenrody" },
|
||||
{ 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, "mediumaquamarin" },
|
||||
{ 0x0000cd, "mediumblue" },
|
||||
{ 0xba55d3, "mediumorchid" },
|
||||
{ 0x9370db, "mediumpurple" },
|
||||
{ 0x3cb371, "mediumseagreen" },
|
||||
{ 0x7b68ee, "mediumslateblue" },
|
||||
{ 0x00fa9a, "mediumspringgre" },
|
||||
{ 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 }
|
||||
};
|
||||
|
||||
for (size_t i = 0; web_colors[i].name; ++i) {
|
||||
if (string == web_colors[i].name)
|
||||
return Color::from_rgb(web_colors[i].color);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
185
Libraries/LibGfx/Color.h
Normal file
185
Libraries/LibGfx/Color.h
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class ColorRole;
|
||||
typedef u32 RGBA32;
|
||||
|
||||
inline constexpr u32 make_rgb(u8 r, u8 g, u8 b)
|
||||
{
|
||||
return ((r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
class Color {
|
||||
public:
|
||||
enum NamedColor {
|
||||
Black,
|
||||
White,
|
||||
Red,
|
||||
Green,
|
||||
Cyan,
|
||||
Blue,
|
||||
Yellow,
|
||||
Magenta,
|
||||
DarkGray,
|
||||
MidGray,
|
||||
LightGray,
|
||||
WarmGray,
|
||||
DarkCyan,
|
||||
DarkGreen,
|
||||
DarkBlue,
|
||||
DarkRed,
|
||||
MidCyan,
|
||||
MidGreen,
|
||||
MidRed,
|
||||
MidBlue,
|
||||
MidMagenta,
|
||||
};
|
||||
|
||||
Color() {}
|
||||
Color(NamedColor);
|
||||
Color(u8 r, u8 g, u8 b)
|
||||
: m_value(0xff000000 | (r << 16) | (g << 8) | b)
|
||||
{
|
||||
}
|
||||
Color(u8 r, u8 g, u8 b, u8 a)
|
||||
: m_value((a << 24) | (r << 16) | (g << 8) | b)
|
||||
{
|
||||
}
|
||||
|
||||
static Color from_rgb(unsigned rgb) { return Color(rgb | 0xff000000); }
|
||||
static Color from_rgba(unsigned rgba) { return Color(rgba); }
|
||||
|
||||
u8 red() const { return (m_value >> 16) & 0xff; }
|
||||
u8 green() const { return (m_value >> 8) & 0xff; }
|
||||
u8 blue() const { return m_value & 0xff; }
|
||||
u8 alpha() const { return (m_value >> 24) & 0xff; }
|
||||
|
||||
void set_alpha(u8 value)
|
||||
{
|
||||
m_value &= 0x00ffffff;
|
||||
m_value |= value << 24;
|
||||
}
|
||||
|
||||
void set_red(u8 value)
|
||||
{
|
||||
m_value &= 0xff00ffff;
|
||||
m_value |= value << 16;
|
||||
}
|
||||
|
||||
void set_green(u8 value)
|
||||
{
|
||||
m_value &= 0xffff00ff;
|
||||
m_value |= value << 8;
|
||||
}
|
||||
|
||||
void set_blue(u8 value)
|
||||
{
|
||||
m_value &= 0xffffff00;
|
||||
m_value |= value;
|
||||
}
|
||||
|
||||
Color with_alpha(u8 alpha)
|
||||
{
|
||||
return Color((m_value & 0x00ffffff) | alpha << 24);
|
||||
}
|
||||
|
||||
Color blend(Color source) const
|
||||
{
|
||||
if (!alpha() || source.alpha() == 255)
|
||||
return source;
|
||||
|
||||
if (!source.alpha())
|
||||
return *this;
|
||||
|
||||
int d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha();
|
||||
u8 r = (red() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.red()) / d;
|
||||
u8 g = (green() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.green()) / d;
|
||||
u8 b = (blue() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.blue()) / d;
|
||||
u8 a = d / 255;
|
||||
return Color(r, g, b, a);
|
||||
}
|
||||
|
||||
Color to_grayscale() const
|
||||
{
|
||||
int gray = (red() + green() + blue()) / 3;
|
||||
return Color(gray, gray, gray, alpha());
|
||||
}
|
||||
|
||||
Color darkened(float amount = 0.5f) const
|
||||
{
|
||||
return Color(red() * amount, green() * amount, blue() * amount, alpha());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
Color inverted() const
|
||||
{
|
||||
return Color(~red(), ~green(), ~blue());
|
||||
}
|
||||
|
||||
RGBA32 value() const { return m_value; }
|
||||
|
||||
bool operator==(const Color& other) const
|
||||
{
|
||||
return m_value == other.m_value;
|
||||
}
|
||||
|
||||
bool operator!=(const Color& other) const
|
||||
{
|
||||
return m_value != other.m_value;
|
||||
}
|
||||
|
||||
String to_string() const;
|
||||
static Optional<Color> from_string(const StringView&);
|
||||
|
||||
private:
|
||||
explicit Color(RGBA32 rgba)
|
||||
: m_value(rgba)
|
||||
{
|
||||
}
|
||||
|
||||
RGBA32 m_value { 0 };
|
||||
};
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, Color value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::Color;
|
75
Libraries/LibGfx/DisjointRectSet.cpp
Normal file
75
Libraries/LibGfx/DisjointRectSet.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/DisjointRectSet.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
void DisjointRectSet::add(const Rect& new_rect)
|
||||
{
|
||||
for (auto& rect : m_rects) {
|
||||
if (rect.contains(new_rect))
|
||||
return;
|
||||
}
|
||||
|
||||
m_rects.append(new_rect);
|
||||
if (m_rects.size() > 1)
|
||||
shatter();
|
||||
}
|
||||
|
||||
void DisjointRectSet::shatter()
|
||||
{
|
||||
Vector<Rect, 32> output;
|
||||
output.ensure_capacity(m_rects.size());
|
||||
bool pass_had_intersections = false;
|
||||
do {
|
||||
pass_had_intersections = false;
|
||||
output.clear_with_capacity();
|
||||
for (int i = 0; i < m_rects.size(); ++i) {
|
||||
auto& r1 = m_rects[i];
|
||||
for (int j = 0; j < m_rects.size(); ++j) {
|
||||
if (i == j)
|
||||
continue;
|
||||
auto& r2 = m_rects[j];
|
||||
if (!r1.intersects(r2))
|
||||
continue;
|
||||
pass_had_intersections = true;
|
||||
auto pieces = r1.shatter(r2);
|
||||
for (auto& piece : pieces)
|
||||
output.append(piece);
|
||||
m_rects.remove(i);
|
||||
for (; i < m_rects.size(); ++i)
|
||||
output.append(m_rects[i]);
|
||||
goto next_pass;
|
||||
}
|
||||
output.append(r1);
|
||||
}
|
||||
next_pass:
|
||||
swap(output, m_rects);
|
||||
} while (pass_had_intersections);
|
||||
}
|
||||
|
||||
}
|
58
Libraries/LibGfx/DisjointRectSet.h
Normal file
58
Libraries/LibGfx/DisjointRectSet.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class DisjointRectSet {
|
||||
public:
|
||||
DisjointRectSet() {}
|
||||
~DisjointRectSet() {}
|
||||
DisjointRectSet(DisjointRectSet&& other)
|
||||
: m_rects(move(other.m_rects))
|
||||
{
|
||||
}
|
||||
|
||||
void add(const Rect&);
|
||||
|
||||
bool is_empty() const { return m_rects.is_empty(); }
|
||||
int size() const { return m_rects.size(); }
|
||||
|
||||
void clear() { m_rects.clear(); }
|
||||
void clear_with_capacity() { m_rects.clear_with_capacity(); }
|
||||
const Vector<Rect, 32>& rects() const { return m_rects; }
|
||||
|
||||
private:
|
||||
void shatter();
|
||||
|
||||
Vector<Rect, 32> m_rects;
|
||||
};
|
||||
|
||||
}
|
54
Libraries/LibGfx/Emoji.cpp
Normal file
54
Libraries/LibGfx/Emoji.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Emoji.h>
|
||||
#include <LibGfx/GraphicsBitmap.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
static HashMap<u32, RefPtr<Gfx::Bitmap>> s_emojis;
|
||||
|
||||
const Bitmap* Emoji::emoji_for_codepoint(u32 codepoint)
|
||||
{
|
||||
auto it = s_emojis.find(codepoint);
|
||||
if (it != s_emojis.end())
|
||||
return (*it).value.ptr();
|
||||
|
||||
String path = String::format("/res/emoji/U+%X.png", codepoint);
|
||||
|
||||
auto bitmap = Bitmap::load_from_file(path);
|
||||
if (!bitmap) {
|
||||
s_emojis.set(codepoint, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
s_emojis.set(codepoint, bitmap);
|
||||
return bitmap.ptr();
|
||||
}
|
||||
|
||||
}
|
40
Libraries/LibGfx/Emoji.h
Normal file
40
Libraries/LibGfx/Emoji.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Bitmap;
|
||||
|
||||
class Emoji {
|
||||
public:
|
||||
static const Gfx::Bitmap* emoji_for_codepoint(u32 codepoint);
|
||||
};
|
||||
|
||||
}
|
149
Libraries/LibGfx/FloatPoint.h
Normal file
149
Libraries/LibGfx/FloatPoint.h
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class FloatRect;
|
||||
|
||||
class FloatPoint {
|
||||
public:
|
||||
FloatPoint() {}
|
||||
FloatPoint(float x, float y)
|
||||
: m_x(x)
|
||||
, m_y(y)
|
||||
{
|
||||
}
|
||||
float x() const { return m_x; }
|
||||
float y() const { return m_y; }
|
||||
|
||||
void set_x(float x) { m_x = x; }
|
||||
void set_y(float y) { m_y = y; }
|
||||
|
||||
void move_by(float dx, float dy)
|
||||
{
|
||||
m_x += dx;
|
||||
m_y += dy;
|
||||
}
|
||||
|
||||
void move_by(const FloatPoint& delta)
|
||||
{
|
||||
move_by(delta.x(), delta.y());
|
||||
}
|
||||
|
||||
FloatPoint translated(const FloatPoint& delta) const
|
||||
{
|
||||
FloatPoint point = *this;
|
||||
point.move_by(delta);
|
||||
return point;
|
||||
}
|
||||
|
||||
FloatPoint translated(float dx, float dy) const
|
||||
{
|
||||
FloatPoint point = *this;
|
||||
point.move_by(dx, dy);
|
||||
return point;
|
||||
}
|
||||
|
||||
void constrain(const FloatRect&);
|
||||
|
||||
bool operator==(const FloatPoint& other) const
|
||||
{
|
||||
return m_x == other.m_x
|
||||
&& m_y == other.m_y;
|
||||
}
|
||||
|
||||
bool operator!=(const FloatPoint& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
FloatPoint operator-() const { return { -m_x, -m_y }; }
|
||||
|
||||
FloatPoint operator-(const FloatPoint& other) const { return { m_x - other.m_x, m_y - other.m_y }; }
|
||||
FloatPoint& operator-=(const FloatPoint& other)
|
||||
{
|
||||
m_x -= other.m_x;
|
||||
m_y -= other.m_y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FloatPoint& operator+=(const FloatPoint& other)
|
||||
{
|
||||
m_x += other.m_x;
|
||||
m_y += other.m_y;
|
||||
return *this;
|
||||
}
|
||||
FloatPoint operator+(const FloatPoint& other) const { return { m_x + other.m_x, m_y + other.m_y }; }
|
||||
|
||||
String to_string() const { return String::format("[%g,%g]", x(), y()); }
|
||||
|
||||
bool is_null() const { return !m_x && !m_y; }
|
||||
|
||||
float primary_offset_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? y() : x();
|
||||
}
|
||||
|
||||
void set_primary_offset_for_orientation(Orientation orientation, float value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_y(value);
|
||||
else
|
||||
set_x(value);
|
||||
}
|
||||
|
||||
float secondary_offset_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? x() : y();
|
||||
}
|
||||
|
||||
void set_secondary_offset_for_orientation(Orientation orientation, float value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_x(value);
|
||||
else
|
||||
set_y(value);
|
||||
}
|
||||
|
||||
private:
|
||||
float m_x { 0 };
|
||||
float m_y { 0 };
|
||||
};
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const FloatPoint& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::FloatPoint;
|
332
Libraries/LibGfx/FloatRect.h
Normal file
332
Libraries/LibGfx/FloatRect.h
Normal file
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/FloatPoint.h>
|
||||
#include <LibGfx/FloatSize.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/TextAlignment.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class FloatRect {
|
||||
public:
|
||||
FloatRect() {}
|
||||
FloatRect(float x, float y, float width, float height)
|
||||
: m_location(x, y)
|
||||
, m_size(width, height)
|
||||
{
|
||||
}
|
||||
FloatRect(const FloatPoint& location, const FloatSize& size)
|
||||
: m_location(location)
|
||||
, m_size(size)
|
||||
{
|
||||
}
|
||||
FloatRect(const FloatRect& other)
|
||||
: m_location(other.m_location)
|
||||
, m_size(other.m_size)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_null() const
|
||||
{
|
||||
return width() == 0 && height() == 0;
|
||||
}
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
return width() <= 0 || height() <= 0;
|
||||
}
|
||||
|
||||
void move_by(float dx, float dy)
|
||||
{
|
||||
m_location.move_by(dx, dy);
|
||||
}
|
||||
|
||||
void move_by(const FloatPoint& delta)
|
||||
{
|
||||
m_location.move_by(delta);
|
||||
}
|
||||
|
||||
FloatPoint center() const
|
||||
{
|
||||
return { x() + width() / 2, y() + height() / 2 };
|
||||
}
|
||||
|
||||
void set_location(const FloatPoint& location)
|
||||
{
|
||||
m_location = location;
|
||||
}
|
||||
|
||||
void set_size(const FloatSize& size)
|
||||
{
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void set_size(float width, float height)
|
||||
{
|
||||
m_size.set_width(width);
|
||||
m_size.set_height(height);
|
||||
}
|
||||
|
||||
void inflate(float w, float h)
|
||||
{
|
||||
set_x(x() - w / 2);
|
||||
set_width(width() + w);
|
||||
set_y(y() - h / 2);
|
||||
set_height(height() + h);
|
||||
}
|
||||
|
||||
void shrink(float w, float h)
|
||||
{
|
||||
set_x(x() + w / 2);
|
||||
set_width(width() - w);
|
||||
set_y(y() + h / 2);
|
||||
set_height(height() - h);
|
||||
}
|
||||
|
||||
FloatRect shrunken(float w, float h) const
|
||||
{
|
||||
FloatRect rect = *this;
|
||||
rect.shrink(w, h);
|
||||
return rect;
|
||||
}
|
||||
|
||||
FloatRect inflated(float w, float h) const
|
||||
{
|
||||
FloatRect rect = *this;
|
||||
rect.inflate(w, h);
|
||||
return rect;
|
||||
}
|
||||
|
||||
FloatRect translated(float dx, float dy) const
|
||||
{
|
||||
FloatRect rect = *this;
|
||||
rect.move_by(dx, dy);
|
||||
return rect;
|
||||
}
|
||||
|
||||
FloatRect translated(const FloatPoint& delta) const
|
||||
{
|
||||
FloatRect rect = *this;
|
||||
rect.move_by(delta);
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool contains_vertically(float y) const
|
||||
{
|
||||
return y >= top() && y <= bottom();
|
||||
}
|
||||
|
||||
bool contains_horizontally(float x) const
|
||||
{
|
||||
return x >= left() && x <= right();
|
||||
}
|
||||
|
||||
bool contains(float x, float y) const
|
||||
{
|
||||
return x >= m_location.x() && x <= right() && y >= m_location.y() && y <= bottom();
|
||||
}
|
||||
|
||||
bool contains(const FloatPoint& point) const
|
||||
{
|
||||
return contains(point.x(), point.y());
|
||||
}
|
||||
|
||||
bool contains(const FloatRect& other) const
|
||||
{
|
||||
return left() <= other.left()
|
||||
&& right() >= other.right()
|
||||
&& top() <= other.top()
|
||||
&& bottom() >= other.bottom();
|
||||
}
|
||||
|
||||
float primary_offset_for_orientation(Orientation orientation) const { return m_location.primary_offset_for_orientation(orientation); }
|
||||
void set_primary_offset_for_orientation(Orientation orientation, float value) { m_location.set_primary_offset_for_orientation(orientation, value); }
|
||||
float secondary_offset_for_orientation(Orientation orientation) const { return m_location.secondary_offset_for_orientation(orientation); }
|
||||
void set_secondary_offset_for_orientation(Orientation orientation, float value) { m_location.set_secondary_offset_for_orientation(orientation, value); }
|
||||
|
||||
float primary_size_for_orientation(Orientation orientation) const { return m_size.primary_size_for_orientation(orientation); }
|
||||
float secondary_size_for_orientation(Orientation orientation) const { return m_size.secondary_size_for_orientation(orientation); }
|
||||
void set_primary_size_for_orientation(Orientation orientation, float value) { m_size.set_primary_size_for_orientation(orientation, value); }
|
||||
void set_secondary_size_for_orientation(Orientation orientation, float value) { m_size.set_secondary_size_for_orientation(orientation, value); }
|
||||
|
||||
float first_edge_for_orientation(Orientation orientation) const
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
return top();
|
||||
return left();
|
||||
}
|
||||
|
||||
float last_edge_for_orientation(Orientation orientation) const
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
return bottom();
|
||||
return right();
|
||||
}
|
||||
|
||||
float left() const { return x(); }
|
||||
float right() const { return x() + width() - 1; }
|
||||
float top() const { return y(); }
|
||||
float bottom() const { return y() + height() - 1; }
|
||||
|
||||
void set_left(float left)
|
||||
{
|
||||
set_x(left);
|
||||
}
|
||||
|
||||
void set_top(float top)
|
||||
{
|
||||
set_y(top);
|
||||
}
|
||||
|
||||
void set_right(float right)
|
||||
{
|
||||
set_width(right - x() + 1);
|
||||
}
|
||||
|
||||
void set_bottom(float bottom)
|
||||
{
|
||||
set_height(bottom - y() + 1);
|
||||
}
|
||||
|
||||
void set_right_without_resize(float new_right)
|
||||
{
|
||||
float delta = new_right - right();
|
||||
move_by(delta, 0);
|
||||
}
|
||||
|
||||
void set_bottom_without_resize(float new_bottom)
|
||||
{
|
||||
float delta = new_bottom - bottom();
|
||||
move_by(0, delta);
|
||||
}
|
||||
|
||||
bool intersects(const FloatRect& other) const
|
||||
{
|
||||
return left() <= other.right()
|
||||
&& other.left() <= right()
|
||||
&& top() <= other.bottom()
|
||||
&& other.top() <= bottom();
|
||||
}
|
||||
|
||||
float x() const { return location().x(); }
|
||||
float y() const { return location().y(); }
|
||||
float width() const { return m_size.width(); }
|
||||
float height() const { return m_size.height(); }
|
||||
|
||||
void set_x(float x) { m_location.set_x(x); }
|
||||
void set_y(float y) { m_location.set_y(y); }
|
||||
void set_width(float width) { m_size.set_width(width); }
|
||||
void set_height(float height) { m_size.set_height(height); }
|
||||
|
||||
FloatPoint location() const { return m_location; }
|
||||
FloatSize size() const { return m_size; }
|
||||
|
||||
Vector<FloatRect, 4> shatter(const FloatRect& hammer) const;
|
||||
|
||||
bool operator==(const FloatRect& other) const
|
||||
{
|
||||
return m_location == other.m_location
|
||||
&& m_size == other.m_size;
|
||||
}
|
||||
|
||||
void intersect(const FloatRect&);
|
||||
|
||||
static FloatRect intersection(const FloatRect& a, const FloatRect& b)
|
||||
{
|
||||
FloatRect r(a);
|
||||
r.intersect(b);
|
||||
return r;
|
||||
}
|
||||
|
||||
FloatRect intersected(const FloatRect& other) const
|
||||
{
|
||||
return intersection(*this, other);
|
||||
}
|
||||
|
||||
FloatRect united(const FloatRect&) const;
|
||||
|
||||
FloatPoint top_left() const { return { left(), top() }; }
|
||||
FloatPoint top_right() const { return { right(), top() }; }
|
||||
FloatPoint bottom_left() const { return { left(), bottom() }; }
|
||||
FloatPoint bottom_right() const { return { right(), bottom() }; }
|
||||
|
||||
void align_within(const FloatRect&, TextAlignment);
|
||||
|
||||
void center_within(const FloatRect& other)
|
||||
{
|
||||
center_horizontally_within(other);
|
||||
center_vertically_within(other);
|
||||
}
|
||||
|
||||
void center_horizontally_within(const FloatRect& other)
|
||||
{
|
||||
set_x(other.center().x() - width() / 2);
|
||||
}
|
||||
|
||||
void center_vertically_within(const FloatRect& other)
|
||||
{
|
||||
set_y(other.center().y() - height() / 2);
|
||||
}
|
||||
|
||||
String to_string() const { return String::format("[%g,%g %gx%g]", x(), y(), width(), height()); }
|
||||
|
||||
private:
|
||||
FloatPoint m_location;
|
||||
FloatSize m_size;
|
||||
};
|
||||
|
||||
inline void FloatPoint::constrain(const FloatRect& rect)
|
||||
{
|
||||
if (x() < rect.left())
|
||||
set_x(rect.left());
|
||||
else if (x() > rect.right())
|
||||
set_x(rect.right());
|
||||
if (y() < rect.top())
|
||||
set_y(rect.top());
|
||||
else if (y() > rect.bottom())
|
||||
set_y(rect.bottom());
|
||||
}
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const FloatRect& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
inline Rect enclosing_int_rect(const FloatRect& float_rect)
|
||||
{
|
||||
return { (int)float_rect.x(), (int)float_rect.y(), (int)ceilf(float_rect.width()), (int)ceilf(float_rect.height()) };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::FloatRect;
|
119
Libraries/LibGfx/FloatSize.h
Normal file
119
Libraries/LibGfx/FloatSize.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class FloatSize {
|
||||
public:
|
||||
FloatSize() {}
|
||||
FloatSize(float w, float h)
|
||||
: m_width(w)
|
||||
, m_height(h)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_null() const { return !m_width && !m_height; }
|
||||
bool is_empty() const { return m_width <= 0 || m_height <= 0; }
|
||||
|
||||
float width() const { return m_width; }
|
||||
float height() const { return m_height; }
|
||||
|
||||
float area() const { return width() * height(); }
|
||||
|
||||
void set_width(float w) { m_width = w; }
|
||||
void set_height(float h) { m_height = h; }
|
||||
|
||||
bool operator==(const FloatSize& other) const
|
||||
{
|
||||
return m_width == other.m_width && m_height == other.m_height;
|
||||
}
|
||||
|
||||
bool operator!=(const FloatSize& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
FloatSize& operator-=(const FloatSize& other)
|
||||
{
|
||||
m_width -= other.m_width;
|
||||
m_height -= other.m_height;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FloatSize& operator+=(const FloatSize& other)
|
||||
{
|
||||
m_width += other.m_width;
|
||||
m_height += other.m_height;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float primary_size_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? height() : width();
|
||||
}
|
||||
|
||||
void set_primary_size_for_orientation(Orientation orientation, float value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_height(value);
|
||||
else
|
||||
set_width(value);
|
||||
}
|
||||
|
||||
float secondary_size_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? width() : height();
|
||||
}
|
||||
|
||||
void set_secondary_size_for_orientation(Orientation orientation, float value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_width(value);
|
||||
else
|
||||
set_height(value);
|
||||
}
|
||||
|
||||
String to_string() const { return String::format("[%gx%g]", m_width, m_height); }
|
||||
|
||||
private:
|
||||
float m_width { 0 };
|
||||
float m_height { 0 };
|
||||
};
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const FloatSize& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::FloatSize;
|
242
Libraries/LibGfx/Font.cpp
Normal file
242
Libraries/LibGfx/Font.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Font.h"
|
||||
#include "Emoji.h"
|
||||
#include "GraphicsBitmap.h"
|
||||
#include <AK/BufferStream.h>
|
||||
#include <AK/MappedFile.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/kmalloc.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
struct [[gnu::packed]] FontFileHeader
|
||||
{
|
||||
char magic[4];
|
||||
u8 glyph_width;
|
||||
u8 glyph_height;
|
||||
u8 type;
|
||||
u8 is_variable_width;
|
||||
u8 glyph_spacing;
|
||||
u8 unused[5];
|
||||
char name[64];
|
||||
};
|
||||
|
||||
Font& Font::default_font()
|
||||
{
|
||||
static Font* s_default_font;
|
||||
static const char* default_font_path = "/res/fonts/Katica10.font";
|
||||
if (!s_default_font) {
|
||||
s_default_font = Font::load_from_file(default_font_path).leak_ref();
|
||||
ASSERT(s_default_font);
|
||||
}
|
||||
return *s_default_font;
|
||||
}
|
||||
|
||||
Font& Font::default_fixed_width_font()
|
||||
{
|
||||
static Font* s_default_fixed_width_font;
|
||||
static const char* default_fixed_width_font_path = "/res/fonts/CsillaThin7x10.font";
|
||||
if (!s_default_fixed_width_font) {
|
||||
s_default_fixed_width_font = Font::load_from_file(default_fixed_width_font_path).leak_ref();
|
||||
ASSERT(s_default_fixed_width_font);
|
||||
}
|
||||
return *s_default_fixed_width_font;
|
||||
}
|
||||
|
||||
Font& Font::default_bold_fixed_width_font()
|
||||
{
|
||||
static Font* font;
|
||||
static const char* default_bold_fixed_width_font_path = "/res/fonts/CsillaBold7x10.font";
|
||||
if (!font) {
|
||||
font = Font::load_from_file(default_bold_fixed_width_font_path).leak_ref();
|
||||
ASSERT(font);
|
||||
}
|
||||
return *font;
|
||||
}
|
||||
|
||||
Font& Font::default_bold_font()
|
||||
{
|
||||
static Font* s_default_bold_font;
|
||||
static const char* default_bold_font_path = "/res/fonts/KaticaBold10.font";
|
||||
if (!s_default_bold_font) {
|
||||
s_default_bold_font = Font::load_from_file(default_bold_font_path).leak_ref();
|
||||
ASSERT(s_default_bold_font);
|
||||
}
|
||||
return *s_default_bold_font;
|
||||
}
|
||||
|
||||
RefPtr<Font> Font::clone() const
|
||||
{
|
||||
size_t bytes_per_glyph = sizeof(u32) * glyph_height();
|
||||
// FIXME: This is leaked!
|
||||
auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * 256));
|
||||
memcpy(new_rows, m_rows, bytes_per_glyph * 256);
|
||||
auto* new_widths = static_cast<u8*>(kmalloc(256));
|
||||
if (m_glyph_widths)
|
||||
memcpy(new_widths, m_glyph_widths, 256);
|
||||
else
|
||||
memset(new_widths, m_glyph_width, 256);
|
||||
return adopt(*new Font(m_name, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing));
|
||||
}
|
||||
|
||||
Font::Font(const StringView& name, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing)
|
||||
: m_name(name)
|
||||
, m_rows(rows)
|
||||
, m_glyph_widths(widths)
|
||||
, m_glyph_width(glyph_width)
|
||||
, m_glyph_height(glyph_height)
|
||||
, m_min_glyph_width(glyph_width)
|
||||
, m_max_glyph_width(glyph_width)
|
||||
, m_glyph_spacing(glyph_spacing)
|
||||
, m_fixed_width(is_fixed_width)
|
||||
{
|
||||
if (!m_fixed_width) {
|
||||
u8 maximum = 0;
|
||||
u8 minimum = 255;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
minimum = min(minimum, m_glyph_widths[i]);
|
||||
maximum = max(maximum, m_glyph_widths[i]);
|
||||
}
|
||||
m_min_glyph_width = minimum;
|
||||
m_max_glyph_width = maximum;
|
||||
}
|
||||
}
|
||||
|
||||
Font::~Font()
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<Font> Font::load_from_memory(const u8* data)
|
||||
{
|
||||
auto& header = *reinterpret_cast<const FontFileHeader*>(data);
|
||||
if (memcmp(header.magic, "!Fnt", 4)) {
|
||||
dbgprintf("header.magic != '!Fnt', instead it's '%c%c%c%c'\n", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
|
||||
return nullptr;
|
||||
}
|
||||
if (header.name[63] != '\0') {
|
||||
dbgprintf("Font name not fully null-terminated\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
|
||||
|
||||
auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
|
||||
u8* widths = nullptr;
|
||||
if (header.is_variable_width)
|
||||
widths = (u8*)(rows) + 256 * bytes_per_glyph;
|
||||
return adopt(*new Font(String(header.name), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing));
|
||||
}
|
||||
|
||||
RefPtr<Font> Font::load_from_file(const StringView& path)
|
||||
{
|
||||
MappedFile mapped_file(path);
|
||||
if (!mapped_file.is_valid())
|
||||
return nullptr;
|
||||
|
||||
auto font = load_from_memory((const u8*)mapped_file.data());
|
||||
font->m_mapped_file = move(mapped_file);
|
||||
return font;
|
||||
}
|
||||
|
||||
bool Font::write_to_file(const StringView& path)
|
||||
{
|
||||
int fd = creat_with_path_length(path.characters_without_null_termination(), path.length(), 0644);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
return false;
|
||||
}
|
||||
|
||||
FontFileHeader header;
|
||||
memset(&header, 0, sizeof(FontFileHeader));
|
||||
memcpy(header.magic, "!Fnt", 4);
|
||||
header.glyph_width = m_glyph_width;
|
||||
header.glyph_height = m_glyph_height;
|
||||
header.type = 0;
|
||||
header.is_variable_width = !m_fixed_width;
|
||||
header.glyph_spacing = m_glyph_spacing;
|
||||
memcpy(header.name, m_name.characters(), min(m_name.length(), (size_t)63));
|
||||
|
||||
size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
|
||||
|
||||
auto buffer = ByteBuffer::create_uninitialized(sizeof(FontFileHeader) + (256 * bytes_per_glyph) + 256);
|
||||
BufferStream stream(buffer);
|
||||
|
||||
stream << ByteBuffer::wrap(&header, sizeof(FontFileHeader));
|
||||
stream << ByteBuffer::wrap(m_rows, (256 * bytes_per_glyph));
|
||||
stream << ByteBuffer::wrap(m_glyph_widths, 256);
|
||||
|
||||
ASSERT(stream.at_end());
|
||||
ssize_t nwritten = write(fd, buffer.data(), buffer.size());
|
||||
ASSERT(nwritten == (ssize_t)buffer.size());
|
||||
int rc = close(fd);
|
||||
ASSERT(rc == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int Font::glyph_or_emoji_width(u32 codepoint) const
|
||||
{
|
||||
if (codepoint < 256)
|
||||
return glyph_width((char)codepoint);
|
||||
|
||||
if (m_fixed_width)
|
||||
return m_glyph_width;
|
||||
|
||||
auto* emoji = Emoji::emoji_for_codepoint(codepoint);
|
||||
if (emoji == nullptr)
|
||||
return glyph_width('?');
|
||||
return emoji->size().width();
|
||||
}
|
||||
|
||||
int Font::width(const StringView& string) const
|
||||
{
|
||||
Utf8View utf8 { string };
|
||||
return width(utf8);
|
||||
}
|
||||
|
||||
int Font::width(const Utf8View& utf8) const
|
||||
{
|
||||
bool first = true;
|
||||
int width = 0;
|
||||
|
||||
for (u32 codepoint : utf8) {
|
||||
if (!first)
|
||||
width += glyph_spacing();
|
||||
first = false;
|
||||
width += glyph_or_emoji_width(codepoint);
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
}
|
132
Libraries/LibGfx/Font.h
Normal file
132
Libraries/LibGfx/Font.h
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/MappedFile.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
// FIXME: Make a MutableGlyphBitmap buddy class for FontEditor instead?
|
||||
class GlyphBitmap {
|
||||
friend class Font;
|
||||
|
||||
public:
|
||||
const unsigned* rows() const { return m_rows; }
|
||||
unsigned row(unsigned index) const { return m_rows[index]; }
|
||||
|
||||
bool bit_at(int x, int y) const { return row(y) & (1 << x); }
|
||||
void set_bit_at(int x, int y, bool b)
|
||||
{
|
||||
auto& mutable_row = const_cast<unsigned*>(m_rows)[y];
|
||||
if (b)
|
||||
mutable_row |= 1 << x;
|
||||
else
|
||||
mutable_row &= ~(1 << x);
|
||||
}
|
||||
|
||||
Size size() const { return m_size; }
|
||||
int width() const { return m_size.width(); }
|
||||
int height() const { return m_size.height(); }
|
||||
|
||||
private:
|
||||
GlyphBitmap(const unsigned* rows, Size size)
|
||||
: m_rows(rows)
|
||||
, m_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
const unsigned* m_rows { nullptr };
|
||||
Size m_size;
|
||||
};
|
||||
|
||||
class Font : public RefCounted<Font> {
|
||||
public:
|
||||
static Font& default_font();
|
||||
static Font& default_bold_font();
|
||||
|
||||
static Font& default_fixed_width_font();
|
||||
static Font& default_bold_fixed_width_font();
|
||||
|
||||
RefPtr<Font> clone() const;
|
||||
|
||||
static RefPtr<Font> load_from_file(const StringView& path);
|
||||
bool write_to_file(const StringView& path);
|
||||
|
||||
~Font();
|
||||
|
||||
GlyphBitmap glyph_bitmap(char ch) const { return GlyphBitmap(&m_rows[(u8)ch * m_glyph_height], { glyph_width(ch), m_glyph_height }); }
|
||||
|
||||
u8 glyph_width(char ch) const { return m_fixed_width ? m_glyph_width : m_glyph_widths[(u8)ch]; }
|
||||
int glyph_or_emoji_width(u32 codepoint) const;
|
||||
u8 glyph_height() const { return m_glyph_height; }
|
||||
u8 min_glyph_width() const { return m_min_glyph_width; }
|
||||
u8 max_glyph_width() const { return m_max_glyph_width; }
|
||||
int width(const StringView&) const;
|
||||
int width(const Utf8View&) const;
|
||||
|
||||
String name() const { return m_name; }
|
||||
void set_name(const StringView& name) { m_name = name; }
|
||||
|
||||
bool is_fixed_width() const { return m_fixed_width; }
|
||||
void set_fixed_width(bool b) { m_fixed_width = b; }
|
||||
|
||||
u8 glyph_spacing() const { return m_glyph_spacing; }
|
||||
void set_glyph_spacing(u8 spacing) { m_glyph_spacing = spacing; }
|
||||
|
||||
void set_glyph_width(char ch, u8 width)
|
||||
{
|
||||
ASSERT(m_glyph_widths);
|
||||
m_glyph_widths[(u8)ch] = width;
|
||||
}
|
||||
|
||||
private:
|
||||
Font(const StringView& name, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing);
|
||||
|
||||
static RefPtr<Font> load_from_memory(const u8*);
|
||||
|
||||
String m_name;
|
||||
|
||||
unsigned* m_rows { nullptr };
|
||||
u8* m_glyph_widths { nullptr };
|
||||
MappedFile m_mapped_file;
|
||||
|
||||
u8 m_glyph_width { 0 };
|
||||
u8 m_glyph_height { 0 };
|
||||
u8 m_min_glyph_width { 0 };
|
||||
u8 m_max_glyph_width { 0 };
|
||||
u8 m_glyph_spacing { 0 };
|
||||
|
||||
bool m_fixed_width { false };
|
||||
};
|
||||
|
||||
}
|
265
Libraries/LibGfx/GIFLoader.cpp
Normal file
265
Libraries/LibGfx/GIFLoader.cpp
Normal file
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/BufferStream.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/FileSystemPath.h>
|
||||
#include <AK/NonnullOwnPtrVector.h>
|
||||
#include <LibGfx/GIFLoader.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
static RefPtr<Gfx::Bitmap> load_gif_impl(const u8*, size_t);
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_gif(const StringView& path)
|
||||
{
|
||||
MappedFile mapped_file(path);
|
||||
if (!mapped_file.is_valid())
|
||||
return nullptr;
|
||||
auto bitmap = load_gif_impl((const u8*)mapped_file.data(), mapped_file.size());
|
||||
if (bitmap)
|
||||
bitmap->set_mmap_name(String::format("GraphicsBitmap [%dx%d] - Decoded GIF: %s", bitmap->width(), bitmap->height(), canonicalized_path(path).characters()));
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_gif_from_memory(const u8* data, size_t length)
|
||||
{
|
||||
auto bitmap = load_gif_impl(data, length);
|
||||
if (bitmap)
|
||||
bitmap->set_mmap_name(String::format("GraphicsBitmap [%dx%d] - Decoded GIF: <memory>", bitmap->width(), bitmap->height()));
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
enum class GIFFormat {
|
||||
GIF87a,
|
||||
GIF89a,
|
||||
};
|
||||
|
||||
struct RGB {
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
};
|
||||
|
||||
struct LogicalScreen {
|
||||
u16 width;
|
||||
u16 height;
|
||||
RGB color_map[256];
|
||||
};
|
||||
|
||||
struct ImageDescriptor {
|
||||
u16 x;
|
||||
u16 y;
|
||||
u16 width;
|
||||
u16 height;
|
||||
bool use_global_color_map;
|
||||
RGB color_map[256];
|
||||
u8 lzw_min_code_size;
|
||||
Vector<u8> lzw_encoded_bytes;
|
||||
};
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_gif_impl(const u8* data, size_t data_size)
|
||||
{
|
||||
if (data_size < 32)
|
||||
return nullptr;
|
||||
|
||||
auto buffer = ByteBuffer::wrap(data, data_size);
|
||||
BufferStream stream(buffer);
|
||||
|
||||
static const char valid_header_87[] = "GIF87a";
|
||||
static const char valid_header_89[] = "GIF89a";
|
||||
|
||||
char header[6];
|
||||
for (int i = 0; i < 6; ++i)
|
||||
stream >> header[i];
|
||||
|
||||
GIFFormat format;
|
||||
if (!memcmp(header, valid_header_87, sizeof(header)))
|
||||
format = GIFFormat::GIF87a;
|
||||
else if (!memcmp(header, valid_header_89, sizeof(header)))
|
||||
format = GIFFormat::GIF89a;
|
||||
else
|
||||
return nullptr;
|
||||
|
||||
printf("Format is %s\n", format == GIFFormat::GIF89a ? "GIF89a" : "GIF87a");
|
||||
|
||||
LogicalScreen logical_screen;
|
||||
stream >> logical_screen.width;
|
||||
stream >> logical_screen.height;
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
u8 gcm_info = 0;
|
||||
stream >> gcm_info;
|
||||
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
bool global_color_map_follows_descriptor = gcm_info & 0x80;
|
||||
u8 bits_per_pixel = (gcm_info & 7) + 1;
|
||||
u8 bits_of_color_resolution = (gcm_info >> 4) & 7;
|
||||
|
||||
printf("LogicalScreen: %dx%d\n", logical_screen.width, logical_screen.height);
|
||||
printf("global_color_map_follows_descriptor: %u\n", global_color_map_follows_descriptor);
|
||||
printf("bits_per_pixel: %u\n", bits_per_pixel);
|
||||
printf("bits_of_color_resolution: %u\n", bits_of_color_resolution);
|
||||
|
||||
u8 background_color = 0;
|
||||
stream >> background_color;
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
printf("background_color: %u\n", background_color);
|
||||
|
||||
u8 pixel_aspect_ratio = 0;
|
||||
stream >> pixel_aspect_ratio;
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
int color_map_entry_count = 1;
|
||||
for (int i = 0; i < bits_per_pixel; ++i)
|
||||
color_map_entry_count *= 2;
|
||||
|
||||
printf("color_map_entry_count: %d\n", color_map_entry_count);
|
||||
|
||||
for (int i = 0; i < color_map_entry_count; ++i) {
|
||||
stream >> logical_screen.color_map[i].r;
|
||||
stream >> logical_screen.color_map[i].g;
|
||||
stream >> logical_screen.color_map[i].b;
|
||||
}
|
||||
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
for (int i = 0; i < color_map_entry_count; ++i) {
|
||||
auto& rgb = logical_screen.color_map[i];
|
||||
printf("[%02x]: %s\n", i, Color(rgb.r, rgb.g, rgb.b).to_string().characters());
|
||||
}
|
||||
|
||||
NonnullOwnPtrVector<ImageDescriptor> images;
|
||||
|
||||
for (;;) {
|
||||
u8 sentinel = 0;
|
||||
stream >> sentinel;
|
||||
printf("Sentinel: %02x\n", sentinel);
|
||||
|
||||
if (sentinel == 0x21) {
|
||||
u8 extension_type = 0;
|
||||
stream >> extension_type;
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
printf("Extension block of type %02x\n", extension_type);
|
||||
|
||||
u8 sub_block_length = 0;
|
||||
|
||||
for (;;) {
|
||||
stream >> sub_block_length;
|
||||
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
if (sub_block_length == 0)
|
||||
break;
|
||||
|
||||
u8 dummy;
|
||||
for (u16 i = 0; i < sub_block_length; ++i)
|
||||
stream >> dummy;
|
||||
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sentinel == 0x2c) {
|
||||
images.append(make<ImageDescriptor>());
|
||||
auto& image = images.last();
|
||||
u8 packed_fields;
|
||||
stream >> image.x;
|
||||
stream >> image.y;
|
||||
stream >> image.width;
|
||||
stream >> image.height;
|
||||
stream >> packed_fields;
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
printf("Image descriptor: %d,%d %dx%d, %02x\n", image.x, image.y, image.width, image.height, packed_fields);
|
||||
|
||||
stream >> image.lzw_min_code_size;
|
||||
|
||||
printf("min code size: %u\n", image.lzw_min_code_size);
|
||||
|
||||
u8 lzw_encoded_bytes_expected = 0;
|
||||
|
||||
for (;;) {
|
||||
stream >> lzw_encoded_bytes_expected;
|
||||
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
if (lzw_encoded_bytes_expected == 0)
|
||||
break;
|
||||
|
||||
u8 buffer[256];
|
||||
for (int i = 0; i < lzw_encoded_bytes_expected; ++i) {
|
||||
stream >> buffer[i];
|
||||
}
|
||||
|
||||
if (stream.handle_read_failure())
|
||||
return nullptr;
|
||||
|
||||
for (int i = 0; i < lzw_encoded_bytes_expected; ++i) {
|
||||
image.lzw_encoded_bytes.append(buffer[i]);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sentinel == 0x3b) {
|
||||
printf("Trailer! Awesome :)\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We exited the block loop after finding a trailer. We should have everything needed.
|
||||
printf("Image count: %d\n", images.size());
|
||||
if (images.is_empty())
|
||||
return nullptr;
|
||||
|
||||
for (int i = 0; i < images.size(); ++i) {
|
||||
auto& image = images.at(i);
|
||||
printf("Image %d: %d,%d %dx%d %d bytes LZW-encoded\n", i, image.x, image.y, image.width, image.height, image.lzw_encoded_bytes.size());
|
||||
|
||||
// FIXME: Decode the LZW-encoded bytes and turn them into an image.
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
53
Libraries/LibGfx/GIFLoader.h
Normal file
53
Libraries/LibGfx/GIFLoader.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/GraphicsBitmap.h>
|
||||
#include <LibGfx/ImageDecoder.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_gif(const StringView& path);
|
||||
RefPtr<Gfx::Bitmap> load_gif_from_memory(const u8*, size_t);
|
||||
|
||||
struct GIFLoadingContext;
|
||||
|
||||
class GIFImageDecoderPlugin final : public ImageDecoderPlugin {
|
||||
public:
|
||||
virtual ~GIFImageDecoderPlugin() override;
|
||||
GIFImageDecoderPlugin(const u8*, size_t);
|
||||
|
||||
virtual Size size() override;
|
||||
virtual RefPtr<Gfx::Bitmap> bitmap() override;
|
||||
virtual void set_volatile() override;
|
||||
[[nodiscard]] virtual bool set_nonvolatile() override;
|
||||
|
||||
private:
|
||||
OwnPtr<GIFLoadingContext> m_context;
|
||||
};
|
||||
|
||||
}
|
177
Libraries/LibGfx/GraphicsBitmap.cpp
Normal file
177
Libraries/LibGfx/GraphicsBitmap.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/MappedFile.h>
|
||||
#include <LibGfx/GraphicsBitmap.h>
|
||||
#include <LibGfx/PNGLoader.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
NonnullRefPtr<Bitmap> Bitmap::create(Format format, const Size& size)
|
||||
{
|
||||
return adopt(*new Bitmap(format, size, Purgeable::No));
|
||||
}
|
||||
|
||||
NonnullRefPtr<Bitmap> Bitmap::create_purgeable(Format format, const Size& size)
|
||||
{
|
||||
return adopt(*new Bitmap(format, size, Purgeable::Yes));
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(Format format, const Size& size, Purgeable purgeable)
|
||||
: m_size(size)
|
||||
, m_pitch(round_up_to_power_of_two(size.width() * sizeof(RGBA32), 16))
|
||||
, m_format(format)
|
||||
, m_purgeable(purgeable == Purgeable::Yes)
|
||||
{
|
||||
if (format == Format::Indexed8)
|
||||
m_palette = new RGBA32[256];
|
||||
int map_flags = purgeable == Purgeable::Yes ? (MAP_PURGEABLE | MAP_PRIVATE) : (MAP_ANONYMOUS | MAP_PRIVATE);
|
||||
m_data = (RGBA32*)mmap_with_name(nullptr, size_in_bytes(), PROT_READ | PROT_WRITE, map_flags, 0, 0, String::format("GraphicsBitmap [%dx%d]", width(), height()).characters());
|
||||
ASSERT(m_data && m_data != (void*)-1);
|
||||
m_needs_munmap = true;
|
||||
}
|
||||
|
||||
NonnullRefPtr<Bitmap> Bitmap::create_wrapper(Format format, const Size& size, size_t pitch, RGBA32* data)
|
||||
{
|
||||
return adopt(*new Bitmap(format, size, pitch, data));
|
||||
}
|
||||
|
||||
RefPtr<Bitmap> Bitmap::load_from_file(const StringView& path)
|
||||
{
|
||||
return load_png(path);
|
||||
}
|
||||
|
||||
RefPtr<Bitmap> Bitmap::load_from_file(Format format, const StringView& path, const Size& size)
|
||||
{
|
||||
MappedFile mapped_file(path);
|
||||
if (!mapped_file.is_valid())
|
||||
return nullptr;
|
||||
return adopt(*new Bitmap(format, size, move(mapped_file)));
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(Format format, const Size& size, size_t pitch, RGBA32* data)
|
||||
: m_size(size)
|
||||
, m_data(data)
|
||||
, m_pitch(pitch)
|
||||
, m_format(format)
|
||||
{
|
||||
if (format == Format::Indexed8)
|
||||
m_palette = new RGBA32[256];
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(Format format, const Size& size, MappedFile&& mapped_file)
|
||||
: m_size(size)
|
||||
, m_data((RGBA32*)mapped_file.data())
|
||||
, m_pitch(round_up_to_power_of_two(size.width() * sizeof(RGBA32), 16))
|
||||
, m_format(format)
|
||||
, m_mapped_file(move(mapped_file))
|
||||
{
|
||||
ASSERT(format != Format::Indexed8);
|
||||
}
|
||||
|
||||
NonnullRefPtr<Bitmap> Bitmap::create_with_shared_buffer(Format format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const Size& size)
|
||||
{
|
||||
return adopt(*new Bitmap(format, move(shared_buffer), size));
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(Format format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const Size& size)
|
||||
: m_size(size)
|
||||
, m_data((RGBA32*)shared_buffer->data())
|
||||
, m_pitch(round_up_to_power_of_two(size.width() * sizeof(RGBA32), 16))
|
||||
, m_format(format)
|
||||
, m_shared_buffer(move(shared_buffer))
|
||||
{
|
||||
ASSERT(format != Format::Indexed8);
|
||||
}
|
||||
|
||||
NonnullRefPtr<Bitmap> Bitmap::to_shareable_bitmap() const
|
||||
{
|
||||
if (m_shared_buffer)
|
||||
return *this;
|
||||
auto buffer = SharedBuffer::create_with_size(size_in_bytes());
|
||||
auto bitmap = Bitmap::create_with_shared_buffer(m_format, *buffer, m_size);
|
||||
memcpy(buffer->data(), scanline(0), size_in_bytes());
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
Bitmap::~Bitmap()
|
||||
{
|
||||
if (m_needs_munmap) {
|
||||
int rc = munmap(m_data, size_in_bytes());
|
||||
ASSERT(rc == 0);
|
||||
}
|
||||
m_data = nullptr;
|
||||
delete[] m_palette;
|
||||
}
|
||||
|
||||
void Bitmap::set_mmap_name(const StringView& name)
|
||||
{
|
||||
ASSERT(m_needs_munmap);
|
||||
::set_mmap_name(m_data, size_in_bytes(), String(name).characters());
|
||||
}
|
||||
|
||||
void Bitmap::fill(Color color)
|
||||
{
|
||||
ASSERT(m_format == Bitmap::Format::RGB32 || m_format == Bitmap::Format::RGBA32);
|
||||
for (int y = 0; y < height(); ++y) {
|
||||
auto* scanline = this->scanline(y);
|
||||
fast_u32_fill(scanline, color.value(), width());
|
||||
}
|
||||
}
|
||||
|
||||
void Bitmap::set_volatile()
|
||||
{
|
||||
ASSERT(m_purgeable);
|
||||
if (m_volatile)
|
||||
return;
|
||||
int rc = madvise(m_data, size_in_bytes(), MADV_SET_VOLATILE);
|
||||
if (rc < 0) {
|
||||
perror("madvise(MADV_SET_VOLATILE)");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
m_volatile = true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Bitmap::set_nonvolatile()
|
||||
{
|
||||
ASSERT(m_purgeable);
|
||||
if (!m_volatile)
|
||||
return true;
|
||||
int rc = madvise(m_data, size_in_bytes(), MADV_SET_NONVOLATILE);
|
||||
if (rc < 0) {
|
||||
perror("madvise(MADV_SET_NONVOLATILE)");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
m_volatile = false;
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
}
|
240
Libraries/LibGfx/GraphicsBitmap.h
Normal file
240
Libraries/LibGfx/GraphicsBitmap.h
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Color.h"
|
||||
#include "Rect.h"
|
||||
#include "Size.h"
|
||||
#include <AK/MappedFile.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Bitmap : public RefCounted<Bitmap> {
|
||||
public:
|
||||
enum class Format {
|
||||
Invalid,
|
||||
RGB32,
|
||||
RGBA32,
|
||||
Indexed8
|
||||
};
|
||||
|
||||
static NonnullRefPtr<Bitmap> create(Format, const Size&);
|
||||
static NonnullRefPtr<Bitmap> create_purgeable(Format, const Size&);
|
||||
static NonnullRefPtr<Bitmap> create_wrapper(Format, const Size&, size_t pitch, RGBA32*);
|
||||
static RefPtr<Bitmap> load_from_file(const StringView& path);
|
||||
static RefPtr<Bitmap> load_from_file(Format, const StringView& path, const Size&);
|
||||
static NonnullRefPtr<Bitmap> create_with_shared_buffer(Format, NonnullRefPtr<SharedBuffer>&&, const Size&);
|
||||
|
||||
NonnullRefPtr<Bitmap> to_shareable_bitmap() const;
|
||||
|
||||
~Bitmap();
|
||||
|
||||
RGBA32* scanline(int y);
|
||||
const RGBA32* scanline(int y) const;
|
||||
|
||||
u8* bits(int y);
|
||||
const u8* bits(int y) const;
|
||||
|
||||
Rect rect() const { return { {}, m_size }; }
|
||||
Size size() const { return m_size; }
|
||||
int width() const { return m_size.width(); }
|
||||
int height() const { return m_size.height(); }
|
||||
size_t pitch() const { return m_pitch; }
|
||||
int shared_buffer_id() const { return m_shared_buffer ? m_shared_buffer->shared_buffer_id() : -1; }
|
||||
|
||||
SharedBuffer* shared_buffer() { return m_shared_buffer.ptr(); }
|
||||
const SharedBuffer* shared_buffer() const { return m_shared_buffer.ptr(); }
|
||||
|
||||
unsigned bpp() const
|
||||
{
|
||||
switch (m_format) {
|
||||
case Format::Indexed8:
|
||||
return 8;
|
||||
case Format::RGB32:
|
||||
case Format::RGBA32:
|
||||
return 32;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
case Format::Invalid:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void fill(Color);
|
||||
|
||||
bool has_alpha_channel() const { return m_format == Format::RGBA32; }
|
||||
Format format() const { return m_format; }
|
||||
|
||||
void set_mmap_name(const StringView&);
|
||||
|
||||
size_t size_in_bytes() const { return m_pitch * m_size.height(); }
|
||||
|
||||
Color palette_color(u8 index) const { return Color::from_rgba(m_palette[index]); }
|
||||
void set_palette_color(u8 index, Color color) { m_palette[index] = color.value(); }
|
||||
|
||||
template<Format>
|
||||
Color get_pixel(int x, int y) const
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Color get_pixel(int x, int y) const;
|
||||
|
||||
Color get_pixel(const Point& position) const
|
||||
{
|
||||
return get_pixel(position.x(), position.y());
|
||||
}
|
||||
|
||||
template<Format>
|
||||
void set_pixel(int x, int y, Color)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void set_pixel(int x, int y, Color);
|
||||
|
||||
void set_pixel(const Point& position, Color color)
|
||||
{
|
||||
set_pixel(position.x(), position.y(), color);
|
||||
}
|
||||
|
||||
bool is_purgeable() const { return m_purgeable; }
|
||||
bool is_volatile() const { return m_volatile; }
|
||||
void set_volatile();
|
||||
[[nodiscard]] bool set_nonvolatile();
|
||||
|
||||
private:
|
||||
enum class Purgeable { No,
|
||||
Yes };
|
||||
Bitmap(Format, const Size&, Purgeable);
|
||||
Bitmap(Format, const Size&, size_t pitch, RGBA32*);
|
||||
Bitmap(Format, const Size&, MappedFile&&);
|
||||
Bitmap(Format, NonnullRefPtr<SharedBuffer>&&, const Size&);
|
||||
|
||||
Size m_size;
|
||||
RGBA32* m_data { nullptr };
|
||||
RGBA32* m_palette { nullptr };
|
||||
size_t m_pitch { 0 };
|
||||
Format m_format { Format::Invalid };
|
||||
bool m_needs_munmap { false };
|
||||
bool m_purgeable { false };
|
||||
bool m_volatile { false };
|
||||
MappedFile m_mapped_file;
|
||||
RefPtr<SharedBuffer> m_shared_buffer;
|
||||
};
|
||||
|
||||
inline RGBA32* Bitmap::scanline(int y)
|
||||
{
|
||||
return reinterpret_cast<RGBA32*>((((u8*)m_data) + (y * m_pitch)));
|
||||
}
|
||||
|
||||
inline const RGBA32* Bitmap::scanline(int y) const
|
||||
{
|
||||
return reinterpret_cast<const RGBA32*>((((const u8*)m_data) + (y * m_pitch)));
|
||||
}
|
||||
|
||||
inline const u8* Bitmap::bits(int y) const
|
||||
{
|
||||
return reinterpret_cast<const u8*>(scanline(y));
|
||||
}
|
||||
|
||||
inline u8* Bitmap::bits(int y)
|
||||
{
|
||||
return reinterpret_cast<u8*>(scanline(y));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Color Bitmap::get_pixel<Bitmap::Format::RGB32>(int x, int y) const
|
||||
{
|
||||
return Color::from_rgb(scanline(y)[x]);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Color Bitmap::get_pixel<Bitmap::Format::RGBA32>(int x, int y) const
|
||||
{
|
||||
return Color::from_rgba(scanline(y)[x]);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Color Bitmap::get_pixel<Bitmap::Format::Indexed8>(int x, int y) const
|
||||
{
|
||||
return Color::from_rgba(m_palette[bits(y)[x]]);
|
||||
}
|
||||
|
||||
inline Color Bitmap::get_pixel(int x, int y) const
|
||||
{
|
||||
switch (m_format) {
|
||||
case Format::RGB32:
|
||||
return get_pixel<Format::RGB32>(x, y);
|
||||
case Format::RGBA32:
|
||||
return get_pixel<Format::RGBA32>(x, y);
|
||||
case Format::Indexed8:
|
||||
return get_pixel<Format::Indexed8>(x, y);
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void Bitmap::set_pixel<Bitmap::Format::RGB32>(int x, int y, Color color)
|
||||
{
|
||||
scanline(y)[x] = color.value();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void Bitmap::set_pixel<Bitmap::Format::RGBA32>(int x, int y, Color color)
|
||||
{
|
||||
scanline(y)[x] = color.value();
|
||||
}
|
||||
|
||||
inline void Bitmap::set_pixel(int x, int y, Color color)
|
||||
{
|
||||
switch (m_format) {
|
||||
case Format::RGB32:
|
||||
set_pixel<Format::RGB32>(x, y, color);
|
||||
break;
|
||||
case Format::RGBA32:
|
||||
set_pixel<Format::RGBA32>(x, y, color);
|
||||
break;
|
||||
case Format::Indexed8:
|
||||
ASSERT_NOT_REACHED();
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
40
Libraries/LibGfx/ImageDecoder.cpp
Normal file
40
Libraries/LibGfx/ImageDecoder.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/ImageDecoder.h>
|
||||
#include <LibGfx/PNGLoader.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
ImageDecoder::ImageDecoder(const u8* data, size_t size)
|
||||
{
|
||||
m_plugin = make<PNGImageDecoderPlugin>(data, size);
|
||||
}
|
||||
|
||||
ImageDecoder::~ImageDecoder()
|
||||
{
|
||||
}
|
||||
}
|
70
Libraries/LibGfx/ImageDecoder.h
Normal file
70
Libraries/LibGfx/ImageDecoder.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <LibGfx/Size.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Bitmap;
|
||||
|
||||
class ImageDecoderPlugin {
|
||||
public:
|
||||
virtual ~ImageDecoderPlugin() {}
|
||||
|
||||
virtual Size size() = 0;
|
||||
virtual RefPtr<Gfx::Bitmap> bitmap() = 0;
|
||||
|
||||
virtual void set_volatile() = 0;
|
||||
[[nodiscard]] virtual bool set_nonvolatile() = 0;
|
||||
|
||||
protected:
|
||||
ImageDecoderPlugin() {}
|
||||
};
|
||||
|
||||
class ImageDecoder : public RefCounted<ImageDecoder> {
|
||||
public:
|
||||
static NonnullRefPtr<ImageDecoder> create(const u8* data, size_t size) { return adopt(*new ImageDecoder(data, size)); }
|
||||
~ImageDecoder();
|
||||
|
||||
Size size() const { return m_plugin->size(); }
|
||||
int width() const { return size().width(); }
|
||||
int height() const { return size().height(); }
|
||||
RefPtr<Gfx::Bitmap> bitmap() const { return m_plugin->bitmap(); }
|
||||
void set_volatile() { m_plugin->set_volatile(); }
|
||||
[[nodiscard]] bool set_nonvolatile() { return m_plugin->set_nonvolatile(); }
|
||||
|
||||
private:
|
||||
ImageDecoder(const u8*, size_t);
|
||||
|
||||
mutable OwnPtr<ImageDecoderPlugin> m_plugin;
|
||||
};
|
||||
|
||||
}
|
24
Libraries/LibGfx/Makefile
Normal file
24
Libraries/LibGfx/Makefile
Normal file
|
@ -0,0 +1,24 @@
|
|||
OBJS = \
|
||||
CharacterBitmap.o \
|
||||
Color.o \
|
||||
DisjointRectSet.o \
|
||||
Font.o \
|
||||
GraphicsBitmap.o \
|
||||
Painter.o \
|
||||
PNGLoader.o \
|
||||
GIFLoader.o \
|
||||
ImageDecoder.o \
|
||||
Rect.o \
|
||||
StylePainter.o \
|
||||
SystemTheme.o \
|
||||
Palette.o \
|
||||
Emoji.o
|
||||
|
||||
LIBRARY = libgfx.a
|
||||
|
||||
install:
|
||||
mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/LibGfx/
|
||||
cp *.h $(SERENITY_BASE_DIR)/Root/usr/include/LibGfx/
|
||||
cp $(LIBRARY) $(SERENITY_BASE_DIR)/Root/usr/lib/
|
||||
|
||||
include ../../Makefile.common
|
38
Libraries/LibGfx/Orientation.h
Normal file
38
Libraries/LibGfx/Orientation.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class Orientation {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using Gfx::Orientation;
|
721
Libraries/LibGfx/PNGLoader.cpp
Normal file
721
Libraries/LibGfx/PNGLoader.cpp
Normal file
|
@ -0,0 +1,721 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/FileSystemPath.h>
|
||||
#include <AK/MappedFile.h>
|
||||
#include <AK/NetworkOrdered.h>
|
||||
#include <LibCore/puff.h>
|
||||
#include <LibGfx/PNGLoader.h>
|
||||
#include <fcntl.h>
|
||||
#include <serenity.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
static const u8 png_header[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
|
||||
|
||||
struct PNG_IHDR {
|
||||
NetworkOrdered<u32> width;
|
||||
NetworkOrdered<u32> height;
|
||||
u8 bit_depth { 0 };
|
||||
u8 color_type { 0 };
|
||||
u8 compression_method { 0 };
|
||||
u8 filter_method { 0 };
|
||||
u8 interlace_method { 0 };
|
||||
};
|
||||
|
||||
static_assert(sizeof(PNG_IHDR) == 13);
|
||||
|
||||
struct Scanline {
|
||||
u8 filter { 0 };
|
||||
ByteBuffer data {};
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] PaletteEntry
|
||||
{
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
//u8 a;
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] Triplet
|
||||
{
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] Triplet16
|
||||
{
|
||||
u16 r;
|
||||
u16 g;
|
||||
u16 b;
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] Quad16
|
||||
{
|
||||
u16 r;
|
||||
u16 g;
|
||||
u16 b;
|
||||
u16 a;
|
||||
};
|
||||
|
||||
struct PNGLoadingContext {
|
||||
enum State {
|
||||
NotDecoded = 0,
|
||||
Error,
|
||||
HeaderDecoded,
|
||||
SizeDecoded,
|
||||
ChunksDecoded,
|
||||
BitmapDecoded,
|
||||
};
|
||||
State state { State::NotDecoded };
|
||||
const u8* data { nullptr };
|
||||
size_t data_size { 0 };
|
||||
int width { -1 };
|
||||
int height { -1 };
|
||||
u8 bit_depth { 0 };
|
||||
u8 color_type { 0 };
|
||||
u8 compression_method { 0 };
|
||||
u8 filter_method { 0 };
|
||||
u8 interlace_method { 0 };
|
||||
u8 bytes_per_pixel { 0 };
|
||||
bool has_seen_zlib_header { false };
|
||||
bool has_alpha() const { return color_type & 4 || palette_transparency_data.size() > 0; }
|
||||
Vector<Scanline> scanlines;
|
||||
RefPtr<Gfx::Bitmap> bitmap;
|
||||
u8* decompression_buffer { nullptr };
|
||||
int decompression_buffer_size { 0 };
|
||||
Vector<u8> compressed_data;
|
||||
Vector<PaletteEntry> palette_data;
|
||||
Vector<u8> palette_transparency_data;
|
||||
};
|
||||
|
||||
class Streamer {
|
||||
public:
|
||||
Streamer(const u8* data, int size)
|
||||
: m_original_data(data)
|
||||
, m_original_size(size)
|
||||
, m_data_ptr(data)
|
||||
, m_size_remaining(size)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool read(T& value)
|
||||
{
|
||||
if (m_size_remaining < (int)sizeof(T))
|
||||
return false;
|
||||
value = *((const NetworkOrdered<T>*)m_data_ptr);
|
||||
m_data_ptr += sizeof(T);
|
||||
m_size_remaining -= sizeof(T);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_bytes(u8* buffer, int count)
|
||||
{
|
||||
if (m_size_remaining < count)
|
||||
return false;
|
||||
memcpy(buffer, m_data_ptr, count);
|
||||
m_data_ptr += count;
|
||||
m_size_remaining -= count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wrap_bytes(ByteBuffer& buffer, int count)
|
||||
{
|
||||
if (m_size_remaining < count)
|
||||
return false;
|
||||
buffer = ByteBuffer::wrap(m_data_ptr, count);
|
||||
m_data_ptr += count;
|
||||
m_size_remaining -= count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool at_end() const { return !m_size_remaining; }
|
||||
|
||||
private:
|
||||
const u8* m_original_data;
|
||||
int m_original_size;
|
||||
const u8* m_data_ptr;
|
||||
int m_size_remaining;
|
||||
};
|
||||
|
||||
static RefPtr<Gfx::Bitmap> load_png_impl(const u8*, int);
|
||||
static bool process_chunk(Streamer&, PNGLoadingContext& context, bool decode_size_only);
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_png(const StringView& path)
|
||||
{
|
||||
MappedFile mapped_file(path);
|
||||
if (!mapped_file.is_valid())
|
||||
return nullptr;
|
||||
auto bitmap = load_png_impl((const u8*)mapped_file.data(), mapped_file.size());
|
||||
if (bitmap)
|
||||
bitmap->set_mmap_name(String::format("GraphicsBitmap [%dx%d] - Decoded PNG: %s", bitmap->width(), bitmap->height(), canonicalized_path(path).characters()));
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_png_from_memory(const u8* data, size_t length)
|
||||
{
|
||||
auto bitmap = load_png_impl(data, length);
|
||||
if (bitmap)
|
||||
bitmap->set_mmap_name(String::format("GraphicsBitmap [%dx%d] - Decoded PNG: <memory>", bitmap->width(), bitmap->height()));
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
[[gnu::always_inline]] static inline u8 paeth_predictor(int a, int b, int c)
|
||||
{
|
||||
int p = a + b - c;
|
||||
int pa = abs(p - a);
|
||||
int pb = abs(p - b);
|
||||
int pc = abs(p - c);
|
||||
if (pa <= pb && pa <= pc)
|
||||
return a;
|
||||
if (pb <= pc)
|
||||
return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
union [[gnu::packed]] Pixel
|
||||
{
|
||||
RGBA32 rgba { 0 };
|
||||
u8 v[4];
|
||||
struct {
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
u8 a;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Pixel) == 4);
|
||||
|
||||
template<bool has_alpha, u8 filter_type>
|
||||
[[gnu::always_inline]] static inline void unfilter_impl(Gfx::Bitmap& bitmap, int y, const void* dummy_scanline_data)
|
||||
{
|
||||
auto* dummy_scanline = (const Pixel*)dummy_scanline_data;
|
||||
if constexpr (filter_type == 0) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (filter_type == 1) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
swap(pixels[0].r, pixels[0].b);
|
||||
for (int i = 1; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
auto& a = (const Pixel&)pixels[i - 1];
|
||||
x.v[0] += a.v[0];
|
||||
x.v[1] += a.v[1];
|
||||
x.v[2] += a.v[2];
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] += a.v[3];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (filter_type == 2) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
const Pixel& b = pixels_y_minus_1[i];
|
||||
x.v[0] += b.v[0];
|
||||
x.v[1] += b.v[1];
|
||||
x.v[2] += b.v[2];
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] += b.v[3];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (filter_type == 3) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
Pixel a;
|
||||
if (i != 0)
|
||||
a = pixels[i - 1];
|
||||
const Pixel& b = pixels_y_minus_1[i];
|
||||
x.v[0] = x.v[0] + ((a.v[0] + b.v[0]) / 2);
|
||||
x.v[1] = x.v[1] + ((a.v[1] + b.v[1]) / 2);
|
||||
x.v[2] = x.v[2] + ((a.v[2] + b.v[2]) / 2);
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] = x.v[3] + ((a.v[3] + b.v[3]) / 2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (filter_type == 4) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
Pixel a;
|
||||
const Pixel& b = pixels_y_minus_1[i];
|
||||
Pixel c;
|
||||
if (i != 0) {
|
||||
a = pixels[i - 1];
|
||||
c = pixels_y_minus_1[i - 1];
|
||||
}
|
||||
x.v[0] += paeth_predictor(a.v[0], b.v[0], c.v[0]);
|
||||
x.v[1] += paeth_predictor(a.v[1], b.v[1], c.v[1]);
|
||||
x.v[2] += paeth_predictor(a.v[2], b.v[2], c.v[2]);
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] += paeth_predictor(a.v[3], b.v[3], c.v[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::noinline]] static void unfilter(PNGLoadingContext& context)
|
||||
{
|
||||
// First unpack the scanlines to RGBA:
|
||||
switch (context.color_type) {
|
||||
case 2:
|
||||
if (context.bit_depth == 8) {
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
auto* triplets = (Triplet*)context.scanlines[y].data.data();
|
||||
for (int i = 0; i < context.width; ++i) {
|
||||
auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
|
||||
pixel.r = triplets[i].r;
|
||||
pixel.g = triplets[i].g;
|
||||
pixel.b = triplets[i].b;
|
||||
pixel.a = 0xff;
|
||||
}
|
||||
}
|
||||
} else if (context.bit_depth == 16) {
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
auto* triplets = (Triplet16*)context.scanlines[y].data.data();
|
||||
for (int i = 0; i < context.width; ++i) {
|
||||
auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
|
||||
pixel.r = triplets[i].r & 0xFF;
|
||||
pixel.g = triplets[i].g & 0xFF;
|
||||
pixel.b = triplets[i].b & 0xFF;
|
||||
pixel.a = 0xff;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (context.bit_depth == 8) {
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
memcpy(context.bitmap->scanline(y), context.scanlines[y].data.data(), context.scanlines[y].data.size());
|
||||
}
|
||||
} else if (context.bit_depth == 16) {
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
auto* triplets = (Quad16*)context.scanlines[y].data.data();
|
||||
for (int i = 0; i < context.width; ++i) {
|
||||
auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
|
||||
pixel.r = triplets[i].r & 0xFF;
|
||||
pixel.g = triplets[i].g & 0xFF;
|
||||
pixel.b = triplets[i].b & 0xFF;
|
||||
pixel.a = triplets[i].a & 0xFF;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
auto* palette_index = (u8*)context.scanlines[y].data.data();
|
||||
for (int i = 0; i < context.width; ++i) {
|
||||
auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
|
||||
auto& color = context.palette_data.at((int)palette_index[i]);
|
||||
auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1
|
||||
? (int)context.palette_transparency_data.data()[palette_index[i]]
|
||||
: 0xFF;
|
||||
pixel.r = color.r;
|
||||
pixel.g = color.g;
|
||||
pixel.b = color.b;
|
||||
pixel.a = transparency;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
|
||||
auto dummy_scanline = ByteBuffer::create_zeroed(context.width * sizeof(RGBA32));
|
||||
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
auto filter = context.scanlines[y].filter;
|
||||
if (filter == 0) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, 0>(*context.bitmap, y, dummy_scanline.data());
|
||||
else
|
||||
unfilter_impl<false, 0>(*context.bitmap, y, dummy_scanline.data());
|
||||
continue;
|
||||
}
|
||||
if (filter == 1) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, 1>(*context.bitmap, y, dummy_scanline.data());
|
||||
else
|
||||
unfilter_impl<false, 1>(*context.bitmap, y, dummy_scanline.data());
|
||||
continue;
|
||||
}
|
||||
if (filter == 2) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, 2>(*context.bitmap, y, dummy_scanline.data());
|
||||
else
|
||||
unfilter_impl<false, 2>(*context.bitmap, y, dummy_scanline.data());
|
||||
continue;
|
||||
}
|
||||
if (filter == 3) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, 3>(*context.bitmap, y, dummy_scanline.data());
|
||||
else
|
||||
unfilter_impl<false, 3>(*context.bitmap, y, dummy_scanline.data());
|
||||
continue;
|
||||
}
|
||||
if (filter == 4) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, 4>(*context.bitmap, y, dummy_scanline.data());
|
||||
else
|
||||
unfilter_impl<false, 4>(*context.bitmap, y, dummy_scanline.data());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool decode_png_header(PNGLoadingContext& context)
|
||||
{
|
||||
if (context.state >= PNGLoadingContext::HeaderDecoded)
|
||||
return true;
|
||||
|
||||
if (memcmp(context.data, png_header, sizeof(png_header)) != 0) {
|
||||
dbg() << "Invalid PNG header";
|
||||
context.state = PNGLoadingContext::State::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
context.state = PNGLoadingContext::HeaderDecoded;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_png_size(PNGLoadingContext& context)
|
||||
{
|
||||
if (context.state >= PNGLoadingContext::SizeDecoded)
|
||||
return true;
|
||||
|
||||
if (context.state < PNGLoadingContext::HeaderDecoded) {
|
||||
if (!decode_png_header(context))
|
||||
return false;
|
||||
}
|
||||
|
||||
const u8* data_ptr = context.data + sizeof(png_header);
|
||||
size_t data_remaining = context.data_size - sizeof(png_header);
|
||||
|
||||
Streamer streamer(data_ptr, data_remaining);
|
||||
while (!streamer.at_end()) {
|
||||
if (!process_chunk(streamer, context, true)) {
|
||||
context.state = PNGLoadingContext::State::Error;
|
||||
return false;
|
||||
}
|
||||
if (context.width && context.height) {
|
||||
context.state = PNGLoadingContext::State::SizeDecoded;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool decode_png_chunks(PNGLoadingContext& context)
|
||||
{
|
||||
if (context.state >= PNGLoadingContext::State::ChunksDecoded)
|
||||
return true;
|
||||
|
||||
if (context.state < PNGLoadingContext::HeaderDecoded) {
|
||||
if (!decode_png_header(context))
|
||||
return false;
|
||||
}
|
||||
|
||||
const u8* data_ptr = context.data + sizeof(png_header);
|
||||
int data_remaining = context.data_size - sizeof(png_header);
|
||||
|
||||
context.compressed_data.ensure_capacity(context.data_size);
|
||||
|
||||
Streamer streamer(data_ptr, data_remaining);
|
||||
while (!streamer.at_end()) {
|
||||
if (!process_chunk(streamer, context, false)) {
|
||||
context.state = PNGLoadingContext::State::Error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.state = PNGLoadingContext::State::ChunksDecoded;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_png_bitmap(PNGLoadingContext& context)
|
||||
{
|
||||
if (context.state < PNGLoadingContext::State::ChunksDecoded) {
|
||||
if (!decode_png_chunks(context))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.state >= PNGLoadingContext::State::BitmapDecoded)
|
||||
return true;
|
||||
|
||||
unsigned long srclen = context.compressed_data.size() - 6;
|
||||
unsigned long destlen = context.decompression_buffer_size;
|
||||
int ret = puff(context.decompression_buffer, &destlen, context.compressed_data.data() + 2, &srclen);
|
||||
if (ret < 0) {
|
||||
context.state = PNGLoadingContext::State::Error;
|
||||
return false;
|
||||
}
|
||||
context.compressed_data.clear();
|
||||
|
||||
context.scanlines.ensure_capacity(context.height);
|
||||
Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
u8 filter;
|
||||
if (!streamer.read(filter)) {
|
||||
context.state = PNGLoadingContext::State::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
context.scanlines.append({ filter });
|
||||
auto& scanline_buffer = context.scanlines.last().data;
|
||||
if (!streamer.wrap_bytes(scanline_buffer, context.width * context.bytes_per_pixel)) {
|
||||
context.state = PNGLoadingContext::State::Error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? Bitmap::Format::RGBA32 : Bitmap::Format::RGB32, { context.width, context.height });
|
||||
|
||||
unfilter(context);
|
||||
|
||||
munmap(context.decompression_buffer, context.decompression_buffer_size);
|
||||
context.decompression_buffer = nullptr;
|
||||
context.decompression_buffer_size = 0;
|
||||
|
||||
context.state = PNGLoadingContext::State::BitmapDecoded;
|
||||
return true;
|
||||
}
|
||||
|
||||
static RefPtr<Gfx::Bitmap> load_png_impl(const u8* data, int data_size)
|
||||
{
|
||||
PNGLoadingContext context;
|
||||
context.data = data;
|
||||
context.data_size = data_size;
|
||||
|
||||
if (!decode_png_chunks(context))
|
||||
return nullptr;
|
||||
|
||||
if (!decode_png_bitmap(context))
|
||||
return nullptr;
|
||||
|
||||
return context.bitmap;
|
||||
}
|
||||
|
||||
static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, bool decode_size_only = false)
|
||||
{
|
||||
if (data.size() < (int)sizeof(PNG_IHDR))
|
||||
return false;
|
||||
auto& ihdr = *(const PNG_IHDR*)data.data();
|
||||
context.width = ihdr.width;
|
||||
context.height = ihdr.height;
|
||||
context.bit_depth = ihdr.bit_depth;
|
||||
context.color_type = ihdr.color_type;
|
||||
context.compression_method = ihdr.compression_method;
|
||||
context.filter_method = ihdr.filter_method;
|
||||
context.interlace_method = ihdr.interlace_method;
|
||||
|
||||
#ifdef PNG_DEBUG
|
||||
printf("PNG: %dx%d (%d bpp)\n", context.width, context.height, context.bit_depth);
|
||||
printf(" Color type: %d\n", context.color_type);
|
||||
printf("Compress Method: %d\n", context.compression_method);
|
||||
printf(" Filter Method: %d\n", context.filter_method);
|
||||
printf(" Interlace type: %d\n", context.interlace_method);
|
||||
#endif
|
||||
|
||||
// FIXME: Implement Adam7 deinterlacing
|
||||
if (context.interlace_method != 0) {
|
||||
dbgprintf("PNGLoader::process_IHDR: Interlaced PNGs not currently supported.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (context.color_type) {
|
||||
case 0: // Each pixel is a grayscale sample.
|
||||
case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
|
||||
// FIXME: Implement grayscale PNG support.
|
||||
dbgprintf("PNGLoader::process_IHDR: Unsupported grayscale format.\n");
|
||||
return false;
|
||||
case 2:
|
||||
context.bytes_per_pixel = 3 * (ihdr.bit_depth / 8);
|
||||
break;
|
||||
case 3: // Each pixel is a palette index; a PLTE chunk must appear.
|
||||
// FIXME: Implement support for 1/2/4 bit palette based images.
|
||||
if (ihdr.bit_depth != 8) {
|
||||
dbgprintf("PNGLoader::process_IHDR: Unsupported index-based format (%d bpp).\n", context.bit_depth);
|
||||
return false;
|
||||
}
|
||||
context.bytes_per_pixel = 1;
|
||||
break;
|
||||
case 6:
|
||||
context.bytes_per_pixel = 4 * (ihdr.bit_depth / 8);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (!decode_size_only) {
|
||||
context.decompression_buffer_size = (context.width * context.height * context.bytes_per_pixel + context.height);
|
||||
context.decompression_buffer = (u8*)mmap_with_name(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, "PNG decompression buffer");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_IDAT(const ByteBuffer& data, PNGLoadingContext& context)
|
||||
{
|
||||
context.compressed_data.append(data.data(), data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_PLTE(const ByteBuffer& data, PNGLoadingContext& context)
|
||||
{
|
||||
context.palette_data.append((const PaletteEntry*)data.data(), data.size() / 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_tRNS(const ByteBuffer& data, PNGLoadingContext& context)
|
||||
{
|
||||
switch (context.color_type) {
|
||||
case 3:
|
||||
context.palette_transparency_data.append(data.data(), data.size());
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_chunk(Streamer& streamer, PNGLoadingContext& context, bool decode_size_only)
|
||||
{
|
||||
u32 chunk_size;
|
||||
if (!streamer.read(chunk_size)) {
|
||||
printf("Bail at chunk_size\n");
|
||||
return false;
|
||||
}
|
||||
u8 chunk_type[5];
|
||||
chunk_type[4] = '\0';
|
||||
if (!streamer.read_bytes(chunk_type, 4)) {
|
||||
printf("Bail at chunk_type\n");
|
||||
return false;
|
||||
}
|
||||
ByteBuffer chunk_data;
|
||||
if (!streamer.wrap_bytes(chunk_data, chunk_size)) {
|
||||
printf("Bail at chunk_data\n");
|
||||
return false;
|
||||
}
|
||||
u32 chunk_crc;
|
||||
if (!streamer.read(chunk_crc)) {
|
||||
printf("Bail at chunk_crc\n");
|
||||
return false;
|
||||
}
|
||||
#ifdef PNG_DEBUG
|
||||
printf("Chunk type: '%s', size: %u, crc: %x\n", chunk_type, chunk_size, chunk_crc);
|
||||
#endif
|
||||
|
||||
if (!strcmp((const char*)chunk_type, "IHDR"))
|
||||
return process_IHDR(chunk_data, context, decode_size_only);
|
||||
if (!strcmp((const char*)chunk_type, "IDAT"))
|
||||
return process_IDAT(chunk_data, context);
|
||||
if (!strcmp((const char*)chunk_type, "PLTE"))
|
||||
return process_PLTE(chunk_data, context);
|
||||
if (!strcmp((const char*)chunk_type, "tRNS"))
|
||||
return process_tRNS(chunk_data, context);
|
||||
return true;
|
||||
}
|
||||
|
||||
PNGImageDecoderPlugin::PNGImageDecoderPlugin(const u8* data, size_t size)
|
||||
{
|
||||
m_context = make<PNGLoadingContext>();
|
||||
m_context->data = data;
|
||||
m_context->data_size = size;
|
||||
}
|
||||
|
||||
PNGImageDecoderPlugin::~PNGImageDecoderPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
Size PNGImageDecoderPlugin::size()
|
||||
{
|
||||
if (m_context->state == PNGLoadingContext::State::Error)
|
||||
return {};
|
||||
|
||||
if (m_context->state < PNGLoadingContext::State::SizeDecoded) {
|
||||
bool success = decode_png_size(*m_context);
|
||||
if (!success)
|
||||
return {};
|
||||
}
|
||||
|
||||
return { m_context->width, m_context->height };
|
||||
}
|
||||
|
||||
RefPtr<Gfx::Bitmap> PNGImageDecoderPlugin::bitmap()
|
||||
{
|
||||
if (m_context->state == PNGLoadingContext::State::Error)
|
||||
return nullptr;
|
||||
|
||||
if (m_context->state < PNGLoadingContext::State::BitmapDecoded) {
|
||||
// NOTE: This forces the chunk decoding to happen.
|
||||
bool success = decode_png_bitmap(*m_context);
|
||||
if (!success)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ASSERT(m_context->bitmap);
|
||||
return m_context->bitmap;
|
||||
}
|
||||
|
||||
void PNGImageDecoderPlugin::set_volatile()
|
||||
{
|
||||
if (m_context->bitmap)
|
||||
m_context->bitmap->set_volatile();
|
||||
}
|
||||
|
||||
bool PNGImageDecoderPlugin::set_nonvolatile()
|
||||
{
|
||||
if (!m_context->bitmap)
|
||||
return false;
|
||||
return m_context->bitmap->set_nonvolatile();
|
||||
}
|
||||
|
||||
}
|
53
Libraries/LibGfx/PNGLoader.h
Normal file
53
Libraries/LibGfx/PNGLoader.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/GraphicsBitmap.h>
|
||||
#include <LibGfx/ImageDecoder.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
RefPtr<Gfx::Bitmap> load_png(const StringView& path);
|
||||
RefPtr<Gfx::Bitmap> load_png_from_memory(const u8*, size_t);
|
||||
|
||||
struct PNGLoadingContext;
|
||||
|
||||
class PNGImageDecoderPlugin final : public ImageDecoderPlugin {
|
||||
public:
|
||||
virtual ~PNGImageDecoderPlugin() override;
|
||||
PNGImageDecoderPlugin(const u8*, size_t);
|
||||
|
||||
virtual Size size() override;
|
||||
virtual RefPtr<Gfx::Bitmap> bitmap() override;
|
||||
virtual void set_volatile() override;
|
||||
[[nodiscard]] virtual bool set_nonvolatile() override;
|
||||
|
||||
private:
|
||||
OwnPtr<PNGLoadingContext> m_context;
|
||||
};
|
||||
|
||||
}
|
958
Libraries/LibGfx/Painter.cpp
Normal file
958
Libraries/LibGfx/Painter.cpp
Normal file
|
@ -0,0 +1,958 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Painter.h"
|
||||
#include "Emoji.h"
|
||||
#include "Font.h"
|
||||
#include "GraphicsBitmap.h"
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibGfx/CharacterBitmap.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC optimize("O3")
|
||||
#endif
|
||||
|
||||
#ifndef ALWAYS_INLINE
|
||||
#if __has_attribute(always_inline)
|
||||
#define ALWAYS_INLINE __attribute__((always_inline))
|
||||
#else
|
||||
#define ALWAYS_INLINE inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
template<Bitmap::Format format = Bitmap::Format::Invalid>
|
||||
static ALWAYS_INLINE Color get_pixel(const Gfx::Bitmap& bitmap, int x, int y)
|
||||
{
|
||||
if constexpr (format == Bitmap::Format::Indexed8)
|
||||
return bitmap.palette_color(bitmap.bits(y)[x]);
|
||||
if constexpr (format == Bitmap::Format::RGB32)
|
||||
return Color::from_rgb(bitmap.scanline(y)[x]);
|
||||
if constexpr (format == Bitmap::Format::RGBA32)
|
||||
return Color::from_rgba(bitmap.scanline(y)[x]);
|
||||
return bitmap.get_pixel(x, y);
|
||||
}
|
||||
|
||||
Painter::Painter(Gfx::Bitmap& bitmap)
|
||||
: m_target(bitmap)
|
||||
{
|
||||
m_state_stack.append(State());
|
||||
state().font = &Font::default_font();
|
||||
state().clip_rect = { { 0, 0 }, bitmap.size() };
|
||||
m_clip_origin = state().clip_rect;
|
||||
}
|
||||
|
||||
Painter::~Painter()
|
||||
{
|
||||
}
|
||||
|
||||
void Painter::fill_rect_with_draw_op(const Rect& a_rect, Color color)
|
||||
{
|
||||
auto rect = a_rect.translated(translation()).intersected(clip_rect());
|
||||
if (rect.is_empty())
|
||||
return;
|
||||
|
||||
RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int i = rect.height() - 1; i >= 0; --i) {
|
||||
for (int j = 0; j < rect.width(); ++j)
|
||||
set_pixel_with_draw_op(dst[j], color);
|
||||
dst += dst_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::clear_rect(const Rect& a_rect, Color color)
|
||||
{
|
||||
auto rect = a_rect.translated(translation()).intersected(clip_rect());
|
||||
if (rect.is_empty())
|
||||
return;
|
||||
|
||||
ASSERT(m_target->rect().contains(rect));
|
||||
|
||||
RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int i = rect.height() - 1; i >= 0; --i) {
|
||||
fast_u32_fill(dst, color.value(), rect.width());
|
||||
dst += dst_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::fill_rect(const Rect& a_rect, Color color)
|
||||
{
|
||||
if (color.alpha() == 0)
|
||||
return;
|
||||
|
||||
if (draw_op() != DrawOp::Copy) {
|
||||
fill_rect_with_draw_op(a_rect, color);
|
||||
return;
|
||||
}
|
||||
|
||||
if (color.alpha() == 0xff) {
|
||||
clear_rect(a_rect, color);
|
||||
return;
|
||||
}
|
||||
|
||||
auto rect = a_rect.translated(translation()).intersected(clip_rect());
|
||||
if (rect.is_empty())
|
||||
return;
|
||||
|
||||
ASSERT(m_target->rect().contains(rect));
|
||||
|
||||
RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int i = rect.height() - 1; i >= 0; --i) {
|
||||
for (int j = 0; j < rect.width(); ++j)
|
||||
dst[j] = Color::from_rgba(dst[j]).blend(color).value();
|
||||
dst += dst_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::fill_rect_with_gradient(const Rect& a_rect, Color gradient_start, Color gradient_end)
|
||||
{
|
||||
#ifdef NO_FPU
|
||||
return fill_rect(a_rect, gradient_start);
|
||||
#endif
|
||||
auto rect = a_rect.translated(translation());
|
||||
auto clipped_rect = Rect::intersection(rect, clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
|
||||
int x_offset = clipped_rect.x() - rect.x();
|
||||
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.top()) + clipped_rect.left();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
float increment = (1.0 / ((rect.width()) / 255.0));
|
||||
|
||||
int r2 = gradient_start.red();
|
||||
int g2 = gradient_start.green();
|
||||
int b2 = gradient_start.blue();
|
||||
int r1 = gradient_end.red();
|
||||
int g1 = gradient_end.green();
|
||||
int b1 = gradient_end.blue();
|
||||
|
||||
for (int i = clipped_rect.height() - 1; i >= 0; --i) {
|
||||
float c = x_offset * increment;
|
||||
for (int j = 0; j < clipped_rect.width(); ++j) {
|
||||
dst[j] = Color(
|
||||
r1 / 255.0 * c + r2 / 255.0 * (255 - c),
|
||||
g1 / 255.0 * c + g2 / 255.0 * (255 - c),
|
||||
b1 / 255.0 * c + b2 / 255.0 * (255 - c))
|
||||
.value();
|
||||
c += increment;
|
||||
}
|
||||
dst += dst_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_ellipse_intersecting(const Rect& rect, Color color, int thickness)
|
||||
{
|
||||
constexpr int number_samples = 100; // FIXME: dynamically work out the number of samples based upon the rect size
|
||||
double increment = M_PI / number_samples;
|
||||
|
||||
auto ellipse_x = [&](double theta) -> int {
|
||||
return (cos(theta) * rect.width() / sqrt(2)) + rect.center().x();
|
||||
};
|
||||
|
||||
auto ellipse_y = [&](double theta) -> int {
|
||||
return (sin(theta) * rect.height() / sqrt(2)) + rect.center().y();
|
||||
};
|
||||
|
||||
for (float theta = 0; theta < 2 * M_PI; theta += increment) {
|
||||
draw_line({ ellipse_x(theta), ellipse_y(theta) }, { ellipse_x(theta + increment), ellipse_y(theta + increment) }, color, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_rect(const Rect& a_rect, Color color, bool rough)
|
||||
{
|
||||
Rect rect = a_rect.translated(translation());
|
||||
auto clipped_rect = rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
|
||||
int min_y = clipped_rect.top();
|
||||
int max_y = clipped_rect.bottom();
|
||||
|
||||
if (rect.top() >= clipped_rect.top() && rect.top() <= clipped_rect.bottom()) {
|
||||
int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
|
||||
int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
|
||||
fast_u32_fill(m_target->scanline(rect.top()) + start_x, color.value(), width);
|
||||
++min_y;
|
||||
}
|
||||
if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) {
|
||||
int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
|
||||
int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
|
||||
fast_u32_fill(m_target->scanline(rect.bottom()) + start_x, color.value(), width);
|
||||
--max_y;
|
||||
}
|
||||
|
||||
bool draw_left_side = rect.left() >= clipped_rect.left();
|
||||
bool draw_right_side = rect.right() == clipped_rect.right();
|
||||
|
||||
if (draw_left_side && draw_right_side) {
|
||||
// Specialized loop when drawing both sides.
|
||||
for (int y = min_y; y <= max_y; ++y) {
|
||||
auto* bits = m_target->scanline(y);
|
||||
bits[rect.left()] = color.value();
|
||||
bits[rect.right()] = color.value();
|
||||
}
|
||||
} else {
|
||||
for (int y = min_y; y <= max_y; ++y) {
|
||||
auto* bits = m_target->scanline(y);
|
||||
if (draw_left_side)
|
||||
bits[rect.left()] = color.value();
|
||||
if (draw_right_side)
|
||||
bits[rect.right()] = color.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_bitmap(const Point& p, const CharacterBitmap& bitmap, Color color)
|
||||
{
|
||||
auto rect = Rect(p, bitmap.size()).translated(translation());
|
||||
auto clipped_rect = rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = clipped_rect.top() - rect.top();
|
||||
const int last_row = clipped_rect.bottom() - rect.top();
|
||||
const int first_column = clipped_rect.left() - rect.left();
|
||||
const int last_column = clipped_rect.right() - rect.left();
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
const char* bitmap_row = &bitmap.bits()[first_row * bitmap.width() + first_column];
|
||||
const size_t bitmap_skip = bitmap.width();
|
||||
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
for (int j = 0; j <= (last_column - first_column); ++j) {
|
||||
char fc = bitmap_row[j];
|
||||
if (fc == '#')
|
||||
dst[j] = color.value();
|
||||
}
|
||||
bitmap_row += bitmap_skip;
|
||||
dst += dst_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_bitmap(const Point& p, const GlyphBitmap& bitmap, Color color)
|
||||
{
|
||||
auto dst_rect = Rect(p, bitmap.size()).translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = clipped_rect.top() - dst_rect.top();
|
||||
const int last_row = clipped_rect.bottom() - dst_rect.top();
|
||||
const int first_column = clipped_rect.left() - dst_rect.left();
|
||||
const int last_column = clipped_rect.right() - dst_rect.left();
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
for (int j = 0; j <= (last_column - first_column); ++j) {
|
||||
if (bitmap.bit_at(j + first_column, row))
|
||||
dst[j] = color.value();
|
||||
}
|
||||
dst += dst_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::blit_scaled(const Rect& dst_rect_raw, const Gfx::Bitmap& source, const Rect& src_rect, float hscale, float vscale)
|
||||
{
|
||||
auto dst_rect = Rect(dst_rect_raw.location(), dst_rect_raw.size()).translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = (clipped_rect.top() - dst_rect.top());
|
||||
const int last_row = (clipped_rect.bottom() - dst_rect.top());
|
||||
const int first_column = (clipped_rect.left() - dst_rect.left());
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
int x_start = first_column + src_rect.left();
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
int sr = (row + src_rect.top()) * vscale;
|
||||
if (sr >= source.size().height() || sr < 0) {
|
||||
dst += dst_skip;
|
||||
continue;
|
||||
}
|
||||
const RGBA32* sl = source.scanline(sr);
|
||||
for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
|
||||
int sx = x * hscale;
|
||||
if (sx < source.size().width() && sx >= 0)
|
||||
dst[x - x_start] = sl[sx];
|
||||
}
|
||||
dst += dst_skip;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void Painter::blit_with_opacity(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect, float opacity)
|
||||
{
|
||||
ASSERT(!m_target->has_alpha_channel());
|
||||
|
||||
if (!opacity)
|
||||
return;
|
||||
if (opacity >= 1.0f)
|
||||
return blit(position, source, src_rect);
|
||||
|
||||
u8 alpha = 255 * opacity;
|
||||
|
||||
Rect safe_src_rect = Rect::intersection(src_rect, source.rect());
|
||||
Rect dst_rect(position, safe_src_rect.size());
|
||||
dst_rect.move_by(state().translation);
|
||||
auto clipped_rect = Rect::intersection(dst_rect, clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = clipped_rect.top() - dst_rect.top();
|
||||
const int last_row = clipped_rect.bottom() - dst_rect.top();
|
||||
const int first_column = clipped_rect.left() - dst_rect.left();
|
||||
const int last_column = clipped_rect.right() - dst_rect.left();
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
const unsigned src_skip = source.pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
for (int x = 0; x <= (last_column - first_column); ++x) {
|
||||
Color src_color_with_alpha = Color::from_rgb(src[x]);
|
||||
src_color_with_alpha.set_alpha(alpha);
|
||||
Color dst_color = Color::from_rgb(dst[x]);
|
||||
dst[x] = dst_color.blend(src_color_with_alpha).value();
|
||||
}
|
||||
dst += dst_skip;
|
||||
src += src_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::blit_dimmed(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect)
|
||||
{
|
||||
Rect safe_src_rect = src_rect.intersected(source.rect());
|
||||
auto dst_rect = Rect(position, safe_src_rect.size()).translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = clipped_rect.top() - dst_rect.top();
|
||||
const int last_row = clipped_rect.bottom() - dst_rect.top();
|
||||
const int first_column = clipped_rect.left() - dst_rect.left();
|
||||
const int last_column = clipped_rect.right() - dst_rect.left();
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
const size_t src_skip = source.pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
for (int x = 0; x <= (last_column - first_column); ++x) {
|
||||
u8 alpha = Color::from_rgba(src[x]).alpha();
|
||||
if (alpha == 0xff)
|
||||
dst[x] = Color::from_rgba(src[x]).to_grayscale().lightened().value();
|
||||
else if (!alpha)
|
||||
continue;
|
||||
else
|
||||
dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x]).to_grayscale().lightened()).value();
|
||||
}
|
||||
dst += dst_skip;
|
||||
src += src_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_tiled_bitmap(const Rect& a_dst_rect, const Gfx::Bitmap& source)
|
||||
{
|
||||
auto dst_rect = a_dst_rect.translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = (clipped_rect.top() - dst_rect.top());
|
||||
const int last_row = (clipped_rect.bottom() - dst_rect.top());
|
||||
const int first_column = (clipped_rect.left() - dst_rect.left());
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
if (source.format() == Bitmap::Format::RGB32 || source.format() == Bitmap::Format::RGBA32) {
|
||||
int x_start = first_column + a_dst_rect.left();
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
const RGBA32* sl = source.scanline((row + a_dst_rect.top())
|
||||
% source.size().height());
|
||||
for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
|
||||
dst[x - x_start] = sl[x % source.size().width()];
|
||||
}
|
||||
dst += dst_skip;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Painter::blit_offset(const Point& position,
|
||||
const Gfx::Bitmap& source,
|
||||
const Rect& src_rect,
|
||||
const Point& offset)
|
||||
{
|
||||
auto dst_rect = Rect(position, src_rect.size()).translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = (clipped_rect.top() - dst_rect.top());
|
||||
const int last_row = (clipped_rect.bottom() - dst_rect.top());
|
||||
const int first_column = (clipped_rect.left() - dst_rect.left());
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
if (source.format() == Bitmap::Format::RGB32 || source.format() == Bitmap::Format::RGBA32) {
|
||||
int x_start = first_column + src_rect.left();
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
int sr = row - offset.y() + src_rect.top();
|
||||
if (sr >= source.size().height() || sr < 0) {
|
||||
dst += dst_skip;
|
||||
continue;
|
||||
}
|
||||
const RGBA32* sl = source.scanline(sr);
|
||||
for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
|
||||
int sx = x - offset.x();
|
||||
if (sx < source.size().width() && sx >= 0)
|
||||
dst[x - x_start] = sl[sx];
|
||||
}
|
||||
dst += dst_skip;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Painter::blit_with_alpha(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect)
|
||||
{
|
||||
ASSERT(source.has_alpha_channel());
|
||||
Rect safe_src_rect = src_rect.intersected(source.rect());
|
||||
auto dst_rect = Rect(position, safe_src_rect.size()).translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = clipped_rect.top() - dst_rect.top();
|
||||
const int last_row = clipped_rect.bottom() - dst_rect.top();
|
||||
const int first_column = clipped_rect.left() - dst_rect.left();
|
||||
const int last_column = clipped_rect.right() - dst_rect.left();
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
const size_t src_skip = source.pitch() / sizeof(RGBA32);
|
||||
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
for (int x = 0; x <= (last_column - first_column); ++x) {
|
||||
u8 alpha = Color::from_rgba(src[x]).alpha();
|
||||
if (alpha == 0xff)
|
||||
dst[x] = src[x];
|
||||
else if (!alpha)
|
||||
continue;
|
||||
else
|
||||
dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x])).value();
|
||||
}
|
||||
dst += dst_skip;
|
||||
src += src_skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::blit(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect, float opacity)
|
||||
{
|
||||
if (opacity < 1.0f)
|
||||
return blit_with_opacity(position, source, src_rect, opacity);
|
||||
if (source.has_alpha_channel())
|
||||
return blit_with_alpha(position, source, src_rect);
|
||||
auto safe_src_rect = src_rect.intersected(source.rect());
|
||||
ASSERT(source.rect().contains(safe_src_rect));
|
||||
auto dst_rect = Rect(position, safe_src_rect.size()).translated(translation());
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
const int first_row = clipped_rect.top() - dst_rect.top();
|
||||
const int last_row = clipped_rect.bottom() - dst_rect.top();
|
||||
const int first_column = clipped_rect.left() - dst_rect.left();
|
||||
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
|
||||
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
|
||||
|
||||
if (source.format() == Bitmap::Format::RGB32 || source.format() == Bitmap::Format::RGBA32) {
|
||||
const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
|
||||
const size_t src_skip = source.pitch() / sizeof(RGBA32);
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
fast_u32_copy(dst, src, clipped_rect.width());
|
||||
dst += dst_skip;
|
||||
src += src_skip;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.format() == Bitmap::Format::Indexed8) {
|
||||
const u8* src = source.bits(src_rect.top() + first_row) + src_rect.left() + first_column;
|
||||
const size_t src_skip = source.pitch();
|
||||
for (int row = first_row; row <= last_row; ++row) {
|
||||
for (int i = 0; i < clipped_rect.width(); ++i)
|
||||
dst[i] = source.palette_color(src[i]).value();
|
||||
dst += dst_skip;
|
||||
src += src_skip;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
template<bool has_alpha_channel, typename GetPixel>
|
||||
ALWAYS_INLINE static void do_draw_integer_scaled_bitmap(Gfx::Bitmap& target, const Rect& dst_rect, const Gfx::Bitmap& source, int hfactor, int vfactor, GetPixel get_pixel)
|
||||
{
|
||||
for (int y = source.rect().top(); y <= source.rect().bottom(); ++y) {
|
||||
int dst_y = dst_rect.y() + y * vfactor;
|
||||
for (int x = source.rect().left(); x <= source.rect().right(); ++x) {
|
||||
auto src_pixel = get_pixel(source, x, y);
|
||||
for (int yo = 0; yo < vfactor; ++yo) {
|
||||
auto* scanline = (Color*)target.scanline(dst_y + yo);
|
||||
int dst_x = dst_rect.x() + x * hfactor;
|
||||
for (int xo = 0; xo < hfactor; ++xo) {
|
||||
if constexpr (has_alpha_channel)
|
||||
scanline[dst_x + xo] = scanline[dst_x + xo].blend(src_pixel);
|
||||
else
|
||||
scanline[dst_x + xo] = src_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<bool has_alpha_channel, typename GetPixel>
|
||||
ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, const Rect& dst_rect, const Rect& clipped_rect, const Gfx::Bitmap& source, const Rect& src_rect, int hscale, int vscale, GetPixel get_pixel)
|
||||
{
|
||||
if (dst_rect == clipped_rect && !(dst_rect.width() % src_rect.width()) && !(dst_rect.height() % src_rect.height())) {
|
||||
int hfactor = dst_rect.width() / src_rect.width();
|
||||
int vfactor = dst_rect.height() / src_rect.height();
|
||||
if (hfactor == 2 && vfactor == 2)
|
||||
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 2, 2, get_pixel);
|
||||
if (hfactor == 3 && vfactor == 3)
|
||||
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 3, 3, get_pixel);
|
||||
if (hfactor == 4 && vfactor == 4)
|
||||
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 4, 4, get_pixel);
|
||||
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, hfactor, vfactor, get_pixel);
|
||||
}
|
||||
|
||||
for (int y = clipped_rect.top(); y <= clipped_rect.bottom(); ++y) {
|
||||
auto* scanline = (Color*)target.scanline(y);
|
||||
for (int x = clipped_rect.left(); x <= clipped_rect.right(); ++x) {
|
||||
auto scaled_x = ((x - dst_rect.x()) * hscale) >> 16;
|
||||
auto scaled_y = ((y - dst_rect.y()) * vscale) >> 16;
|
||||
auto src_pixel = get_pixel(source, scaled_x, scaled_y);
|
||||
|
||||
if constexpr (has_alpha_channel) {
|
||||
scanline[x] = scanline[x].blend(src_pixel);
|
||||
} else
|
||||
scanline[x] = src_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_scaled_bitmap(const Rect& a_dst_rect, const Gfx::Bitmap& source, const Rect& src_rect)
|
||||
{
|
||||
auto dst_rect = a_dst_rect;
|
||||
if (dst_rect.size() == src_rect.size())
|
||||
return blit(dst_rect.location(), source, src_rect);
|
||||
|
||||
auto safe_src_rect = src_rect.intersected(source.rect());
|
||||
ASSERT(source.rect().contains(safe_src_rect));
|
||||
dst_rect.move_by(state().translation);
|
||||
auto clipped_rect = dst_rect.intersected(clip_rect());
|
||||
if (clipped_rect.is_empty())
|
||||
return;
|
||||
|
||||
int hscale = (src_rect.width() << 16) / dst_rect.width();
|
||||
int vscale = (src_rect.height() << 16) / dst_rect.height();
|
||||
|
||||
if (source.has_alpha_channel()) {
|
||||
switch (source.format()) {
|
||||
case Bitmap::Format::RGB32:
|
||||
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::RGB32>);
|
||||
break;
|
||||
case Bitmap::Format::RGBA32:
|
||||
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::RGBA32>);
|
||||
break;
|
||||
case Bitmap::Format::Indexed8:
|
||||
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::Indexed8>);
|
||||
break;
|
||||
default:
|
||||
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::Invalid>);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (source.format()) {
|
||||
case Bitmap::Format::RGB32:
|
||||
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::RGB32>);
|
||||
break;
|
||||
case Bitmap::Format::RGBA32:
|
||||
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::RGBA32>);
|
||||
break;
|
||||
case Bitmap::Format::Indexed8:
|
||||
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::Indexed8>);
|
||||
break;
|
||||
default:
|
||||
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<Bitmap::Format::Invalid>);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::flatten]] void Painter::draw_glyph(const Point& point, char ch, Color color)
|
||||
{
|
||||
draw_glyph(point, ch, font(), color);
|
||||
}
|
||||
|
||||
[[gnu::flatten]] void Painter::draw_glyph(const Point& point, char ch, const Font& font, Color color)
|
||||
{
|
||||
draw_bitmap(point, font.glyph_bitmap(ch), color);
|
||||
}
|
||||
|
||||
void Painter::draw_emoji(const Point& point, const Gfx::Bitmap& emoji, const Font& font)
|
||||
{
|
||||
if (!font.is_fixed_width())
|
||||
blit(point, emoji, emoji.rect());
|
||||
else {
|
||||
Rect dst_rect {
|
||||
point.x(),
|
||||
point.y(),
|
||||
font.glyph_width('x'),
|
||||
font.glyph_height()
|
||||
};
|
||||
draw_scaled_bitmap(dst_rect, emoji, emoji.rect());
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_glyph_or_emoji(const Point& point, u32 codepoint, const Font& font, Color color)
|
||||
{
|
||||
if (codepoint < 256) {
|
||||
// This looks like a regular character.
|
||||
draw_glyph(point, (char)codepoint, font, color);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perhaps it's an emoji?
|
||||
auto* emoji = Emoji::emoji_for_codepoint(codepoint);
|
||||
if (emoji == nullptr) {
|
||||
#ifdef EMOJI_DEBUG
|
||||
dbg() << "Failed to find an emoji for codepoint " << codepoint;
|
||||
#endif
|
||||
draw_glyph(point, '?', font, color);
|
||||
return;
|
||||
}
|
||||
|
||||
draw_emoji(point, *emoji, font);
|
||||
}
|
||||
|
||||
void Painter::draw_text_line(const Rect& a_rect, const Utf8View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||
{
|
||||
auto rect = a_rect;
|
||||
Utf8View final_text(text);
|
||||
String elided_text;
|
||||
if (elision == TextElision::Right) {
|
||||
int text_width = font.width(final_text);
|
||||
if (font.width(final_text) > rect.width()) {
|
||||
int glyph_spacing = font.glyph_spacing();
|
||||
int byte_offset = 0;
|
||||
int new_width = font.width("...");
|
||||
if (new_width < text_width) {
|
||||
for (auto it = final_text.begin(); it != final_text.end(); ++it) {
|
||||
u32 codepoint = *it;
|
||||
int glyph_width = font.glyph_or_emoji_width(codepoint);
|
||||
// NOTE: Glyph spacing should not be added after the last glyph on the line,
|
||||
// but since we are here because the last glyph does not actually fit on the line,
|
||||
// we don't have to worry about spacing.
|
||||
int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
|
||||
if (width_with_this_glyph_included > rect.width())
|
||||
break;
|
||||
byte_offset = final_text.byte_offset_of(it);
|
||||
new_width += glyph_width + glyph_spacing;
|
||||
}
|
||||
StringBuilder builder;
|
||||
builder.append(final_text.substring_view(0, byte_offset).as_string());
|
||||
builder.append("...");
|
||||
elided_text = builder.to_string();
|
||||
final_text = Utf8View { elided_text };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (alignment) {
|
||||
case TextAlignment::TopLeft:
|
||||
case TextAlignment::CenterLeft:
|
||||
break;
|
||||
case TextAlignment::TopRight:
|
||||
case TextAlignment::CenterRight:
|
||||
rect.set_x(rect.right() - font.width(final_text));
|
||||
break;
|
||||
case TextAlignment::Center: {
|
||||
auto shrunken_rect = rect;
|
||||
shrunken_rect.set_width(font.width(final_text));
|
||||
shrunken_rect.center_within(rect);
|
||||
rect = shrunken_rect;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto point = rect.location();
|
||||
int space_width = font.glyph_width(' ') + font.glyph_spacing();
|
||||
|
||||
for (u32 codepoint : final_text) {
|
||||
if (codepoint == ' ') {
|
||||
point.move_by(space_width, 0);
|
||||
continue;
|
||||
}
|
||||
draw_glyph_or_emoji(point, codepoint, font, color);
|
||||
point.move_by(font.glyph_or_emoji_width(codepoint) + font.glyph_spacing(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_text(const Rect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision)
|
||||
{
|
||||
draw_text(rect, text, font(), alignment, color, elision);
|
||||
}
|
||||
|
||||
void Painter::draw_text(const Rect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||
{
|
||||
Utf8View text { raw_text };
|
||||
Vector<Utf8View, 32> lines;
|
||||
|
||||
int start_of_current_line = 0;
|
||||
for (auto it = text.begin(); it != text.end(); ++it) {
|
||||
u32 codepoint = *it;
|
||||
if (codepoint == '\n') {
|
||||
int byte_offset = text.byte_offset_of(it);
|
||||
Utf8View line = text.substring_view(start_of_current_line, byte_offset - start_of_current_line);
|
||||
lines.append(line);
|
||||
start_of_current_line = byte_offset + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (start_of_current_line != text.byte_length()) {
|
||||
Utf8View line = text.substring_view(start_of_current_line, text.byte_length() - start_of_current_line);
|
||||
lines.append(line);
|
||||
}
|
||||
|
||||
static const int line_spacing = 4;
|
||||
int line_height = font.glyph_height() + line_spacing;
|
||||
Rect bounding_rect { 0, 0, 0, (lines.size() * line_height) - line_spacing };
|
||||
|
||||
for (auto& line : lines) {
|
||||
auto line_width = font.width(line);
|
||||
if (line_width > bounding_rect.width())
|
||||
bounding_rect.set_width(line_width);
|
||||
}
|
||||
|
||||
switch (alignment) {
|
||||
case TextAlignment::TopLeft:
|
||||
bounding_rect.set_location(rect.location());
|
||||
break;
|
||||
case TextAlignment::TopRight:
|
||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() });
|
||||
break;
|
||||
case TextAlignment::CenterLeft:
|
||||
bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
|
||||
break;
|
||||
case TextAlignment::CenterRight:
|
||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
|
||||
break;
|
||||
case TextAlignment::Center:
|
||||
bounding_rect.center_within(rect);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
auto& line = lines[i];
|
||||
Rect line_rect { bounding_rect.x(), bounding_rect.y() + i * line_height, bounding_rect.width(), line_height };
|
||||
line_rect.intersect(rect);
|
||||
draw_text_line(line_rect, line, font, alignment, color, elision);
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::set_pixel(const Point& p, Color color)
|
||||
{
|
||||
auto point = p;
|
||||
point.move_by(state().translation);
|
||||
if (!clip_rect().contains(point))
|
||||
return;
|
||||
m_target->scanline(point.y())[point.x()] = color.value();
|
||||
}
|
||||
|
||||
[[gnu::always_inline]] inline void Painter::set_pixel_with_draw_op(u32& pixel, const Color& color)
|
||||
{
|
||||
if (draw_op() == DrawOp::Copy)
|
||||
pixel = color.value();
|
||||
else if (draw_op() == DrawOp::Xor)
|
||||
pixel ^= color.value();
|
||||
}
|
||||
|
||||
void Painter::draw_pixel(const Point& position, Color color, int thickness)
|
||||
{
|
||||
ASSERT(draw_op() == DrawOp::Copy);
|
||||
if (thickness == 1)
|
||||
return set_pixel_with_draw_op(m_target->scanline(position.y())[position.x()], color);
|
||||
Rect rect { position.translated(-(thickness / 2), -(thickness / 2)), { thickness, thickness } };
|
||||
fill_rect(rect.translated(-state().translation), color);
|
||||
}
|
||||
|
||||
void Painter::draw_line(const Point& p1, const Point& p2, Color color, int thickness, bool dotted)
|
||||
{
|
||||
auto clip_rect = this->clip_rect();
|
||||
|
||||
auto point1 = p1;
|
||||
point1.move_by(state().translation);
|
||||
|
||||
auto point2 = p2;
|
||||
point2.move_by(state().translation);
|
||||
|
||||
// Special case: vertical line.
|
||||
if (point1.x() == point2.x()) {
|
||||
const int x = point1.x();
|
||||
if (x < clip_rect.left() || x > clip_rect.right())
|
||||
return;
|
||||
if (point1.y() > point2.y())
|
||||
swap(point1, point2);
|
||||
if (point1.y() > clip_rect.bottom())
|
||||
return;
|
||||
if (point2.y() < clip_rect.top())
|
||||
return;
|
||||
int min_y = max(point1.y(), clip_rect.top());
|
||||
int max_y = min(point2.y(), clip_rect.bottom());
|
||||
if (dotted) {
|
||||
for (int y = min_y; y <= max_y; y += 2)
|
||||
draw_pixel({ x, y }, color, thickness);
|
||||
} else {
|
||||
for (int y = min_y; y <= max_y; ++y)
|
||||
draw_pixel({ x, y }, color, thickness);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: horizontal line.
|
||||
if (point1.y() == point2.y()) {
|
||||
const int y = point1.y();
|
||||
if (y < clip_rect.top() || y > clip_rect.bottom())
|
||||
return;
|
||||
if (point1.x() > point2.x())
|
||||
swap(point1, point2);
|
||||
if (point1.x() > clip_rect.right())
|
||||
return;
|
||||
if (point2.x() < clip_rect.left())
|
||||
return;
|
||||
int min_x = max(point1.x(), clip_rect.left());
|
||||
int max_x = min(point2.x(), clip_rect.right());
|
||||
if (dotted) {
|
||||
for (int x = min_x; x <= max_x; x += 2)
|
||||
draw_pixel({ x, y }, color, thickness);
|
||||
} else {
|
||||
for (int x = min_x; x <= max_x; ++x)
|
||||
draw_pixel({ x, y }, color, thickness);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Implement dotted diagonal lines.
|
||||
ASSERT(!dotted);
|
||||
|
||||
const double adx = abs(point2.x() - point1.x());
|
||||
const double ady = abs(point2.y() - point1.y());
|
||||
|
||||
if (adx > ady) {
|
||||
if (point1.x() > point2.x())
|
||||
swap(point1, point2);
|
||||
} else {
|
||||
if (point1.y() > point2.y())
|
||||
swap(point1, point2);
|
||||
}
|
||||
|
||||
// FIXME: Implement clipping below.
|
||||
const double dx = point2.x() - point1.x();
|
||||
const double dy = point2.y() - point1.y();
|
||||
double error = 0;
|
||||
|
||||
if (dx > dy) {
|
||||
const double y_step = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
|
||||
const double delta_error = fabs(dy / dx);
|
||||
int y = point1.y();
|
||||
for (int x = point1.x(); x <= point2.x(); ++x) {
|
||||
if (clip_rect.contains(x, y))
|
||||
draw_pixel({ x, y }, color, thickness);
|
||||
error += delta_error;
|
||||
if (error >= 0.5) {
|
||||
y = (double)y + y_step;
|
||||
error -= 1.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const double x_step = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
|
||||
const double delta_error = fabs(dx / dy);
|
||||
int x = point1.x();
|
||||
for (int y = point1.y(); y <= point2.y(); ++y) {
|
||||
if (clip_rect.contains(x, y))
|
||||
draw_pixel({ x, y }, color, thickness);
|
||||
error += delta_error;
|
||||
if (error >= 0.5) {
|
||||
x = (double)x + x_step;
|
||||
error -= 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::add_clip_rect(const Rect& rect)
|
||||
{
|
||||
state().clip_rect.intersect(rect.translated(m_clip_origin.location()));
|
||||
state().clip_rect.intersect(m_target->rect());
|
||||
}
|
||||
|
||||
void Painter::clear_clip_rect()
|
||||
{
|
||||
state().clip_rect = m_clip_origin;
|
||||
}
|
||||
|
||||
PainterStateSaver::PainterStateSaver(Painter& painter)
|
||||
: m_painter(painter)
|
||||
{
|
||||
m_painter.save();
|
||||
}
|
||||
|
||||
PainterStateSaver::~PainterStateSaver()
|
||||
{
|
||||
m_painter.restore();
|
||||
}
|
||||
|
||||
}
|
133
Libraries/LibGfx/Painter.h
Normal file
133
Libraries/LibGfx/Painter.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Color.h"
|
||||
#include "Point.h"
|
||||
#include "Rect.h"
|
||||
#include "Size.h"
|
||||
#include <AK/String.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibGfx/TextAlignment.h>
|
||||
#include <LibGfx/TextElision.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class CharacterBitmap;
|
||||
class GlyphBitmap;
|
||||
class Bitmap;
|
||||
class Font;
|
||||
class Emoji;
|
||||
|
||||
class Painter {
|
||||
public:
|
||||
explicit Painter(Gfx::Bitmap&);
|
||||
~Painter();
|
||||
void clear_rect(const Rect&, Color);
|
||||
void fill_rect(const Rect&, Color);
|
||||
void fill_rect_with_gradient(const Rect&, Color gradient_start, Color gradient_end);
|
||||
void draw_rect(const Rect&, Color, bool rough = false);
|
||||
void draw_bitmap(const Point&, const CharacterBitmap&, Color = Color());
|
||||
void draw_bitmap(const Point&, const GlyphBitmap&, Color = Color());
|
||||
void draw_ellipse_intersecting(const Rect&, Color, int thickness = 1);
|
||||
void set_pixel(const Point&, Color);
|
||||
void draw_line(const Point&, const Point&, Color, int thickness = 1, bool dotted = false);
|
||||
void draw_scaled_bitmap(const Rect& dst_rect, const Gfx::Bitmap&, const Rect& src_rect);
|
||||
void blit(const Point&, const Gfx::Bitmap&, const Rect& src_rect, float opacity = 1.0f);
|
||||
void blit_dimmed(const Point&, const Gfx::Bitmap&, const Rect& src_rect);
|
||||
void draw_tiled_bitmap(const Rect& dst_rect, const Gfx::Bitmap&);
|
||||
void blit_offset(const Point&, const Gfx::Bitmap&, const Rect& src_rect, const Point&);
|
||||
void blit_scaled(const Rect&, const Gfx::Bitmap&, const Rect&, float, float);
|
||||
void draw_text(const Rect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
||||
void draw_text(const Rect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
||||
void draw_glyph(const Point&, char, Color);
|
||||
void draw_glyph(const Point&, char, const Font&, Color);
|
||||
void draw_emoji(const Point&, const Gfx::Bitmap&, const Font&);
|
||||
void draw_glyph_or_emoji(const Point&, u32 codepoint, const Font&, Color);
|
||||
|
||||
const Font& font() const { return *state().font; }
|
||||
void set_font(const Font& font) { state().font = &font; }
|
||||
|
||||
enum class DrawOp {
|
||||
Copy,
|
||||
Xor
|
||||
};
|
||||
void set_draw_op(DrawOp op) { state().draw_op = op; }
|
||||
DrawOp draw_op() const { return state().draw_op; }
|
||||
|
||||
void add_clip_rect(const Rect& rect);
|
||||
void clear_clip_rect();
|
||||
Rect clip_rect() const { return state().clip_rect; }
|
||||
|
||||
void translate(int dx, int dy) { state().translation.move_by(dx, dy); }
|
||||
void translate(const Point& delta) { state().translation.move_by(delta); }
|
||||
|
||||
Point translation() const { return state().translation; }
|
||||
|
||||
Gfx::Bitmap* target() { return m_target.ptr(); }
|
||||
|
||||
void save() { m_state_stack.append(m_state_stack.last()); }
|
||||
void restore()
|
||||
{
|
||||
ASSERT(m_state_stack.size() > 1);
|
||||
m_state_stack.take_last();
|
||||
}
|
||||
|
||||
protected:
|
||||
void set_pixel_with_draw_op(u32& pixel, const Color&);
|
||||
void fill_rect_with_draw_op(const Rect&, Color);
|
||||
void blit_with_alpha(const Point&, const Gfx::Bitmap&, const Rect& src_rect);
|
||||
void blit_with_opacity(const Point&, const Gfx::Bitmap&, const Rect& src_rect, float opacity);
|
||||
void draw_pixel(const Point&, Color, int thickness = 1);
|
||||
|
||||
void draw_text_line(const Rect&, const Utf8View&, const Font&, TextAlignment, Color, TextElision);
|
||||
|
||||
struct State {
|
||||
const Font* font;
|
||||
Point translation;
|
||||
Rect clip_rect;
|
||||
DrawOp draw_op;
|
||||
};
|
||||
|
||||
State& state() { return m_state_stack.last(); }
|
||||
const State& state() const { return m_state_stack.last(); }
|
||||
|
||||
Rect m_clip_origin;
|
||||
NonnullRefPtr<Gfx::Bitmap> m_target;
|
||||
Vector<State, 4> m_state_stack;
|
||||
};
|
||||
|
||||
class PainterStateSaver {
|
||||
public:
|
||||
explicit PainterStateSaver(Painter&);
|
||||
~PainterStateSaver();
|
||||
|
||||
private:
|
||||
Painter& m_painter;
|
||||
};
|
||||
|
||||
}
|
76
Libraries/LibGfx/Palette.cpp
Normal file
76
Libraries/LibGfx/Palette.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/Palette.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
NonnullRefPtr<PaletteImpl> PaletteImpl::create_with_shared_buffer(SharedBuffer& buffer)
|
||||
{
|
||||
return adopt(*new PaletteImpl(buffer));
|
||||
}
|
||||
|
||||
PaletteImpl::PaletteImpl(SharedBuffer& buffer)
|
||||
: m_theme_buffer(buffer)
|
||||
{
|
||||
}
|
||||
|
||||
Palette::Palette(const PaletteImpl& impl)
|
||||
: m_impl(impl)
|
||||
{
|
||||
}
|
||||
|
||||
Palette::~Palette()
|
||||
{
|
||||
}
|
||||
|
||||
const SystemTheme& PaletteImpl::theme() const
|
||||
{
|
||||
return *(const SystemTheme*)m_theme_buffer->data();
|
||||
}
|
||||
|
||||
Color PaletteImpl::color(ColorRole role) const
|
||||
{
|
||||
ASSERT((int)role < (int)ColorRole::__Count);
|
||||
return theme().color[(int)role];
|
||||
}
|
||||
|
||||
NonnullRefPtr<PaletteImpl> PaletteImpl::clone() const
|
||||
{
|
||||
auto new_theme_buffer = SharedBuffer::create_with_size(m_theme_buffer->size());
|
||||
memcpy(new_theme_buffer->data(), m_theme_buffer->data(), m_theme_buffer->size());
|
||||
return adopt(*new PaletteImpl(*new_theme_buffer));
|
||||
}
|
||||
|
||||
void Palette::set_color(ColorRole role, Color color)
|
||||
{
|
||||
if (m_impl->ref_count() != 1)
|
||||
m_impl = m_impl->clone();
|
||||
auto& theme = const_cast<SystemTheme&>(impl().theme());
|
||||
theme.color[(int)role] = color;
|
||||
}
|
||||
|
||||
}
|
115
Libraries/LibGfx/Palette.h
Normal file
115
Libraries/LibGfx/Palette.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <LibGfx/SystemTheme.h>
|
||||
|
||||
namespace GUI {
|
||||
class Application;
|
||||
}
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class PaletteImpl : public RefCounted<PaletteImpl> {
|
||||
AK_MAKE_NONCOPYABLE(PaletteImpl)
|
||||
AK_MAKE_NONMOVABLE(PaletteImpl)
|
||||
public:
|
||||
static NonnullRefPtr<PaletteImpl> create_with_shared_buffer(SharedBuffer&);
|
||||
NonnullRefPtr<PaletteImpl> clone() const;
|
||||
|
||||
Color color(ColorRole) const;
|
||||
const SystemTheme& theme() const;
|
||||
|
||||
void replace_internal_buffer(Badge<GUI::Application>, SharedBuffer& buffer) { m_theme_buffer = buffer; }
|
||||
|
||||
private:
|
||||
explicit PaletteImpl(SharedBuffer&);
|
||||
|
||||
RefPtr<SharedBuffer> m_theme_buffer;
|
||||
};
|
||||
|
||||
class Palette {
|
||||
|
||||
public:
|
||||
explicit Palette(const PaletteImpl&);
|
||||
~Palette();
|
||||
|
||||
Color window() const { return color(ColorRole::Window); }
|
||||
Color window_text() const { return color(ColorRole::WindowText); }
|
||||
Color selection() const { return color(ColorRole::Selection); }
|
||||
Color selection_text() const { return color(ColorRole::SelectionText); }
|
||||
Color desktop_background() const { return color(ColorRole::DesktopBackground); }
|
||||
Color active_window_border1() const { return color(ColorRole::ActiveWindowBorder1); }
|
||||
Color active_window_border2() const { return color(ColorRole::ActiveWindowBorder2); }
|
||||
Color active_window_title() const { return color(ColorRole::ActiveWindowTitle); }
|
||||
Color inactive_window_border1() const { return color(ColorRole::InactiveWindowBorder1); }
|
||||
Color inactive_window_border2() const { return color(ColorRole::InactiveWindowBorder2); }
|
||||
Color inactive_window_title() const { return color(ColorRole::InactiveWindowTitle); }
|
||||
Color moving_window_border1() const { return color(ColorRole::MovingWindowBorder1); }
|
||||
Color moving_window_border2() const { return color(ColorRole::MovingWindowBorder2); }
|
||||
Color moving_window_title() const { return color(ColorRole::MovingWindowTitle); }
|
||||
Color highlight_window_border1() const { return color(ColorRole::HighlightWindowBorder1); }
|
||||
Color highlight_window_border2() const { return color(ColorRole::HighlightWindowBorder2); }
|
||||
Color highlight_window_title() const { return color(ColorRole::HighlightWindowTitle); }
|
||||
Color menu_stripe() const { return color(ColorRole::MenuStripe); }
|
||||
Color menu_base() const { return color(ColorRole::MenuBase); }
|
||||
Color menu_base_text() const { return color(ColorRole::MenuBaseText); }
|
||||
Color menu_selection() const { return color(ColorRole::MenuSelection); }
|
||||
Color menu_selection_text() const { return color(ColorRole::MenuSelectionText); }
|
||||
Color base() const { return color(ColorRole::Base); }
|
||||
Color base_text() const { return color(ColorRole::BaseText); }
|
||||
Color button() const { return color(ColorRole::Button); }
|
||||
Color button_text() const { return color(ColorRole::ButtonText); }
|
||||
Color threed_highlight() const { return color(ColorRole::ThreedHighlight); }
|
||||
Color threed_shadow1() const { return color(ColorRole::ThreedShadow1); }
|
||||
Color threed_shadow2() const { return color(ColorRole::ThreedShadow2); }
|
||||
Color hover_highlight() const { return color(ColorRole::ThreedHighlight); }
|
||||
Color rubber_band_fill() const { return color(ColorRole::RubberBandFill); }
|
||||
Color rubber_band_border() const { return color(ColorRole::RubberBandBorder); }
|
||||
|
||||
Color link() const { return color(ColorRole::Link); }
|
||||
Color active_link() const { return color(ColorRole::ActiveLink); }
|
||||
Color visited_link() const { return color(ColorRole::VisitedLink); }
|
||||
|
||||
Color color(ColorRole role) const { return m_impl->color(role); }
|
||||
|
||||
void set_color(ColorRole, Color);
|
||||
|
||||
const SystemTheme& theme() const { return m_impl->theme(); }
|
||||
|
||||
PaletteImpl& impl() { return *m_impl; }
|
||||
const PaletteImpl& impl() const { return *m_impl; }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<PaletteImpl> m_impl;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using Gfx::Palette;
|
166
Libraries/LibGfx/Point.h
Normal file
166
Libraries/LibGfx/Point.h
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Rect;
|
||||
|
||||
class Point {
|
||||
public:
|
||||
Point() {}
|
||||
Point(int x, int y)
|
||||
: m_x(x)
|
||||
, m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
int x() const { return m_x; }
|
||||
int y() const { return m_y; }
|
||||
|
||||
void set_x(int x) { m_x = x; }
|
||||
void set_y(int y) { m_y = y; }
|
||||
|
||||
void move_by(int dx, int dy)
|
||||
{
|
||||
m_x += dx;
|
||||
m_y += dy;
|
||||
}
|
||||
|
||||
void move_by(const Point& delta)
|
||||
{
|
||||
move_by(delta.x(), delta.y());
|
||||
}
|
||||
|
||||
Point translated(const Point& delta) const
|
||||
{
|
||||
Point point = *this;
|
||||
point.move_by(delta);
|
||||
return point;
|
||||
}
|
||||
|
||||
Point translated(int dx, int dy) const
|
||||
{
|
||||
Point point = *this;
|
||||
point.move_by(dx, dy);
|
||||
return point;
|
||||
}
|
||||
|
||||
void constrain(const Rect&);
|
||||
|
||||
bool operator==(const Point& other) const
|
||||
{
|
||||
return m_x == other.m_x
|
||||
&& m_y == other.m_y;
|
||||
}
|
||||
|
||||
bool operator!=(const Point& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Point operator-() const { return { -m_x, -m_y }; }
|
||||
|
||||
Point operator-(const Point& other) const { return { m_x - other.m_x, m_y - other.m_y }; }
|
||||
Point& operator-=(const Point& other)
|
||||
{
|
||||
m_x -= other.m_x;
|
||||
m_y -= other.m_y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Point& operator+=(const Point& other)
|
||||
{
|
||||
m_x += other.m_x;
|
||||
m_y += other.m_y;
|
||||
return *this;
|
||||
}
|
||||
Point operator+(const Point& other) const { return { m_x + other.m_x, m_y + other.m_y }; }
|
||||
|
||||
String to_string() const { return String::format("[%d,%d]", x(), y()); }
|
||||
|
||||
bool is_null() const { return !m_x && !m_y; }
|
||||
|
||||
int primary_offset_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? y() : x();
|
||||
}
|
||||
|
||||
void set_primary_offset_for_orientation(Orientation orientation, int value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_y(value);
|
||||
else
|
||||
set_x(value);
|
||||
}
|
||||
|
||||
int secondary_offset_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? x() : y();
|
||||
}
|
||||
|
||||
void set_secondary_offset_for_orientation(Orientation orientation, int value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_x(value);
|
||||
else
|
||||
set_y(value);
|
||||
}
|
||||
|
||||
int dx_relative_to(const Point& other) const
|
||||
{
|
||||
return x() - other.x();
|
||||
}
|
||||
|
||||
int dy_relative_to(const Point& other) const
|
||||
{
|
||||
return y() - other.y();
|
||||
}
|
||||
|
||||
// Returns pixels moved from other in either direction
|
||||
int pixels_moved(const Point& other) const
|
||||
{
|
||||
return max(abs(dx_relative_to(other)), abs(dy_relative_to(other)));
|
||||
}
|
||||
|
||||
private:
|
||||
int m_x { 0 };
|
||||
int m_y { 0 };
|
||||
};
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const Point& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::Point;
|
128
Libraries/LibGfx/Rect.cpp
Normal file
128
Libraries/LibGfx/Rect.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Rect.h"
|
||||
#include <AK/StdLibExtras.h>
|
||||
|
||||
void Rect::intersect(const Rect& other)
|
||||
{
|
||||
int l = max(left(), other.left());
|
||||
int r = min(right(), other.right());
|
||||
int t = max(top(), other.top());
|
||||
int b = min(bottom(), other.bottom());
|
||||
|
||||
if (l > r || t > b) {
|
||||
m_location = {};
|
||||
m_size = {};
|
||||
return;
|
||||
}
|
||||
|
||||
m_location.set_x(l);
|
||||
m_location.set_y(t);
|
||||
m_size.set_width((r - l) + 1);
|
||||
m_size.set_height((b - t) + 1);
|
||||
}
|
||||
|
||||
Rect Rect::united(const Rect& other) const
|
||||
{
|
||||
if (is_null())
|
||||
return other;
|
||||
if (other.is_null())
|
||||
return *this;
|
||||
Rect rect;
|
||||
rect.set_left(min(left(), other.left()));
|
||||
rect.set_top(min(top(), other.top()));
|
||||
rect.set_right(max(right(), other.right()));
|
||||
rect.set_bottom(max(bottom(), other.bottom()));
|
||||
return rect;
|
||||
}
|
||||
|
||||
Vector<Rect, 4> Rect::shatter(const Rect& hammer) const
|
||||
{
|
||||
Vector<Rect, 4> pieces;
|
||||
if (!intersects(hammer)) {
|
||||
pieces.unchecked_append(*this);
|
||||
return pieces;
|
||||
}
|
||||
Rect top_shard {
|
||||
x(),
|
||||
y(),
|
||||
width(),
|
||||
hammer.y() - y()
|
||||
};
|
||||
Rect bottom_shard {
|
||||
x(),
|
||||
hammer.y() + hammer.height(),
|
||||
width(),
|
||||
(y() + height()) - (hammer.y() + hammer.height())
|
||||
};
|
||||
Rect left_shard {
|
||||
x(),
|
||||
max(hammer.y(), y()),
|
||||
hammer.x() - x(),
|
||||
min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
|
||||
};
|
||||
Rect right_shard {
|
||||
hammer.x() + hammer.width(),
|
||||
max(hammer.y(), y()),
|
||||
right() - hammer.right(),
|
||||
min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
|
||||
};
|
||||
if (intersects(top_shard))
|
||||
pieces.unchecked_append(top_shard);
|
||||
if (intersects(bottom_shard))
|
||||
pieces.unchecked_append(bottom_shard);
|
||||
if (intersects(left_shard))
|
||||
pieces.unchecked_append(left_shard);
|
||||
if (intersects(right_shard))
|
||||
pieces.unchecked_append(right_shard);
|
||||
|
||||
return pieces;
|
||||
}
|
||||
|
||||
void Rect::align_within(const Rect& other, TextAlignment alignment)
|
||||
{
|
||||
switch (alignment) {
|
||||
case TextAlignment::Center:
|
||||
center_within(other);
|
||||
return;
|
||||
case TextAlignment::TopLeft:
|
||||
set_location(other.location());
|
||||
return;
|
||||
case TextAlignment::TopRight:
|
||||
set_x(other.x() + other.width() - width());
|
||||
set_y(other.y());
|
||||
return;
|
||||
case TextAlignment::CenterLeft:
|
||||
set_x(other.x());
|
||||
center_vertically_within(other);
|
||||
return;
|
||||
case TextAlignment::CenterRight:
|
||||
set_x(other.x() + other.width() - width());
|
||||
center_vertically_within(other);
|
||||
return;
|
||||
}
|
||||
}
|
337
Libraries/LibGfx/Rect.h
Normal file
337
Libraries/LibGfx/Rect.h
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
#include <LibGfx/Point.h>
|
||||
#include <LibGfx/Size.h>
|
||||
#include <LibGfx/TextAlignment.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
Rect() {}
|
||||
Rect(int x, int y, int width, int height)
|
||||
: m_location(x, y)
|
||||
, m_size(width, height)
|
||||
{
|
||||
}
|
||||
Rect(const Point& location, const Size& size)
|
||||
: m_location(location)
|
||||
, m_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_null() const
|
||||
{
|
||||
return width() == 0 && height() == 0;
|
||||
}
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
return width() <= 0 || height() <= 0;
|
||||
}
|
||||
|
||||
void move_by(int dx, int dy)
|
||||
{
|
||||
m_location.move_by(dx, dy);
|
||||
}
|
||||
|
||||
void move_by(const Point& delta)
|
||||
{
|
||||
m_location.move_by(delta);
|
||||
}
|
||||
|
||||
Point center() const
|
||||
{
|
||||
return { x() + width() / 2, y() + height() / 2 };
|
||||
}
|
||||
|
||||
void set_location(const Point& location)
|
||||
{
|
||||
m_location = location;
|
||||
}
|
||||
|
||||
void set_size(const Size& size)
|
||||
{
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void set_size(int width, int height)
|
||||
{
|
||||
m_size.set_width(width);
|
||||
m_size.set_height(height);
|
||||
}
|
||||
|
||||
void inflate(int w, int h)
|
||||
{
|
||||
set_x(x() - w / 2);
|
||||
set_width(width() + w);
|
||||
set_y(y() - h / 2);
|
||||
set_height(height() + h);
|
||||
}
|
||||
|
||||
void shrink(int w, int h)
|
||||
{
|
||||
set_x(x() + w / 2);
|
||||
set_width(width() - w);
|
||||
set_y(y() + h / 2);
|
||||
set_height(height() - h);
|
||||
}
|
||||
|
||||
Rect shrunken(int w, int h) const
|
||||
{
|
||||
Rect rect = *this;
|
||||
rect.shrink(w, h);
|
||||
return rect;
|
||||
}
|
||||
|
||||
Rect inflated(int w, int h) const
|
||||
{
|
||||
Rect rect = *this;
|
||||
rect.inflate(w, h);
|
||||
return rect;
|
||||
}
|
||||
|
||||
Rect translated(int dx, int dy) const
|
||||
{
|
||||
Rect rect = *this;
|
||||
rect.move_by(dx, dy);
|
||||
return rect;
|
||||
}
|
||||
|
||||
Rect translated(const Point& delta) const
|
||||
{
|
||||
Rect rect = *this;
|
||||
rect.move_by(delta);
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool contains_vertically(int y) const
|
||||
{
|
||||
return y >= top() && y <= bottom();
|
||||
}
|
||||
|
||||
bool contains_horizontally(int x) const
|
||||
{
|
||||
return x >= left() && x <= right();
|
||||
}
|
||||
|
||||
bool contains(int x, int y) const
|
||||
{
|
||||
return x >= m_location.x() && x <= right() && y >= m_location.y() && y <= bottom();
|
||||
}
|
||||
|
||||
bool contains(const Point& point) const
|
||||
{
|
||||
return contains(point.x(), point.y());
|
||||
}
|
||||
|
||||
bool contains(const Rect& other) const
|
||||
{
|
||||
return left() <= other.left()
|
||||
&& right() >= other.right()
|
||||
&& top() <= other.top()
|
||||
&& bottom() >= other.bottom();
|
||||
}
|
||||
|
||||
int primary_offset_for_orientation(Orientation orientation) const { return m_location.primary_offset_for_orientation(orientation); }
|
||||
void set_primary_offset_for_orientation(Orientation orientation, int value) { m_location.set_primary_offset_for_orientation(orientation, value); }
|
||||
int secondary_offset_for_orientation(Orientation orientation) const { return m_location.secondary_offset_for_orientation(orientation); }
|
||||
void set_secondary_offset_for_orientation(Orientation orientation, int value) { m_location.set_secondary_offset_for_orientation(orientation, value); }
|
||||
|
||||
int primary_size_for_orientation(Orientation orientation) const { return m_size.primary_size_for_orientation(orientation); }
|
||||
int secondary_size_for_orientation(Orientation orientation) const { return m_size.secondary_size_for_orientation(orientation); }
|
||||
void set_primary_size_for_orientation(Orientation orientation, int value) { m_size.set_primary_size_for_orientation(orientation, value); }
|
||||
void set_secondary_size_for_orientation(Orientation orientation, int value) { m_size.set_secondary_size_for_orientation(orientation, value); }
|
||||
|
||||
int first_edge_for_orientation(Orientation orientation) const
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
return top();
|
||||
return left();
|
||||
}
|
||||
|
||||
int last_edge_for_orientation(Orientation orientation) const
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
return bottom();
|
||||
return right();
|
||||
}
|
||||
|
||||
int left() const { return x(); }
|
||||
int right() const { return x() + width() - 1; }
|
||||
int top() const { return y(); }
|
||||
int bottom() const { return y() + height() - 1; }
|
||||
|
||||
void set_left(int left)
|
||||
{
|
||||
set_x(left);
|
||||
}
|
||||
|
||||
void set_top(int top)
|
||||
{
|
||||
set_y(top);
|
||||
}
|
||||
|
||||
void set_right(int right)
|
||||
{
|
||||
set_width(right - x() + 1);
|
||||
}
|
||||
|
||||
void set_bottom(int bottom)
|
||||
{
|
||||
set_height(bottom - y() + 1);
|
||||
}
|
||||
|
||||
void set_right_without_resize(int new_right)
|
||||
{
|
||||
int delta = new_right - right();
|
||||
move_by(delta, 0);
|
||||
}
|
||||
|
||||
void set_bottom_without_resize(int new_bottom)
|
||||
{
|
||||
int delta = new_bottom - bottom();
|
||||
move_by(0, delta);
|
||||
}
|
||||
|
||||
bool intersects_vertically(const Rect& other) const
|
||||
{
|
||||
return top() <= other.bottom()
|
||||
&& other.top() <= bottom();
|
||||
}
|
||||
|
||||
bool intersects_horizontally(const Rect& other) const
|
||||
{
|
||||
return left() <= other.right()
|
||||
&& other.left() <= right();
|
||||
}
|
||||
|
||||
bool intersects(const Rect& other) const
|
||||
{
|
||||
return left() <= other.right()
|
||||
&& other.left() <= right()
|
||||
&& top() <= other.bottom()
|
||||
&& other.top() <= bottom();
|
||||
}
|
||||
|
||||
int x() const { return location().x(); }
|
||||
int y() const { return location().y(); }
|
||||
int width() const { return m_size.width(); }
|
||||
int height() const { return m_size.height(); }
|
||||
|
||||
void set_x(int x) { m_location.set_x(x); }
|
||||
void set_y(int y) { m_location.set_y(y); }
|
||||
void set_width(int width) { m_size.set_width(width); }
|
||||
void set_height(int height) { m_size.set_height(height); }
|
||||
|
||||
Point location() const { return m_location; }
|
||||
Size size() const { return m_size; }
|
||||
|
||||
Vector<Rect, 4> shatter(const Rect& hammer) const;
|
||||
|
||||
bool operator==(const Rect& other) const
|
||||
{
|
||||
return m_location == other.m_location
|
||||
&& m_size == other.m_size;
|
||||
}
|
||||
|
||||
void intersect(const Rect&);
|
||||
|
||||
static Rect from_two_points(const Point& a, const Point& b)
|
||||
{
|
||||
return { min(a.x(), b.x()), min(a.y(), b.y()), abs(a.x() - b.x()), abs(a.y() - b.y()) };
|
||||
}
|
||||
|
||||
static Rect intersection(const Rect& a, const Rect& b)
|
||||
{
|
||||
Rect r(a);
|
||||
r.intersect(b);
|
||||
return r;
|
||||
}
|
||||
|
||||
Rect intersected(const Rect& other) const
|
||||
{
|
||||
return intersection(*this, other);
|
||||
}
|
||||
|
||||
Rect united(const Rect&) const;
|
||||
|
||||
Point top_left() const { return { left(), top() }; }
|
||||
Point top_right() const { return { right(), top() }; }
|
||||
Point bottom_left() const { return { left(), bottom() }; }
|
||||
Point bottom_right() const { return { right(), bottom() }; }
|
||||
|
||||
void align_within(const Rect&, TextAlignment);
|
||||
|
||||
void center_within(const Rect& other)
|
||||
{
|
||||
center_horizontally_within(other);
|
||||
center_vertically_within(other);
|
||||
}
|
||||
|
||||
void center_horizontally_within(const Rect& other)
|
||||
{
|
||||
set_x(other.center().x() - width() / 2);
|
||||
}
|
||||
|
||||
void center_vertically_within(const Rect& other)
|
||||
{
|
||||
set_y(other.center().y() - height() / 2);
|
||||
}
|
||||
|
||||
String to_string() const { return String::format("[%d,%d %dx%d]", x(), y(), width(), height()); }
|
||||
|
||||
private:
|
||||
Point m_location;
|
||||
Size m_size;
|
||||
};
|
||||
|
||||
inline void Point::constrain(const Rect& rect)
|
||||
{
|
||||
if (x() < rect.left())
|
||||
set_x(rect.left());
|
||||
else if (x() > rect.right())
|
||||
set_x(rect.right());
|
||||
if (y() < rect.top())
|
||||
set_y(rect.top());
|
||||
else if (y() > rect.bottom())
|
||||
set_y(rect.bottom());
|
||||
}
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const Rect& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::Rect;
|
119
Libraries/LibGfx/Size.h
Normal file
119
Libraries/LibGfx/Size.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Size {
|
||||
public:
|
||||
Size() {}
|
||||
Size(int w, int h)
|
||||
: m_width(w)
|
||||
, m_height(h)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_null() const { return !m_width && !m_height; }
|
||||
bool is_empty() const { return m_width <= 0 || m_height <= 0; }
|
||||
|
||||
int width() const { return m_width; }
|
||||
int height() const { return m_height; }
|
||||
|
||||
int area() const { return width() * height(); }
|
||||
|
||||
void set_width(int w) { m_width = w; }
|
||||
void set_height(int h) { m_height = h; }
|
||||
|
||||
bool operator==(const Size& other) const
|
||||
{
|
||||
return m_width == other.m_width && m_height == other.m_height;
|
||||
}
|
||||
|
||||
bool operator!=(const Size& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Size& operator-=(const Size& other)
|
||||
{
|
||||
m_width -= other.m_width;
|
||||
m_height -= other.m_height;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Size& operator+=(const Size& other)
|
||||
{
|
||||
m_width += other.m_width;
|
||||
m_height += other.m_height;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int primary_size_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? height() : width();
|
||||
}
|
||||
|
||||
void set_primary_size_for_orientation(Orientation orientation, int value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_height(value);
|
||||
else
|
||||
set_width(value);
|
||||
}
|
||||
|
||||
int secondary_size_for_orientation(Orientation orientation) const
|
||||
{
|
||||
return orientation == Orientation::Vertical ? width() : height();
|
||||
}
|
||||
|
||||
void set_secondary_size_for_orientation(Orientation orientation, int value)
|
||||
{
|
||||
if (orientation == Orientation::Vertical)
|
||||
set_width(value);
|
||||
else
|
||||
set_height(value);
|
||||
}
|
||||
|
||||
String to_string() const { return String::format("[%dx%d]", m_width, m_height); }
|
||||
|
||||
private:
|
||||
int m_width { 0 };
|
||||
int m_height { 0 };
|
||||
};
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const Size& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Gfx::Size;
|
315
Libraries/LibGfx/StylePainter.cpp
Normal file
315
Libraries/LibGfx/StylePainter.cpp
Normal file
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/GraphicsBitmap.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
void StylePainter::paint_tab_button(Painter& painter, const Rect& rect, const Palette& palette, bool active, bool hovered, bool enabled)
|
||||
{
|
||||
Color base_color = palette.button();
|
||||
Color highlight_color2 = palette.threed_highlight();
|
||||
Color shadow_color1 = palette.threed_shadow1();
|
||||
Color shadow_color2 = palette.threed_shadow2();
|
||||
|
||||
if (hovered && enabled && !active)
|
||||
base_color = palette.hover_highlight();
|
||||
|
||||
PainterStateSaver saver(painter);
|
||||
painter.translate(rect.location());
|
||||
|
||||
// Base
|
||||
painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 1 }, base_color);
|
||||
|
||||
// Top line
|
||||
painter.draw_line({ 2, 0 }, { rect.width() - 3, 0 }, highlight_color2);
|
||||
|
||||
// Left side
|
||||
painter.draw_line({ 0, 2 }, { 0, rect.height() - 1 }, highlight_color2);
|
||||
painter.set_pixel({ 1, 1 }, highlight_color2);
|
||||
|
||||
// Right side
|
||||
painter.draw_line({
|
||||
rect.width() - 1,
|
||||
2,
|
||||
},
|
||||
{ rect.width() - 1, rect.height() - 1 }, shadow_color2);
|
||||
painter.draw_line({
|
||||
rect.width() - 2,
|
||||
2,
|
||||
},
|
||||
{ rect.width() - 2, rect.height() - 1 }, shadow_color1);
|
||||
painter.set_pixel({
|
||||
rect.width() - 2,
|
||||
1,
|
||||
},
|
||||
shadow_color2);
|
||||
}
|
||||
|
||||
static void paint_button_new(Painter& painter, const Rect& rect, const Palette& palette, bool pressed, bool checked, bool hovered, bool enabled)
|
||||
{
|
||||
Color button_color = palette.button();
|
||||
Color highlight_color2 = palette.threed_highlight();
|
||||
Color shadow_color1 = palette.threed_shadow1();
|
||||
Color shadow_color2 = palette.threed_shadow2();
|
||||
|
||||
if (checked && enabled) {
|
||||
if (hovered)
|
||||
button_color = palette.hover_highlight();
|
||||
else
|
||||
button_color = palette.button();
|
||||
} else if (hovered && enabled)
|
||||
button_color = palette.hover_highlight();
|
||||
|
||||
PainterStateSaver saver(painter);
|
||||
painter.translate(rect.location());
|
||||
|
||||
if (pressed || checked) {
|
||||
// Base
|
||||
painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
|
||||
|
||||
painter.draw_rect({ {}, rect.size() }, shadow_color2);
|
||||
|
||||
// Sunken shadow
|
||||
painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, shadow_color1);
|
||||
painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, shadow_color1);
|
||||
} else {
|
||||
// Base
|
||||
painter.fill_rect({ 1, 1, rect.width() - 3, rect.height() - 3 }, button_color);
|
||||
|
||||
// Outer highlight
|
||||
painter.draw_line({ 0, 0 }, { rect.width() - 2, 0 }, highlight_color2);
|
||||
painter.draw_line({ 0, 1 }, { 0, rect.height() - 2 }, highlight_color2);
|
||||
|
||||
// Outer shadow
|
||||
painter.draw_line({ 0, rect.height() - 1 }, { rect.width() - 1, rect.height() - 1 }, shadow_color2);
|
||||
painter.draw_line({ rect.width() - 1, 0 }, { rect.width() - 1, rect.height() - 2 }, shadow_color2);
|
||||
|
||||
// Inner shadow
|
||||
painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color1);
|
||||
painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color1);
|
||||
}
|
||||
}
|
||||
|
||||
void StylePainter::paint_button(Painter& painter, const Rect& rect, const Palette& palette, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled)
|
||||
{
|
||||
if (button_style == ButtonStyle::Normal)
|
||||
return paint_button_new(painter, rect, palette, pressed, checked, hovered, enabled);
|
||||
|
||||
Color button_color = palette.button();
|
||||
Color highlight_color = palette.threed_highlight();
|
||||
Color shadow_color = palette.threed_shadow1();
|
||||
|
||||
if (button_style == ButtonStyle::CoolBar && !enabled)
|
||||
return;
|
||||
|
||||
PainterStateSaver saver(painter);
|
||||
painter.translate(rect.location());
|
||||
|
||||
if (pressed || checked) {
|
||||
// Base
|
||||
painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
|
||||
|
||||
// Sunken shadow
|
||||
painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, shadow_color);
|
||||
painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, shadow_color);
|
||||
|
||||
// Bottom highlight
|
||||
painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, highlight_color);
|
||||
painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, highlight_color);
|
||||
} else if (button_style == ButtonStyle::CoolBar && hovered) {
|
||||
// Base
|
||||
painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
|
||||
|
||||
// White highlight
|
||||
painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, highlight_color);
|
||||
painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, highlight_color);
|
||||
|
||||
// Gray shadow
|
||||
painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color);
|
||||
painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color);
|
||||
}
|
||||
}
|
||||
|
||||
void StylePainter::paint_surface(Painter& painter, const Rect& rect, const Palette& palette, bool paint_vertical_lines, bool paint_top_line)
|
||||
{
|
||||
painter.fill_rect({ rect.x(), rect.y() + 1, rect.width(), rect.height() - 2 }, palette.button());
|
||||
painter.draw_line(rect.top_left(), rect.top_right(), paint_top_line ? palette.threed_highlight() : palette.button());
|
||||
painter.draw_line(rect.bottom_left(), rect.bottom_right(), palette.threed_shadow1());
|
||||
if (paint_vertical_lines) {
|
||||
painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), palette.threed_highlight());
|
||||
painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), palette.threed_shadow1());
|
||||
}
|
||||
}
|
||||
|
||||
void StylePainter::paint_frame(Painter& painter, const Rect& rect, const Palette& palette, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
|
||||
{
|
||||
Color top_left_color;
|
||||
Color bottom_right_color;
|
||||
Color dark_shade = palette.threed_shadow1();
|
||||
Color light_shade = palette.threed_highlight();
|
||||
|
||||
if (shape == FrameShape::Container && thickness >= 2) {
|
||||
if (shadow == FrameShadow::Raised) {
|
||||
dark_shade = palette.threed_shadow2();
|
||||
}
|
||||
}
|
||||
|
||||
if (shadow == FrameShadow::Raised) {
|
||||
top_left_color = light_shade;
|
||||
bottom_right_color = dark_shade;
|
||||
} else if (shadow == FrameShadow::Sunken) {
|
||||
top_left_color = dark_shade;
|
||||
bottom_right_color = light_shade;
|
||||
} else if (shadow == FrameShadow::Plain) {
|
||||
top_left_color = dark_shade;
|
||||
bottom_right_color = dark_shade;
|
||||
}
|
||||
|
||||
if (thickness >= 1) {
|
||||
painter.draw_line(rect.top_left(), rect.top_right(), top_left_color);
|
||||
painter.draw_line(rect.bottom_left(), rect.bottom_right(), bottom_right_color);
|
||||
|
||||
if (shape != FrameShape::Panel || !skip_vertical_lines) {
|
||||
painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), top_left_color);
|
||||
painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), bottom_right_color);
|
||||
}
|
||||
}
|
||||
|
||||
if (shape == FrameShape::Container && thickness >= 2) {
|
||||
Color top_left_color;
|
||||
Color bottom_right_color;
|
||||
Color dark_shade = palette.threed_shadow2();
|
||||
Color light_shade = palette.button();
|
||||
if (shadow == FrameShadow::Raised) {
|
||||
dark_shade = palette.threed_shadow1();
|
||||
top_left_color = light_shade;
|
||||
bottom_right_color = dark_shade;
|
||||
} else if (shadow == FrameShadow::Sunken) {
|
||||
top_left_color = dark_shade;
|
||||
bottom_right_color = light_shade;
|
||||
} else if (shadow == FrameShadow::Plain) {
|
||||
top_left_color = dark_shade;
|
||||
bottom_right_color = dark_shade;
|
||||
}
|
||||
Rect inner_container_frame_rect = rect.shrunken(2, 2);
|
||||
painter.draw_line(inner_container_frame_rect.top_left(), inner_container_frame_rect.top_right(), top_left_color);
|
||||
painter.draw_line(inner_container_frame_rect.bottom_left(), inner_container_frame_rect.bottom_right(), bottom_right_color);
|
||||
painter.draw_line(inner_container_frame_rect.top_left().translated(0, 1), inner_container_frame_rect.bottom_left().translated(0, -1), top_left_color);
|
||||
painter.draw_line(inner_container_frame_rect.top_right(), inner_container_frame_rect.bottom_right().translated(0, -1), bottom_right_color);
|
||||
}
|
||||
|
||||
if (shape == FrameShape::Box && thickness >= 2) {
|
||||
swap(top_left_color, bottom_right_color);
|
||||
Rect inner_rect = rect.shrunken(2, 2);
|
||||
painter.draw_line(inner_rect.top_left(), inner_rect.top_right(), top_left_color);
|
||||
painter.draw_line(inner_rect.bottom_left(), inner_rect.bottom_right(), bottom_right_color);
|
||||
painter.draw_line(inner_rect.top_left().translated(0, 1), inner_rect.bottom_left().translated(0, -1), top_left_color);
|
||||
painter.draw_line(inner_rect.top_right(), inner_rect.bottom_right().translated(0, -1), bottom_right_color);
|
||||
}
|
||||
}
|
||||
|
||||
void StylePainter::paint_window_frame(Painter& painter, const Rect& rect, const Palette& palette)
|
||||
{
|
||||
Color base_color = palette.button();
|
||||
Color dark_shade = palette.threed_shadow2();
|
||||
Color mid_shade = palette.threed_shadow1();
|
||||
Color light_shade = palette.threed_highlight();
|
||||
|
||||
painter.draw_line(rect.top_left(), rect.top_right(), base_color);
|
||||
painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left(), base_color);
|
||||
painter.draw_line(rect.top_left().translated(1, 1), rect.top_right().translated(-1, 1), light_shade);
|
||||
painter.draw_line(rect.top_left().translated(1, 1), rect.bottom_left().translated(1, -1), light_shade);
|
||||
painter.draw_line(rect.top_left().translated(2, 2), rect.top_right().translated(-2, 2), base_color);
|
||||
painter.draw_line(rect.top_left().translated(2, 2), rect.bottom_left().translated(2, -2), base_color);
|
||||
|
||||
painter.draw_line(rect.top_right(), rect.bottom_right(), dark_shade);
|
||||
painter.draw_line(rect.top_right().translated(-1, 1), rect.bottom_right().translated(-1, -1), mid_shade);
|
||||
painter.draw_line(rect.top_right().translated(-2, 2), rect.bottom_right().translated(-2, -2), base_color);
|
||||
painter.draw_line(rect.bottom_left(), rect.bottom_right(), dark_shade);
|
||||
painter.draw_line(rect.bottom_left().translated(1, -1), rect.bottom_right().translated(-1, -1), mid_shade);
|
||||
painter.draw_line(rect.bottom_left().translated(2, -2), rect.bottom_right().translated(-2, -2), base_color);
|
||||
}
|
||||
|
||||
void StylePainter::paint_progress_bar(Painter& painter, const Rect& rect, const Palette& palette, int min, int max, int value, const StringView& text)
|
||||
{
|
||||
// First we fill the entire widget with the gradient. This incurs a bit of
|
||||
// overdraw but ensures a consistent look throughout the progression.
|
||||
Color start_color = palette.active_window_border1();
|
||||
Color end_color = palette.active_window_border2();
|
||||
painter.fill_rect_with_gradient(rect, start_color, end_color);
|
||||
|
||||
if (!text.is_null()) {
|
||||
painter.draw_text(rect.translated(1, 1), text, TextAlignment::Center, palette.base_text());
|
||||
painter.draw_text(rect, text, TextAlignment::Center, palette.base_text().inverted());
|
||||
}
|
||||
|
||||
float range_size = max - min;
|
||||
float progress = (value - min) / range_size;
|
||||
|
||||
// Then we carve out a hole in the remaining part of the widget.
|
||||
// We draw the text a third time, clipped and inverse, for sharp contrast.
|
||||
float progress_width = progress * rect.width();
|
||||
Rect hole_rect { (int)progress_width, 0, (int)(rect.width() - progress_width), rect.height() };
|
||||
hole_rect.move_by(rect.location());
|
||||
hole_rect.set_right_without_resize(rect.right());
|
||||
PainterStateSaver saver(painter);
|
||||
painter.fill_rect(hole_rect, palette.base());
|
||||
|
||||
painter.add_clip_rect(hole_rect);
|
||||
if (!text.is_null())
|
||||
painter.draw_text(rect.translated(0, 0), text, TextAlignment::Center, palette.base_text());
|
||||
}
|
||||
|
||||
static RefPtr<Gfx::Bitmap> s_unfilled_circle_bitmap;
|
||||
static RefPtr<Gfx::Bitmap> s_filled_circle_bitmap;
|
||||
static RefPtr<Gfx::Bitmap> s_changing_filled_circle_bitmap;
|
||||
static RefPtr<Gfx::Bitmap> s_changing_unfilled_circle_bitmap;
|
||||
|
||||
static const Gfx::Bitmap& circle_bitmap(bool checked, bool changing)
|
||||
{
|
||||
if (changing)
|
||||
return checked ? *s_changing_filled_circle_bitmap : *s_changing_unfilled_circle_bitmap;
|
||||
return checked ? *s_filled_circle_bitmap : *s_unfilled_circle_bitmap;
|
||||
}
|
||||
|
||||
void StylePainter::paint_radio_button(Painter& painter, const Rect& rect, const Palette&, bool is_checked, bool is_being_pressed)
|
||||
{
|
||||
if (!s_unfilled_circle_bitmap) {
|
||||
s_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/unfilled-radio-circle.png");
|
||||
s_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/filled-radio-circle.png");
|
||||
s_changing_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/changing-filled-radio-circle.png");
|
||||
s_changing_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/changing-unfilled-radio-circle.png");
|
||||
}
|
||||
|
||||
auto& bitmap = circle_bitmap(is_checked, is_being_pressed);
|
||||
painter.blit(rect.location(), bitmap, bitmap.rect());
|
||||
}
|
||||
|
||||
}
|
66
Libraries/LibGfx/StylePainter.h
Normal file
66
Libraries/LibGfx/StylePainter.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/Color.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Painter;
|
||||
class Palette;
|
||||
class Rect;
|
||||
|
||||
enum class ButtonStyle {
|
||||
Normal,
|
||||
CoolBar
|
||||
};
|
||||
enum class FrameShadow {
|
||||
Plain,
|
||||
Raised,
|
||||
Sunken
|
||||
};
|
||||
enum class FrameShape {
|
||||
NoFrame,
|
||||
Box,
|
||||
Container,
|
||||
Panel,
|
||||
VerticalLine,
|
||||
HorizontalLine
|
||||
};
|
||||
|
||||
class StylePainter {
|
||||
public:
|
||||
static void paint_button(Painter&, const Rect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true);
|
||||
static void paint_tab_button(Painter&, const Rect&, const Palette&, bool active, bool hovered, bool enabled);
|
||||
static void paint_surface(Painter&, const Rect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true);
|
||||
static void paint_frame(Painter&, const Rect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false);
|
||||
static void paint_window_frame(Painter&, const Rect&, const Palette&);
|
||||
static void paint_progress_bar(Painter&, const Rect&, const Palette&, int min, int max, int value, const StringView& text = {});
|
||||
static void paint_radio_button(Painter&, const Rect&, const Palette&, bool is_checked, bool is_being_pressed);
|
||||
};
|
||||
|
||||
}
|
115
Libraries/LibGfx/SystemTheme.cpp
Normal file
115
Libraries/LibGfx/SystemTheme.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/CConfigFile.h>
|
||||
#include <LibGfx/SystemTheme.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
static SystemTheme dummy_theme;
|
||||
static const SystemTheme* theme_page = &dummy_theme;
|
||||
static RefPtr<SharedBuffer> theme_buffer;
|
||||
|
||||
const SystemTheme& current_system_theme()
|
||||
{
|
||||
ASSERT(theme_page);
|
||||
return *theme_page;
|
||||
}
|
||||
|
||||
int current_system_theme_buffer_id()
|
||||
{
|
||||
ASSERT(theme_buffer);
|
||||
return theme_buffer->shared_buffer_id();
|
||||
}
|
||||
|
||||
void set_system_theme(SharedBuffer& buffer)
|
||||
{
|
||||
theme_buffer = buffer;
|
||||
theme_page = (SystemTheme*)theme_buffer->data();
|
||||
}
|
||||
|
||||
RefPtr<SharedBuffer> load_system_theme(const String& path)
|
||||
{
|
||||
auto file = Core::ConfigFile::open(path);
|
||||
auto buffer = SharedBuffer::create_with_size(sizeof(SystemTheme));
|
||||
|
||||
auto* data = (SystemTheme*)buffer->data();
|
||||
|
||||
auto get_color = [&](auto& name) {
|
||||
auto color_string = file->read_entry("Colors", name);
|
||||
auto color = Color::from_string(color_string);
|
||||
if (!color.has_value())
|
||||
return Color(Color::Black);
|
||||
return color.value();
|
||||
};
|
||||
|
||||
#define DO_COLOR(x) \
|
||||
data->color[(int)ColorRole::x] = get_color(#x)
|
||||
|
||||
DO_COLOR(DesktopBackground);
|
||||
DO_COLOR(ThreedHighlight);
|
||||
DO_COLOR(ThreedShadow1);
|
||||
DO_COLOR(ThreedShadow2);
|
||||
DO_COLOR(HoverHighlight);
|
||||
DO_COLOR(Selection);
|
||||
DO_COLOR(SelectionText);
|
||||
DO_COLOR(Window);
|
||||
DO_COLOR(WindowText);
|
||||
DO_COLOR(Base);
|
||||
DO_COLOR(BaseText);
|
||||
DO_COLOR(Button);
|
||||
DO_COLOR(ButtonText);
|
||||
DO_COLOR(DesktopBackground);
|
||||
DO_COLOR(ActiveWindowBorder1);
|
||||
DO_COLOR(ActiveWindowBorder2);
|
||||
DO_COLOR(ActiveWindowTitle);
|
||||
DO_COLOR(InactiveWindowBorder1);
|
||||
DO_COLOR(InactiveWindowBorder2);
|
||||
DO_COLOR(InactiveWindowTitle);
|
||||
DO_COLOR(MovingWindowBorder1);
|
||||
DO_COLOR(MovingWindowBorder2);
|
||||
DO_COLOR(MovingWindowTitle);
|
||||
DO_COLOR(HighlightWindowBorder1);
|
||||
DO_COLOR(HighlightWindowBorder2);
|
||||
DO_COLOR(HighlightWindowTitle);
|
||||
DO_COLOR(MenuStripe);
|
||||
DO_COLOR(MenuBase);
|
||||
DO_COLOR(MenuBaseText);
|
||||
DO_COLOR(MenuSelection);
|
||||
DO_COLOR(MenuSelectionText);
|
||||
DO_COLOR(RubberBandFill);
|
||||
DO_COLOR(RubberBandBorder);
|
||||
DO_COLOR(Link);
|
||||
DO_COLOR(ActiveLink);
|
||||
DO_COLOR(VisitedLink);
|
||||
|
||||
buffer->seal();
|
||||
buffer->share_globally();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
90
Libraries/LibGfx/SystemTheme.h
Normal file
90
Libraries/LibGfx/SystemTheme.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibGfx/Color.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class ColorRole {
|
||||
NoRole,
|
||||
DesktopBackground,
|
||||
ActiveWindowBorder1,
|
||||
ActiveWindowBorder2,
|
||||
ActiveWindowTitle,
|
||||
InactiveWindowBorder1,
|
||||
InactiveWindowBorder2,
|
||||
InactiveWindowTitle,
|
||||
MovingWindowBorder1,
|
||||
MovingWindowBorder2,
|
||||
MovingWindowTitle,
|
||||
HighlightWindowBorder1,
|
||||
HighlightWindowBorder2,
|
||||
HighlightWindowTitle,
|
||||
MenuStripe,
|
||||
MenuBase,
|
||||
MenuBaseText,
|
||||
MenuSelection,
|
||||
MenuSelectionText,
|
||||
Window,
|
||||
WindowText,
|
||||
Button,
|
||||
ButtonText,
|
||||
Base,
|
||||
BaseText,
|
||||
ThreedHighlight,
|
||||
ThreedShadow1,
|
||||
ThreedShadow2,
|
||||
HoverHighlight,
|
||||
Selection,
|
||||
SelectionText,
|
||||
RubberBandFill,
|
||||
RubberBandBorder,
|
||||
Link,
|
||||
ActiveLink,
|
||||
VisitedLink,
|
||||
|
||||
__Count,
|
||||
|
||||
Background = Window,
|
||||
DisabledText = ThreedShadow1,
|
||||
};
|
||||
|
||||
struct SystemTheme {
|
||||
Color color[(int)ColorRole::__Count];
|
||||
};
|
||||
|
||||
const SystemTheme& current_system_theme();
|
||||
int current_system_theme_buffer_id();
|
||||
void set_system_theme(SharedBuffer&);
|
||||
RefPtr<SharedBuffer> load_system_theme(const String& path);
|
||||
|
||||
}
|
||||
|
||||
using Gfx::ColorRole;
|
50
Libraries/LibGfx/TextAlignment.h
Normal file
50
Libraries/LibGfx/TextAlignment.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class TextAlignment {
|
||||
TopLeft,
|
||||
CenterLeft,
|
||||
Center,
|
||||
CenterRight,
|
||||
TopRight,
|
||||
};
|
||||
|
||||
inline bool is_right_text_alignment(TextAlignment alignment)
|
||||
{
|
||||
switch (alignment) {
|
||||
case TextAlignment::CenterRight:
|
||||
case TextAlignment::TopRight:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
Libraries/LibGfx/TextElision.h
Normal file
36
Libraries/LibGfx/TextElision.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class TextElision {
|
||||
None,
|
||||
Right,
|
||||
};
|
||||
|
||||
}
|
81
Libraries/LibGfx/Triangle.h
Normal file
81
Libraries/LibGfx/Triangle.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class Triangle {
|
||||
public:
|
||||
Triangle(Point a, Point b, Point c)
|
||||
: m_a(a)
|
||||
, m_b(b)
|
||||
, m_c(c)
|
||||
{
|
||||
m_det = (m_b.x() - m_a.x()) * (m_c.y() - m_a.y()) - (m_b.y() - m_a.y()) * (m_c.x() - m_a.x());
|
||||
}
|
||||
|
||||
Point a() const { return m_a; }
|
||||
Point b() const { return m_b; }
|
||||
Point c() const { return m_c; }
|
||||
|
||||
bool contains(Point p) const
|
||||
{
|
||||
int x = p.x();
|
||||
int y = p.y();
|
||||
|
||||
int ax = m_a.x();
|
||||
int bx = m_b.x();
|
||||
int cx = m_c.x();
|
||||
|
||||
int ay = m_a.y();
|
||||
int by = m_b.y();
|
||||
int cy = m_c.y();
|
||||
|
||||
if (m_det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) <= 0)
|
||||
return false;
|
||||
if (m_det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) <= 0)
|
||||
return false;
|
||||
if (m_det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) <= 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
String to_string() const { return String::format("(%s,%s,%s)", m_a.to_string().characters(), m_b.to_string().characters(), m_c.to_string().characters()); }
|
||||
|
||||
private:
|
||||
int m_det;
|
||||
Point m_a;
|
||||
Point m_b;
|
||||
Point m_c;
|
||||
};
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const Triangle& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue