1
Fork 0
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:
Andreas Kling 2020-02-06 12:04:00 +01:00
parent 11580babbf
commit 9ac94d393e
215 changed files with 291 additions and 291 deletions

View 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));
}
}

View 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
View 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
View 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;

View 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);
}
}

View 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;
};
}

View 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
View 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);
};
}

View 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;

View 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;

View 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
View 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
View 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 };
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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();
}
}
}

View 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()
{
}
}

View 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
View 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

View 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;

View 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();
}
}

View 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;
};
}

View 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
View 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;
};
}

View 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
View 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
View 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
View 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
View 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
View 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;

View 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());
}
}

View 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);
};
}

View 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;
}
}

View 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;

View 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;
}
}
}

View 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,
};
}

View 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();
}
}