1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 05:17:34 +00:00

LibDraw: Introduce (formerly known as SharedGraphics.)

Instead of LibGUI and WindowServer building their own copies of the drawing
and graphics code, let's it in a separate LibDraw library.

This avoids building the code twice, and will encourage better separation
of concerns. :^)
This commit is contained in:
Andreas Kling 2019-07-18 10:15:00 +02:00
parent 2167f60235
commit 1c0669f010
120 changed files with 201 additions and 190 deletions

View file

@ -5,7 +5,7 @@
#include <AK/RefPtr.h>
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <SharedGraphics/Color.h>
#include <LibDraw/Color.h>
class CConfigFile : public RefCounted<CConfigFile> {
public:

View file

@ -0,0 +1,16 @@
#include "CharacterBitmap.h"
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,24 @@
#pragma once
#include "Size.h"
#include <AK/RefPtr.h>
#include <AK/RefCounted.h>
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;
};

View file

@ -0,0 +1,81 @@
#include "Color.h"
#include <AK/Assertions.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 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());
}

143
Libraries/LibDraw/Color.h Normal file
View file

@ -0,0 +1,143 @@
#pragma once
#include <AK/AKString.h>
#include <AK/Types.h>
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,
DarkGreen,
DarkBlue,
DarkRed,
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.5) const
{
return Color(red() * amount, green() * amount, blue() * amount, alpha());
}
Color lightened() const
{
return Color(min(255.0, red() * 1.2), min(255.0, green() * 1.2), min(255.0, blue() * 1.2), 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;
private:
explicit Color(RGBA32 rgba)
: m_value(rgba)
{
}
RGBA32 m_value { 0 };
};

View file

@ -0,0 +1,45 @@
#include <LibDraw/DisjointRectSet.h>
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,28 @@
#pragma once
#include <AK/Vector.h>
#include <LibDraw/Rect.h>
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;
};

175
Libraries/LibDraw/Font.cpp Normal file
View file

@ -0,0 +1,175 @@
#include "Font.h"
#include <AK/BufferStream.h>
#include <AK/MappedFile.h>
#include <AK/StdLibExtras.h>
#include <AK/kmalloc.h>
#include <LibC/errno.h>
#include <LibC/fcntl.h>
#include <LibC/mman.h>
#include <LibC/stdio.h>
#include <LibC/unistd.h>
struct [[gnu::packed]] FontFileHeader
{
char magic[4];
u8 glyph_width;
u8 glyph_height;
u8 type;
u8 is_variable_width;
u8 unused[6];
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_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));
}
Font::Font(const StringView& name, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height)
: 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_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));
}
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.pointer());
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;
memcpy(header.name, m_name.characters(), min(m_name.length(), 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.pointer(), buffer.size());
ASSERT(nwritten == (ssize_t)buffer.size());
int rc = close(fd);
ASSERT(rc == 0);
return true;
}
int Font::width(const StringView& string) const
{
if (!string.length())
return 0;
if (m_fixed_width)
return string.length() * m_glyph_width;
int width = 0;
for (int i = 0; i < string.length(); ++i)
width += glyph_width(string.characters_without_null_termination()[i]) + 1;
return width - 1;
}

95
Libraries/LibDraw/Font.h Normal file
View file

@ -0,0 +1,95 @@
#pragma once
#include <AK/AKString.h>
#include <AK/MappedFile.h>
#include <AK/RefPtr.h>
#include <AK/RefCounted.h>
#include <AK/Types.h>
#include <LibDraw/Rect.h>
// 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();
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]; }
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; }
u8 glyph_spacing() const { return m_fixed_width ? 0 : 1; }
int width(const StringView& string) 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; }
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);
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 };
bool m_fixed_width { false };
};

View file

@ -0,0 +1,102 @@
#include <AK/MappedFile.h>
#include <LibDraw/GraphicsBitmap.h>
#include <LibDraw/PNGLoader.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
NonnullRefPtr<GraphicsBitmap> GraphicsBitmap::create(Format format, const Size& size)
{
return adopt(*new GraphicsBitmap(format, size));
}
GraphicsBitmap::GraphicsBitmap(Format format, const Size& size)
: m_size(size)
, m_pitch(round_up_to_power_of_two(size.width() * sizeof(RGBA32), 16))
, m_format(format)
{
if (format == Format::Indexed8)
m_palette = new RGBA32[256];
m_data = (RGBA32*)mmap_with_name(nullptr, size_in_bytes(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, String::format("GraphicsBitmap [%dx%d]", width(), height()).characters());
ASSERT(m_data && m_data != (void*)-1);
m_needs_munmap = true;
}
NonnullRefPtr<GraphicsBitmap> GraphicsBitmap::create_wrapper(Format format, const Size& size, RGBA32* data)
{
return adopt(*new GraphicsBitmap(format, size, data));
}
RefPtr<GraphicsBitmap> GraphicsBitmap::load_from_file(const StringView& path)
{
return load_png(path);
}
RefPtr<GraphicsBitmap> GraphicsBitmap::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 GraphicsBitmap(format, size, move(mapped_file)));
}
GraphicsBitmap::GraphicsBitmap(Format format, const Size& size, RGBA32* data)
: m_size(size)
, m_data(data)
, m_pitch(round_up_to_power_of_two(size.width() * sizeof(RGBA32), 16))
, m_format(format)
{
ASSERT(format != Format::Indexed8);
}
GraphicsBitmap::GraphicsBitmap(Format format, const Size& size, MappedFile&& mapped_file)
: m_size(size)
, m_data((RGBA32*)mapped_file.pointer())
, 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<GraphicsBitmap> GraphicsBitmap::create_with_shared_buffer(Format format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const Size& size)
{
return adopt(*new GraphicsBitmap(format, move(shared_buffer), size));
}
GraphicsBitmap::GraphicsBitmap(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);
}
GraphicsBitmap::~GraphicsBitmap()
{
if (m_needs_munmap) {
int rc = munmap(m_data, size_in_bytes());
ASSERT(rc == 0);
}
m_data = nullptr;
delete[] m_palette;
}
void GraphicsBitmap::set_mmap_name(const StringView& name)
{
ASSERT(m_needs_munmap);
::set_mmap_name(m_data, size_in_bytes(), String(name).characters());
}
void GraphicsBitmap::fill(Color color)
{
ASSERT(m_format == GraphicsBitmap::Format::RGB32 || m_format == GraphicsBitmap::Format::RGBA32);
for (int y = 0; y < height(); ++y) {
auto* scanline = this->scanline(y);
fast_u32_fill(scanline, color.value(), width());
}
}

View file

@ -0,0 +1,189 @@
#pragma once
#include "Color.h"
#include "Rect.h"
#include "Size.h"
#include <AK/AKString.h>
#include <AK/MappedFile.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/StringView.h>
#include <SharedBuffer.h>
class GraphicsBitmap : public RefCounted<GraphicsBitmap> {
public:
enum class Format {
Invalid,
RGB32,
RGBA32,
Indexed8
};
static NonnullRefPtr<GraphicsBitmap> create(Format, const Size&);
static NonnullRefPtr<GraphicsBitmap> create_wrapper(Format, const Size&, RGBA32*);
static RefPtr<GraphicsBitmap> load_from_file(const StringView& path);
static RefPtr<GraphicsBitmap> load_from_file(Format, const StringView& path, const Size&);
static NonnullRefPtr<GraphicsBitmap> create_with_shared_buffer(Format, NonnullRefPtr<SharedBuffer>&&, const Size&);
~GraphicsBitmap();
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; }
unsigned bpp() const
{
switch (m_format) {
case Format::Indexed8:
return 8;
case Format::RGB32:
case Format::RGBA32:
return 32;
case Format::Invalid:
return 0;
default:
ASSERT_NOT_REACHED();
}
}
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
{
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)
{
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);
}
private:
GraphicsBitmap(Format, const Size&);
GraphicsBitmap(Format, const Size&, RGBA32*);
GraphicsBitmap(Format, const Size&, MappedFile&&);
GraphicsBitmap(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 };
MappedFile m_mapped_file;
RefPtr<SharedBuffer> m_shared_buffer;
};
inline RGBA32* GraphicsBitmap::scanline(int y)
{
return reinterpret_cast<RGBA32*>((((u8*)m_data) + (y * m_pitch)));
}
inline const RGBA32* GraphicsBitmap::scanline(int y) const
{
return reinterpret_cast<const RGBA32*>((((const u8*)m_data) + (y * m_pitch)));
}
inline const u8* GraphicsBitmap::bits(int y) const
{
return reinterpret_cast<const u8*>(scanline(y));
}
inline u8* GraphicsBitmap::bits(int y)
{
return reinterpret_cast<u8*>(scanline(y));
}
template<>
inline Color GraphicsBitmap::get_pixel<GraphicsBitmap::Format::RGB32>(int x, int y) const
{
return Color::from_rgb(scanline(y)[x]);
}
template<>
inline Color GraphicsBitmap::get_pixel<GraphicsBitmap::Format::RGBA32>(int x, int y) const
{
return Color::from_rgba(scanline(y)[x]);
}
template<>
inline Color GraphicsBitmap::get_pixel<GraphicsBitmap::Format::Indexed8>(int x, int y) const
{
return Color::from_rgba(m_palette[bits(y)[x]]);
}
inline Color GraphicsBitmap::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();
}
}
template<>
inline void GraphicsBitmap::set_pixel<GraphicsBitmap::Format::RGB32>(int x, int y, Color color)
{
scanline(y)[x] = color.value();
}
template<>
inline void GraphicsBitmap::set_pixel<GraphicsBitmap::Format::RGBA32>(int x, int y, Color color)
{
scanline(y)[x] = color.value();
}
inline void GraphicsBitmap::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,35 @@
include ../../Makefile.common
OBJS = \
CharacterBitmap.o \
Color.o \
DisjointRectSet.o \
Font.o \
GraphicsBitmap.o \
Painter.o \
PNGLoader.o \
Rect.o \
StylePainter.o
LIBRARY = libdraw.a
DEFINES += -DUSERLAND
all: $(LIBRARY)
$(LIBRARY): $(OBJS)
@echo "LIB $@"; $(AR) rcs $@ $(OBJS) $(LIBS)
.cpp.o:
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
-include $(OBJS:%.o=%.d)
clean:
@echo "CLEAN"; rm -f $(LIBRARY) $(OBJS) *.d
install: $(LIBRARY)
mkdir -p ../../Root/usr/include/LibGfx
# Copy headers
rsync -r -a --include '*/' --include '*.h' --exclude '*' . ../../Root/usr/include/LibGfx
# Install the library
cp $(LIBRARY) ../../Root/usr/lib

View file

@ -0,0 +1,457 @@
#include <AK/FileSystemPath.h>
#include <AK/MappedFile.h>
#include <AK/NetworkOrdered.h>
#include <LibDraw/PNGLoader.h>
#include <LibDraw/puff.c>
#include <fcntl.h>
#include <serenity.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
//#define PNG_STOPWATCH_DEBUG
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 PNGLoadingContext {
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; }
Vector<Scanline> scanlines;
RefPtr<GraphicsBitmap> bitmap;
u8* decompression_buffer { nullptr };
int decompression_buffer_size { 0 };
Vector<u8> compressed_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<GraphicsBitmap> load_png_impl(const u8*, int);
static bool process_chunk(Streamer&, PNGLoadingContext& context);
RefPtr<GraphicsBitmap> 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.pointer(), 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;
}
[[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(GraphicsBitmap& 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)
{
{
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: unfilter: unpack");
#endif
// First unpack the scanlines to RGBA:
switch (context.color_type) {
case 2:
for (int y = 0; y < context.height; ++y) {
struct [[gnu::packed]] Triplet
{
u8 r;
u8 g;
u8 b;
};
auto* triplets = (Triplet*)context.scanlines[y].data.pointer();
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;
}
}
break;
case 6:
for (int y = 0; y < context.height; ++y) {
memcpy(context.bitmap->scanline(y), context.scanlines[y].data.pointer(), context.scanlines[y].data.size());
}
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
auto dummy_scanline = ByteBuffer::create_zeroed(context.width * sizeof(RGBA32));
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: unfilter: process");
#endif
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.pointer());
else
unfilter_impl<false, 0>(*context.bitmap, y, dummy_scanline.pointer());
continue;
}
if (filter == 1) {
if (context.has_alpha())
unfilter_impl<true, 1>(*context.bitmap, y, dummy_scanline.pointer());
else
unfilter_impl<false, 1>(*context.bitmap, y, dummy_scanline.pointer());
continue;
}
if (filter == 2) {
if (context.has_alpha())
unfilter_impl<true, 2>(*context.bitmap, y, dummy_scanline.pointer());
else
unfilter_impl<false, 2>(*context.bitmap, y, dummy_scanline.pointer());
continue;
}
if (filter == 3) {
if (context.has_alpha())
unfilter_impl<true, 3>(*context.bitmap, y, dummy_scanline.pointer());
else
unfilter_impl<false, 3>(*context.bitmap, y, dummy_scanline.pointer());
continue;
}
if (filter == 4) {
if (context.has_alpha())
unfilter_impl<true, 4>(*context.bitmap, y, dummy_scanline.pointer());
else
unfilter_impl<false, 4>(*context.bitmap, y, dummy_scanline.pointer());
continue;
}
}
}
static RefPtr<GraphicsBitmap> load_png_impl(const u8* data, int data_size)
{
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: total");
#endif
const u8* data_ptr = data;
int data_remaining = data_size;
const u8 png_header[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
if (memcmp(data, png_header, sizeof(png_header))) {
dbgprintf("Invalid PNG header\n");
return nullptr;
}
PNGLoadingContext context;
context.compressed_data.ensure_capacity(data_size);
data_ptr += sizeof(png_header);
data_remaining -= sizeof(png_header);
{
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: read chunks");
#endif
Streamer streamer(data_ptr, data_remaining);
while (!streamer.at_end()) {
if (!process_chunk(streamer, context)) {
return nullptr;
}
}
}
{
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: uncompress");
#endif
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)
return nullptr;
context.compressed_data.clear();
}
{
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: extract scanlines");
#endif
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))
return nullptr;
context.scanlines.append({ filter });
auto& scanline_buffer = context.scanlines.last().data;
if (!streamer.wrap_bytes(scanline_buffer, context.width * context.bytes_per_pixel))
return nullptr;
}
}
{
#ifdef PNG_STOPWATCH_DEBUG
Stopwatch sw("load_png_impl: create bitmap");
#endif
context.bitmap = GraphicsBitmap::create(context.has_alpha() ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::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;
return context.bitmap;
}
static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context)
{
if (data.size() < (int)sizeof(PNG_IHDR))
return false;
auto& ihdr = *(const PNG_IHDR*)data.pointer();
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;
switch (context.color_type) {
case 2:
context.bytes_per_pixel = 3;
break;
case 6:
context.bytes_per_pixel = 4;
break;
default:
ASSERT_NOT_REACHED();
}
#ifdef PNG_DEBUG
printf("PNG: %dx%d (%d bpp)\n", context.width, context.height, context.bit_depth);
printf(" Color type: %b\n", context.color_type);
printf(" Interlace type: %b\n", context.interlace_method);
#endif
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.pointer(), data.size());
return true;
}
static bool process_chunk(Streamer& streamer, PNGLoadingContext& context)
{
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);
if (!strcmp((const char*)chunk_type, "IDAT"))
return process_IDAT(chunk_data, context);
return true;
}

View file

@ -0,0 +1,5 @@
#pragma once
#include <LibDraw/GraphicsBitmap.h>
RefPtr<GraphicsBitmap> load_png(const StringView& path);

View file

@ -0,0 +1,808 @@
#include "Painter.h"
#include "Font.h"
#include "GraphicsBitmap.h"
#include <AK/Assertions.h>
#include <AK/StdLibExtras.h>
#include <AK/StringBuilder.h>
#include <LibDraw/CharacterBitmap.h>
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#pragma GCC optimize("O3")
template<GraphicsBitmap::Format format = GraphicsBitmap::Format::Invalid>
static ALWAYS_INLINE Color get_pixel(const GraphicsBitmap& bitmap, int x, int y)
{
if constexpr (format == GraphicsBitmap::Format::Indexed8)
return bitmap.palette_color(bitmap.bits(y)[x]);
if constexpr (format == GraphicsBitmap::Format::RGB32)
return Color::from_rgb(bitmap.scanline(y)[x]);
if constexpr (format == GraphicsBitmap::Format::RGBA32)
return Color::from_rgba(bitmap.scanline(y)[x]);
return bitmap.get_pixel(x, y);
}
Painter::Painter(GraphicsBitmap& 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::fill_rect(const Rect& a_rect, Color color)
{
if (draw_op() != DrawOp::Copy) {
fill_rect_with_draw_op(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) {
fast_u32_fill(dst, color.value(), rect.width());
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_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 GraphicsBitmap& 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 GraphicsBitmap& 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 GraphicsBitmap& 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::blit_tiled(const Point& position, const GraphicsBitmap& source, const Rect& src_rect)
{
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() == GraphicsBitmap::Format::RGB32 || source.format() == GraphicsBitmap::Format::RGBA32) {
int x_start = first_column + src_rect.left();
for (int row = first_row; row <= last_row; ++row) {
const RGBA32* sl = source.scanline((row + src_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 GraphicsBitmap& 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() == GraphicsBitmap::Format::RGB32 || source.format() == GraphicsBitmap::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 GraphicsBitmap& 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 GraphicsBitmap& 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() == GraphicsBitmap::Format::RGB32 || source.format() == GraphicsBitmap::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() == GraphicsBitmap::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(GraphicsBitmap& target, const Rect& dst_rect, const GraphicsBitmap& 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(GraphicsBitmap& target, const Rect& dst_rect, const Rect& clipped_rect, const GraphicsBitmap& 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 GraphicsBitmap& 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 GraphicsBitmap::Format::RGB32:
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::RGB32>);
break;
case GraphicsBitmap::Format::RGBA32:
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::RGB32>);
break;
case GraphicsBitmap::Format::Indexed8:
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::Indexed8>);
break;
default:
do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::Invalid>);
break;
}
} else {
switch (source.format()) {
case GraphicsBitmap::Format::RGB32:
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::RGB32>);
break;
case GraphicsBitmap::Format::RGBA32:
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::RGB32>);
break;
case GraphicsBitmap::Format::Indexed8:
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::Format::Indexed8>);
break;
default:
do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<GraphicsBitmap::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_text_line(const Rect& a_rect, const StringView& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
{
auto rect = a_rect;
StringView 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 new_length = 0;
int new_width = font.width("...");
if (new_width < text_width) {
for (int i = 0; i < final_text.length(); ++i) {
int glyph_width = font.glyph_width(final_text.characters_without_null_termination()[i]);
// 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;
++new_length;
new_width += glyph_width + glyph_spacing;
}
StringBuilder builder;
builder.append(StringView(final_text.characters_without_null_termination(), new_length));
builder.append("...");
elided_text = builder.to_string();
final_text = elided_text;
}
}
}
if (alignment == TextAlignment::TopLeft) {
// No-op.
} else if (alignment == TextAlignment::CenterLeft) {
// No-op.
} else if (alignment == TextAlignment::CenterRight) {
rect.set_x(rect.right() - font.width(final_text));
} else if (alignment == TextAlignment::Center) {
auto shrunken_rect = rect;
shrunken_rect.set_width(font.width(final_text));
shrunken_rect.center_within(rect);
rect = shrunken_rect;
} else {
ASSERT_NOT_REACHED();
}
auto point = rect.location();
int space_width = font.glyph_width(' ') + font.glyph_spacing();
for (ssize_t i = 0; i < final_text.length(); ++i) {
char ch = final_text.characters_without_null_termination()[i];
if (ch == ' ') {
point.move_by(space_width, 0);
continue;
}
draw_glyph(point, ch, font, color);
point.move_by(font.glyph_width(ch) + 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& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
{
Vector<StringView, 32> lines;
int start_of_current_line = 0;
for (int i = 0; i < text.length(); ++i) {
auto ch = text[i];
if (ch == '\n') {
lines.append(text.substring_view(start_of_current_line, i - start_of_current_line));
start_of_current_line = i + 1;
continue;
}
}
if (start_of_current_line != text.length()) {
lines.append(text.substring_view(start_of_current_line, text.length() - start_of_current_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);
}
if (alignment == TextAlignment::TopLeft) {
bounding_rect.set_location(rect.location());
} else if (alignment == TextAlignment::CenterLeft) {
bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
} else if (alignment == TextAlignment::CenterRight) {
bounding_rect.set_location({ rect.right() - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
} else if (alignment == TextAlignment::Center) {
bounding_rect.center_within(rect);
} else {
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, color);
}
void Painter::draw_line(const Point& p1, const Point& p2, Color color, int thickness)
{
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());
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());
for (int x = min_x; x <= max_x; ++x)
draw_pixel({ x, y }, color, thickness);
return;
}
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();
}

View file

@ -0,0 +1,97 @@
#pragma once
#include "Color.h"
#include "Point.h"
#include "Rect.h"
#include "Size.h"
#include <AK/AKString.h>
#include <LibDraw/TextAlignment.h>
#include <LibDraw/TextElision.h>
class CharacterBitmap;
class GlyphBitmap;
class GraphicsBitmap;
class Font;
class Painter {
public:
explicit Painter(GraphicsBitmap&);
~Painter();
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 set_pixel(const Point&, Color);
void draw_line(const Point&, const Point&, Color, int thickness = 1);
void draw_scaled_bitmap(const Rect& dst_rect, const GraphicsBitmap&, const Rect& src_rect);
void blit(const Point&, const GraphicsBitmap&, const Rect& src_rect, float opacity = 1.0f);
void blit_dimmed(const Point&, const GraphicsBitmap&, const Rect& src_rect);
void blit_tiled(const Point&, const GraphicsBitmap&, const Rect& src_rect);
void blit_offset(const Point&, const GraphicsBitmap&, const Rect& src_rect, const Point&);
void blit_scaled(const Rect&, const GraphicsBitmap&, 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);
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; }
GraphicsBitmap* 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 GraphicsBitmap&, const Rect& src_rect);
void blit_with_opacity(const Point&, const GraphicsBitmap&, const Rect& src_rect, float opacity);
void draw_pixel(const Point&, Color, int thickness = 1);
void draw_text_line(const Rect&, const StringView&, 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<GraphicsBitmap> m_target;
Vector<State, 4> m_state_stack;
};
class PainterStateSaver {
public:
explicit PainterStateSaver(Painter&);
~PainterStateSaver();
private:
Painter& m_painter;
};

79
Libraries/LibDraw/Point.h Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include <AK/AKString.h>
#include <AK/LogStream.h>
class Rect;
struct WSAPI_Point;
class Point {
public:
Point() {}
Point(int x, int y)
: m_x(x)
, m_y(y)
{
}
Point(const WSAPI_Point&);
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 }; }
operator WSAPI_Point() const;
String to_string() const { return String::format("[%d,%d]", x(), y()); }
bool is_null() const { return !m_x && !m_y; }
private:
int m_x { 0 };
int m_y { 0 };
};
inline const LogStream& operator<<(const LogStream& stream, const Point& value)
{
return stream << value.to_string();
}

View file

@ -0,0 +1,98 @@
#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::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;
}
}

252
Libraries/LibDraw/Rect.h Normal file
View file

@ -0,0 +1,252 @@
#pragma once
#include <AK/AKString.h>
#include <AK/LogStream.h>
#include <LibDraw/Point.h>
#include <LibDraw/Size.h>
#include <LibDraw/TextAlignment.h>
struct WSAPI_Rect;
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)
{
}
Rect(const Rect& other)
: m_location(other.m_location)
, m_size(other.m_size)
{
}
Rect(const WSAPI_Rect&);
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(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 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);
}
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;
operator WSAPI_Rect() const;
bool operator==(const Rect& other) const
{
return m_location == other.m_location
&& m_size == other.m_size;
}
void intersect(const Rect&);
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();
}

65
Libraries/LibDraw/Size.h Normal file
View file

@ -0,0 +1,65 @@
#pragma once
#include <AK/AKString.h>
#include <AK/LogStream.h>
struct WSAPI_Size;
class Size {
public:
Size() {}
Size(int w, int h)
: m_width(w)
, m_height(h)
{
}
Size(const WSAPI_Size&);
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;
}
operator WSAPI_Size() const;
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();
}

View file

@ -0,0 +1,228 @@
#include <LibDraw/Painter.h>
#include <LibDraw/StylePainter.h>
void StylePainter::paint_tab_button(Painter& painter, const Rect& rect, bool active, bool hovered, bool enabled)
{
Color base_color = Color::WarmGray;
Color highlight_color2 = Color::from_rgb(0xdfdfdf);
Color shadow_color1 = Color::from_rgb(0x808080);
Color shadow_color2 = Color::from_rgb(0x404040);
if (hovered && enabled && !active)
base_color = Color::from_rgb(0xd4d4d4);
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, bool pressed, bool checked, bool hovered, bool enabled)
{
Color button_color = Color::WarmGray;
Color highlight_color2 = Color::from_rgb(0xdfdfdf);
Color shadow_color1 = Color::from_rgb(0x808080);
Color shadow_color2 = Color::from_rgb(0x404040);
if (checked && enabled) {
if (hovered)
button_color = Color::from_rgb(0xe3dfdb);
else
button_color = Color::from_rgb(0xd6d2ce);
} else if (hovered && enabled)
button_color = Color::from_rgb(0xd4d4d4);
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, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled)
{
if (button_style == ButtonStyle::Normal)
return paint_button_new(painter, rect, pressed, checked, hovered, enabled);
Color button_color = checked ? Color::from_rgb(0xd6d2ce) : Color::WarmGray;
Color highlight_color = Color::White;
Color shadow_color = Color(96, 96, 96);
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, bool paint_vertical_lines, bool paint_top_line)
{
painter.fill_rect({ rect.x(), rect.y() + 1, rect.width(), rect.height() - 2 }, Color::WarmGray);
painter.draw_line(rect.top_left(), rect.top_right(), paint_top_line ? Color::White : Color::WarmGray);
painter.draw_line(rect.bottom_left(), rect.bottom_right(), Color::MidGray);
if (paint_vertical_lines) {
painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), Color::White);
painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), Color::MidGray);
}
}
void StylePainter::paint_frame(Painter& painter, const Rect& rect, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
{
Color top_left_color;
Color bottom_right_color;
Color dark_shade = Color::from_rgb(0x808080);
Color light_shade = Color::from_rgb(0xffffff);
if (shape == FrameShape::Container && thickness >= 2) {
if (shadow == FrameShadow::Raised) {
dark_shade = Color::from_rgb(0x404040);
}
}
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 = Color::from_rgb(0x404040);
Color light_shade = Color::WarmGray;
if (shadow == FrameShadow::Raised) {
dark_shade = Color::from_rgb(0x808080);
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)
{
Color base_color = Color::WarmGray;
Color dark_shade = Color::from_rgb(0x404040);
Color mid_shade = Color::from_rgb(0x808080);
Color light_shade = Color::from_rgb(0xffffff);
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);
}

View file

@ -0,0 +1,31 @@
#pragma once
class Painter;
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&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true);
static void paint_tab_button(Painter&, const Rect&, bool active, bool hovered, bool enabled);
static void paint_surface(Painter&, const Rect&, bool paint_vertical_lines = true, bool paint_top_line = true);
static void paint_frame(Painter&, const Rect&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false);
static void paint_window_frame(Painter&, const Rect&);
};

View file

@ -0,0 +1,18 @@
#pragma once
enum class TextAlignment {
TopLeft,
CenterLeft,
Center,
CenterRight
};
inline bool is_right_text_alignment(TextAlignment alignment)
{
switch (alignment) {
case TextAlignment::CenterRight:
return true;
default:
return false;
}
}

View file

@ -0,0 +1,6 @@
#pragma once
enum class TextElision {
None,
Right,
};

4
Libraries/LibDraw/install.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
mkdir -p ../Root/usr/include/LibGfx/
cp *.h ../Root/usr/include/LibGfx/

840
Libraries/LibDraw/puff.c Normal file
View file

@ -0,0 +1,840 @@
/*
* puff.c
* Copyright (C) 2002-2013 Mark Adler
* For conditions of distribution and use, see copyright notice in puff.h
* version 2.3, 21 Jan 2013
*
* puff.c is a simple inflate written to be an unambiguous way to specify the
* deflate format. It is not written for speed but rather simplicity. As a
* side benefit, this code might actually be useful when small code is more
* important than speed, such as bootstrap applications. For typical deflate
* data, zlib's inflate() is about four times as fast as puff(). zlib's
* inflate compiles to around 20K on my machine, whereas puff.c compiles to
* around 4K on my machine (a PowerPC using GNU cc). If the faster decode()
* function here is used, then puff() is only twice as slow as zlib's
* inflate().
*
* All dynamically allocated memory comes from the stack. The stack required
* is less than 2K bytes. This code is compatible with 16-bit int's and
* assumes that long's are at least 32 bits. puff.c uses the short data type,
* assumed to be 16 bits, for arrays in order to conserve memory. The code
* works whether integers are stored big endian or little endian.
*
* In the comments below are "Format notes" that describe the inflate process
* and document some of the less obvious aspects of the format. This source
* code is meant to supplement RFC 1951, which formally describes the deflate
* format:
*
* http://www.zlib.org/rfc-deflate.html
*/
/*
* Change history:
*
* 1.0 10 Feb 2002 - First version
* 1.1 17 Feb 2002 - Clarifications of some comments and notes
* - Update puff() dest and source pointers on negative
* errors to facilitate debugging deflators
* - Remove longest from struct huffman -- not needed
* - Simplify offs[] index in construct()
* - Add input size and checking, using longjmp() to
* maintain easy readability
* - Use short data type for large arrays
* - Use pointers instead of long to specify source and
* destination sizes to avoid arbitrary 4 GB limits
* 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!),
* but leave simple version for readabilty
* - Make sure invalid distances detected if pointers
* are 16 bits
* - Fix fixed codes table error
* - Provide a scanning mode for determining size of
* uncompressed data
* 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly]
* - Add a puff.h file for the interface
* - Add braces in puff() for else do [Gailly]
* - Use indexes instead of pointers for readability
* 1.4 31 Mar 2002 - Simplify construct() code set check
* - Fix some comments
* - Add FIXLCODES #define
* 1.5 6 Apr 2002 - Minor comment fixes
* 1.6 7 Aug 2002 - Minor format changes
* 1.7 3 Mar 2003 - Added test code for distribution
* - Added zlib-like license
* 1.8 9 Jan 2004 - Added some comments on no distance codes case
* 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland]
* - Catch missing end-of-block symbol error
* 2.0 25 Jul 2008 - Add #define to permit distance too far back
* - Add option in TEST code for puff to write the data
* - Add option in TEST code to skip input bytes
* - Allow TEST code to read from piped stdin
* 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers
* - Avoid unsigned comparisons for even happier compilers
* 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer]
* - Add const where appropriate [Oberhumer]
* - Split if's and ?'s for coverage testing
* - Break out test code to separate file
* - Move NIL to puff.h
* - Allow incomplete code only if single code length is 1
* - Add full code coverage test to Makefile
* 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks
*/
#include <setjmp.h> /* for setjmp(), longjmp(), and jmp_buf */
#include "puff.h" /* prototype for puff() */
#define local static /* for local function definitions */
/*
* Maximums for allocations and loops. It is not useful to change these --
* they are fixed by the deflate format.
*/
#define MAXBITS 15 /* maximum bits in a code */
#define MAXLCODES 286 /* maximum number of literal/length codes */
#define MAXDCODES 30 /* maximum number of distance codes */
#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */
#define FIXLCODES 288 /* number of fixed literal/length codes */
/* input and output state */
struct state {
/* output state */
unsigned char *out; /* output buffer */
unsigned long outlen; /* available space at out */
unsigned long outcnt; /* bytes written to out so far */
/* input state */
const unsigned char *in; /* input buffer */
unsigned long inlen; /* available input at in */
unsigned long incnt; /* bytes read so far */
int bitbuf; /* bit buffer */
int bitcnt; /* number of bits in bit buffer */
/* input limit error return state for bits() and decode() */
jmp_buf env;
};
/*
* Return need bits from the input stream. This always leaves less than
* eight bits in the buffer. bits() works properly for need == 0.
*
* Format notes:
*
* - Bits are stored in bytes from the least significant bit to the most
* significant bit. Therefore bits are dropped from the bottom of the bit
* buffer, using shift right, and new bytes are appended to the top of the
* bit buffer, using shift left.
*/
local int bits(struct state *s, int need)
{
long val; /* bit accumulator (can use up to 20 bits) */
/* load at least need bits into val */
val = s->bitbuf;
while (s->bitcnt < need) {
if (s->incnt == s->inlen)
longjmp(s->env, 1); /* out of input */
val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */
s->bitcnt += 8;
}
/* drop need bits and update buffer, always zero to seven bits left */
s->bitbuf = (int)(val >> need);
s->bitcnt -= need;
/* return need bits, zeroing the bits above that */
return (int)(val & ((1L << need) - 1));
}
/*
* Process a stored block.
*
* Format notes:
*
* - After the two-bit stored block type (00), the stored block length and
* stored bytes are byte-aligned for fast copying. Therefore any leftover
* bits in the byte that has the last bit of the type, as many as seven, are
* discarded. The value of the discarded bits are not defined and should not
* be checked against any expectation.
*
* - The second inverted copy of the stored block length does not have to be
* checked, but it's probably a good idea to do so anyway.
*
* - A stored block can have zero length. This is sometimes used to byte-align
* subsets of the compressed data for random access or partial recovery.
*/
local int stored(struct state *s)
{
unsigned len; /* length of stored block */
/* discard leftover bits from current byte (assumes s->bitcnt < 8) */
s->bitbuf = 0;
s->bitcnt = 0;
/* get length and check against its one's complement */
if (s->incnt + 4 > s->inlen)
return 2; /* not enough input */
len = s->in[s->incnt++];
len |= s->in[s->incnt++] << 8;
if (s->in[s->incnt++] != (~len & 0xff) ||
s->in[s->incnt++] != ((~len >> 8) & 0xff))
return -2; /* didn't match complement! */
/* copy len bytes from in to out */
if (s->incnt + len > s->inlen)
return 2; /* not enough input */
if (s->out != NIL) {
if (s->outcnt + len > s->outlen)
return 1; /* not enough output space */
while (len--)
s->out[s->outcnt++] = s->in[s->incnt++];
}
else { /* just scanning */
s->outcnt += len;
s->incnt += len;
}
/* done with a valid stored block */
return 0;
}
/*
* Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
* each length, which for a canonical code are stepped through in order.
* symbol[] are the symbol values in canonical order, where the number of
* entries is the sum of the counts in count[]. The decoding process can be
* seen in the function decode() below.
*/
struct huffman {
short *count; /* number of symbols of each length */
short *symbol; /* canonically ordered symbols */
};
/*
* Decode a code from the stream s using huffman table h. Return the symbol or
* a negative value if there is an error. If all of the lengths are zero, i.e.
* an empty code, or if the code is incomplete and an invalid code is received,
* then -10 is returned after reading MAXBITS bits.
*
* Format notes:
*
* - The codes as stored in the compressed data are bit-reversed relative to
* a simple integer ordering of codes of the same lengths. Hence below the
* bits are pulled from the compressed data one at a time and used to
* build the code value reversed from what is in the stream in order to
* permit simple integer comparisons for decoding. A table-based decoding
* scheme (as used in zlib) does not need to do this reversal.
*
* - The first code for the shortest length is all zeros. Subsequent codes of
* the same length are simply integer increments of the previous code. When
* moving up a length, a zero bit is appended to the code. For a complete
* code, the last code of the longest length will be all ones.
*
* - Incomplete codes are handled by this decoder, since they are permitted
* in the deflate format. See the format notes for fixed() and dynamic().
*/
#ifdef SLOW
local int decode(struct state *s, const struct huffman *h)
{
int len; /* current number of bits in code */
int code; /* len bits being decoded */
int first; /* first code of length len */
int count; /* number of codes of length len */
int index; /* index of first code of length len in symbol table */
code = first = index = 0;
for (len = 1; len <= MAXBITS; len++) {
code |= bits(s, 1); /* get next bit */
count = h->count[len];
if (code - count < first) /* if length len, return symbol */
return h->symbol[index + (code - first)];
index += count; /* else update for next length */
first += count;
first <<= 1;
code <<= 1;
}
return -10; /* ran out of codes */
}
/*
* A faster version of decode() for real applications of this code. It's not
* as readable, but it makes puff() twice as fast. And it only makes the code
* a few percent larger.
*/
#else /* !SLOW */
local int decode(struct state *s, const struct huffman *h)
{
int len; /* current number of bits in code */
int code; /* len bits being decoded */
int first; /* first code of length len */
int count; /* number of codes of length len */
int index; /* index of first code of length len in symbol table */
int bitbuf; /* bits from stream */
int left; /* bits left in next or left to process */
short *next; /* next number of codes */
bitbuf = s->bitbuf;
left = s->bitcnt;
code = first = index = 0;
len = 1;
next = h->count + 1;
while (1) {
while (left--) {
code |= bitbuf & 1;
bitbuf >>= 1;
count = *next++;
if (code - count < first) { /* if length len, return symbol */
s->bitbuf = bitbuf;
s->bitcnt = (s->bitcnt - len) & 7;
return h->symbol[index + (code - first)];
}
index += count; /* else update for next length */
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS+1) - len;
if (left == 0)
break;
if (s->incnt == s->inlen)
longjmp(s->env, 1); /* out of input */
bitbuf = s->in[s->incnt++];
if (left > 8)
left = 8;
}
return -10; /* ran out of codes */
}
#endif /* SLOW */
/*
* Given the list of code lengths length[0..n-1] representing a canonical
* Huffman code for n symbols, construct the tables required to decode those
* codes. Those tables are the number of codes of each length, and the symbols
* sorted by length, retaining their original order within each length. The
* return value is zero for a complete code set, negative for an over-
* subscribed code set, and positive for an incomplete code set. The tables
* can be used if the return value is zero or positive, but they cannot be used
* if the return value is negative. If the return value is zero, it is not
* possible for decode() using that table to return an error--any stream of
* enough bits will resolve to a symbol. If the return value is positive, then
* it is possible for decode() using that table to return an error for received
* codes past the end of the incomplete lengths.
*
* Not used by decode(), but used for error checking, h->count[0] is the number
* of the n symbols not in the code. So n - h->count[0] is the number of
* codes. This is useful for checking for incomplete codes that have more than
* one symbol, which is an error in a dynamic block.
*
* Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS
* This is assured by the construction of the length arrays in dynamic() and
* fixed() and is not verified by construct().
*
* Format notes:
*
* - Permitted and expected examples of incomplete codes are one of the fixed
* codes and any code with a single symbol which in deflate is coded as one
* bit instead of zero bits. See the format notes for fixed() and dynamic().
*
* - Within a given code length, the symbols are kept in ascending order for
* the code bits definition.
*/
local int construct(struct huffman *h, const short *length, int n)
{
int symbol; /* current symbol when stepping through length[] */
int len; /* current length when stepping through h->count[] */
int left; /* number of possible codes left of current length */
short offs[MAXBITS+1]; /* offsets in symbol table for each length */
/* count number of codes of each length */
for (len = 0; len <= MAXBITS; len++)
h->count[len] = 0;
for (symbol = 0; symbol < n; symbol++)
(h->count[length[symbol]])++; /* assumes lengths are within bounds */
if (h->count[0] == n) /* no codes! */
return 0; /* complete, but decode() will fail */
/* check for an over-subscribed or incomplete set of lengths */
left = 1; /* one possible code of zero length */
for (len = 1; len <= MAXBITS; len++) {
left <<= 1; /* one more bit, double codes left */
left -= h->count[len]; /* deduct count from possible codes */
if (left < 0)
return left; /* over-subscribed--return negative */
} /* left > 0 means incomplete */
/* generate offsets into symbol table for each length for sorting */
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
offs[len + 1] = offs[len] + h->count[len];
/*
* put symbols in table sorted by length, by symbol order within each
* length
*/
for (symbol = 0; symbol < n; symbol++)
if (length[symbol] != 0)
h->symbol[offs[length[symbol]]++] = symbol;
/* return zero for complete set, positive for incomplete set */
return left;
}
/*
* Decode literal/length and distance codes until an end-of-block code.
*
* Format notes:
*
* - Compressed data that is after the block type if fixed or after the code
* description if dynamic is a combination of literals and length/distance
* pairs terminated by and end-of-block code. Literals are simply Huffman
* coded bytes. A length/distance pair is a coded length followed by a
* coded distance to represent a string that occurs earlier in the
* uncompressed data that occurs again at the current location.
*
* - Literals, lengths, and the end-of-block code are combined into a single
* code of up to 286 symbols. They are 256 literals (0..255), 29 length
* symbols (257..285), and the end-of-block symbol (256).
*
* - There are 256 possible lengths (3..258), and so 29 symbols are not enough
* to represent all of those. Lengths 3..10 and 258 are in fact represented
* by just a length symbol. Lengths 11..257 are represented as a symbol and
* some number of extra bits that are added as an integer to the base length
* of the length symbol. The number of extra bits is determined by the base
* length symbol. These are in the static arrays below, lens[] for the base
* lengths and lext[] for the corresponding number of extra bits.
*
* - The reason that 258 gets its own symbol is that the longest length is used
* often in highly redundant files. Note that 258 can also be coded as the
* base value 227 plus the maximum extra value of 31. While a good deflate
* should never do this, it is not an error, and should be decoded properly.
*
* - If a length is decoded, including its extra bits if any, then it is
* followed a distance code. There are up to 30 distance symbols. Again
* there are many more possible distances (1..32768), so extra bits are added
* to a base value represented by the symbol. The distances 1..4 get their
* own symbol, but the rest require extra bits. The base distances and
* corresponding number of extra bits are below in the static arrays dist[]
* and dext[].
*
* - Literal bytes are simply written to the output. A length/distance pair is
* an instruction to copy previously uncompressed bytes to the output. The
* copy is from distance bytes back in the output stream, copying for length
* bytes.
*
* - Distances pointing before the beginning of the output data are not
* permitted.
*
* - Overlapped copies, where the length is greater than the distance, are
* allowed and common. For example, a distance of one and a length of 258
* simply copies the last byte 258 times. A distance of four and a length of
* twelve copies the last four bytes three times. A simple forward copy
* ignoring whether the length is greater than the distance or not implements
* this correctly. You should not use memcpy() since its behavior is not
* defined for overlapped arrays. You should not use memmove() or bcopy()
* since though their behavior -is- defined for overlapping arrays, it is
* defined to do the wrong thing in this case.
*/
local int codes(struct state *s,
const struct huffman *lencode,
const struct huffman *distcode)
{
int symbol; /* decoded symbol */
int len; /* length for copy */
unsigned dist; /* distance for copy */
static const short lens[29] = { /* Size base for length codes 257..285 */
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
static const short lext[29] = { /* Extra bits for length codes 257..285 */
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const short dists[30] = { /* Offset base for distance codes 0..29 */
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577};
static const short dext[30] = { /* Extra bits for distance codes 0..29 */
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13};
/* decode literals and length/distance pairs */
do {
symbol = decode(s, lencode);
if (symbol < 0)
return symbol; /* invalid symbol */
if (symbol < 256) { /* literal: symbol is the byte */
/* write out the literal */
if (s->out != NIL) {
if (s->outcnt == s->outlen)
return 1;
s->out[s->outcnt] = symbol;
}
s->outcnt++;
}
else if (symbol > 256) { /* length */
/* get and compute length */
symbol -= 257;
if (symbol >= 29)
return -10; /* invalid fixed code */
len = lens[symbol] + bits(s, lext[symbol]);
/* get and check distance */
symbol = decode(s, distcode);
if (symbol < 0)
return symbol; /* invalid symbol */
dist = dists[symbol] + bits(s, dext[symbol]);
#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
if (dist > s->outcnt)
return -11; /* distance too far back */
#endif
/* copy length bytes from distance bytes back */
if (s->out != NIL) {
if (s->outcnt + len > s->outlen)
return 1;
while (len--) {
s->out[s->outcnt] =
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
dist > s->outcnt ?
0 :
#endif
s->out[s->outcnt - dist];
s->outcnt++;
}
}
else
s->outcnt += len;
}
} while (symbol != 256); /* end of block symbol */
/* done with a valid fixed or dynamic block */
return 0;
}
/*
* Process a fixed codes block.
*
* Format notes:
*
* - This block type can be useful for compressing small amounts of data for
* which the size of the code descriptions in a dynamic block exceeds the
* benefit of custom codes for that block. For fixed codes, no bits are
* spent on code descriptions. Instead the code lengths for literal/length
* codes and distance codes are fixed. The specific lengths for each symbol
* can be seen in the "for" loops below.
*
* - The literal/length code is complete, but has two symbols that are invalid
* and should result in an error if received. This cannot be implemented
* simply as an incomplete code since those two symbols are in the "middle"
* of the code. They are eight bits long and the longest literal/length\
* code is nine bits. Therefore the code must be constructed with those
* symbols, and the invalid symbols must be detected after decoding.
*
* - The fixed distance codes also have two invalid symbols that should result
* in an error if received. Since all of the distance codes are the same
* length, this can be implemented as an incomplete code. Then the invalid
* codes are detected while decoding.
*/
local int fixed(struct state *s)
{
static int virgin = 1;
static short lencnt[MAXBITS+1], lensym[FIXLCODES];
static short distcnt[MAXBITS+1], distsym[MAXDCODES];
static struct huffman lencode, distcode;
/* build fixed huffman tables if first call (may not be thread safe) */
if (virgin) {
int symbol;
short lengths[FIXLCODES];
/* construct lencode and distcode */
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
/* literal/length table */
for (symbol = 0; symbol < 144; symbol++)
lengths[symbol] = 8;
for (; symbol < 256; symbol++)
lengths[symbol] = 9;
for (; symbol < 280; symbol++)
lengths[symbol] = 7;
for (; symbol < FIXLCODES; symbol++)
lengths[symbol] = 8;
construct(&lencode, lengths, FIXLCODES);
/* distance table */
for (symbol = 0; symbol < MAXDCODES; symbol++)
lengths[symbol] = 5;
construct(&distcode, lengths, MAXDCODES);
/* do this just once */
virgin = 0;
}
/* decode data until end-of-block code */
return codes(s, &lencode, &distcode);
}
/*
* Process a dynamic codes block.
*
* Format notes:
*
* - A dynamic block starts with a description of the literal/length and
* distance codes for that block. New dynamic blocks allow the compressor to
* rapidly adapt to changing data with new codes optimized for that data.
*
* - The codes used by the deflate format are "canonical", which means that
* the actual bits of the codes are generated in an unambiguous way simply
* from the number of bits in each code. Therefore the code descriptions
* are simply a list of code lengths for each symbol.
*
* - The code lengths are stored in order for the symbols, so lengths are
* provided for each of the literal/length symbols, and for each of the
* distance symbols.
*
* - If a symbol is not used in the block, this is represented by a zero as
* as the code length. This does not mean a zero-length code, but rather
* that no code should be created for this symbol. There is no way in the
* deflate format to represent a zero-length code.
*
* - The maximum number of bits in a code is 15, so the possible lengths for
* any code are 1..15.
*
* - The fact that a length of zero is not permitted for a code has an
* interesting consequence. Normally if only one symbol is used for a given
* code, then in fact that code could be represented with zero bits. However
* in deflate, that code has to be at least one bit. So for example, if
* only a single distance base symbol appears in a block, then it will be
* represented by a single code of length one, in particular one 0 bit. This
* is an incomplete code, since if a 1 bit is received, it has no meaning,
* and should result in an error. So incomplete distance codes of one symbol
* should be permitted, and the receipt of invalid codes should be handled.
*
* - It is also possible to have a single literal/length code, but that code
* must be the end-of-block code, since every dynamic block has one. This
* is not the most efficient way to create an empty block (an empty fixed
* block is fewer bits), but it is allowed by the format. So incomplete
* literal/length codes of one symbol should also be permitted.
*
* - If there are only literal codes and no lengths, then there are no distance
* codes. This is represented by one distance code with zero bits.
*
* - The list of up to 286 length/literal lengths and up to 30 distance lengths
* are themselves compressed using Huffman codes and run-length encoding. In
* the list of code lengths, a 0 symbol means no code, a 1..15 symbol means
* that length, and the symbols 16, 17, and 18 are run-length instructions.
* Each of 16, 17, and 18 are follwed by extra bits to define the length of
* the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10
* zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols
* are common, hence the special coding for zero lengths.
*
* - The symbols for 0..18 are Huffman coded, and so that code must be
* described first. This is simply a sequence of up to 19 three-bit values
* representing no code (0) or the code length for that symbol (1..7).
*
* - A dynamic block starts with three fixed-size counts from which is computed
* the number of literal/length code lengths, the number of distance code
* lengths, and the number of code length code lengths (ok, you come up with
* a better name!) in the code descriptions. For the literal/length and
* distance codes, lengths after those provided are considered zero, i.e. no
* code. The code length code lengths are received in a permuted order (see
* the order[] array below) to make a short code length code length list more
* likely. As it turns out, very short and very long codes are less likely
* to be seen in a dynamic code description, hence what may appear initially
* to be a peculiar ordering.
*
* - Given the number of literal/length code lengths (nlen) and distance code
* lengths (ndist), then they are treated as one long list of nlen + ndist
* code lengths. Therefore run-length coding can and often does cross the
* boundary between the two sets of lengths.
*
* - So to summarize, the code description at the start of a dynamic block is
* three counts for the number of code lengths for the literal/length codes,
* the distance codes, and the code length codes. This is followed by the
* code length code lengths, three bits each. This is used to construct the
* code length code which is used to read the remainder of the lengths. Then
* the literal/length code lengths and distance lengths are read as a single
* set of lengths using the code length codes. Codes are constructed from
* the resulting two sets of lengths, and then finally you can start
* decoding actual compressed data in the block.
*
* - For reference, a "typical" size for the code description in a dynamic
* block is around 80 bytes.
*/
local int dynamic(struct state *s)
{
int nlen, ndist, ncode; /* number of lengths in descriptor */
int index; /* index of lengths[] */
int err; /* construct() return value */
short lengths[MAXCODES]; /* descriptor code lengths */
short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */
short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */
struct huffman lencode, distcode; /* length and distance codes */
static const short order[19] = /* permutation of code length codes */
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
/* construct lencode and distcode */
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
/* get number of lengths in each table, check lengths */
nlen = bits(s, 5) + 257;
ndist = bits(s, 5) + 1;
ncode = bits(s, 4) + 4;
if (nlen > MAXLCODES || ndist > MAXDCODES)
return -3; /* bad counts */
/* read code length code lengths (really), missing lengths are zero */
for (index = 0; index < ncode; index++)
lengths[order[index]] = bits(s, 3);
for (; index < 19; index++)
lengths[order[index]] = 0;
/* build huffman table for code lengths codes (use lencode temporarily) */
err = construct(&lencode, lengths, 19);
if (err != 0) /* require complete code set here */
return -4;
/* read length/literal and distance code length tables */
index = 0;
while (index < nlen + ndist) {
int symbol; /* decoded value */
int len; /* last length to repeat */
symbol = decode(s, &lencode);
if (symbol < 0)
return symbol; /* invalid symbol */
if (symbol < 16) /* length in 0..15 */
lengths[index++] = symbol;
else { /* repeat instruction */
len = 0; /* assume repeating zeros */
if (symbol == 16) { /* repeat last length 3..6 times */
if (index == 0)
return -5; /* no last length! */
len = lengths[index - 1]; /* last length */
symbol = 3 + bits(s, 2);
}
else if (symbol == 17) /* repeat zero 3..10 times */
symbol = 3 + bits(s, 3);
else /* == 18, repeat zero 11..138 times */
symbol = 11 + bits(s, 7);
if (index + symbol > nlen + ndist)
return -6; /* too many lengths! */
while (symbol--) /* repeat last or zero symbol times */
lengths[index++] = len;
}
}
/* check for end-of-block code -- there better be one! */
if (lengths[256] == 0)
return -9;
/* build huffman table for literal/length codes */
err = construct(&lencode, lengths, nlen);
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
return -7; /* incomplete code ok only for single length 1 code */
/* build huffman table for distance codes */
err = construct(&distcode, lengths + nlen, ndist);
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
return -8; /* incomplete code ok only for single length 1 code */
/* decode data until end-of-block code */
return codes(s, &lencode, &distcode);
}
/*
* Inflate source to dest. On return, destlen and sourcelen are updated to the
* size of the uncompressed data and the size of the deflate data respectively.
* On success, the return value of puff() is zero. If there is an error in the
* source data, i.e. it is not in the deflate format, then a negative value is
* returned. If there is not enough input available or there is not enough
* output space, then a positive error is returned. In that case, destlen and
* sourcelen are not updated to facilitate retrying from the beginning with the
* provision of more input data or more output space. In the case of invalid
* inflate data (a negative error), the dest and source pointers are updated to
* facilitate the debugging of deflators.
*
* puff() also has a mode to determine the size of the uncompressed output with
* no output written. For this dest must be (unsigned char *)0. In this case,
* the input value of *destlen is ignored, and on return *destlen is set to the
* size of the uncompressed output.
*
* The return codes are:
*
* 2: available inflate data did not terminate
* 1: output space exhausted before completing inflate
* 0: successful inflate
* -1: invalid block type (type == 3)
* -2: stored block length did not match one's complement
* -3: dynamic block code description: too many length or distance codes
* -4: dynamic block code description: code lengths codes incomplete
* -5: dynamic block code description: repeat lengths with no first length
* -6: dynamic block code description: repeat more than specified lengths
* -7: dynamic block code description: invalid literal/length code lengths
* -8: dynamic block code description: invalid distance code lengths
* -9: dynamic block code description: missing end-of-block code
* -10: invalid literal/length or distance code in fixed or dynamic block
* -11: distance is too far back in fixed or dynamic block
*
* Format notes:
*
* - Three bits are read for each block to determine the kind of block and
* whether or not it is the last block. Then the block is decoded and the
* process repeated if it was not the last block.
*
* - The leftover bits in the last byte of the deflate data after the last
* block (if it was a fixed or dynamic block) are undefined and have no
* expected values to check.
*/
int puff(unsigned char *dest, /* pointer to destination pointer */
unsigned long *destlen, /* amount of output space */
const unsigned char *source, /* pointer to source data pointer */
unsigned long *sourcelen) /* amount of input available */
{
struct state s; /* input/output state */
int last, type; /* block information */
int err; /* return value */
/* initialize output state */
s.out = dest;
s.outlen = *destlen; /* ignored if dest is NIL */
s.outcnt = 0;
/* initialize input state */
s.in = source;
s.inlen = *sourcelen;
s.incnt = 0;
s.bitbuf = 0;
s.bitcnt = 0;
/* return if bits() or decode() tries to read past available input */
if (setjmp(s.env) != 0) /* if came back here via longjmp() */
err = 2; /* then skip do-loop, return error */
else {
/* process blocks until last block or error */
do {
last = bits(&s, 1); /* one if last block */
type = bits(&s, 2); /* block type 0..3 */
err = type == 0 ?
stored(&s) :
(type == 1 ?
fixed(&s) :
(type == 2 ?
dynamic(&s) :
-1)); /* type == 3, invalid */
if (err != 0)
break; /* return with error */
} while (!last);
}
/* update the lengths and return */
if (err <= 0) {
*destlen = s.outcnt;
*sourcelen = s.incnt;
}
return err;
}

34
Libraries/LibDraw/puff.h Normal file
View file

@ -0,0 +1,34 @@
/* puff.h
Copyright (C) 2002-2013 Mark Adler, all rights reserved
version 2.3, 21 Jan 2013
This software is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Mark Adler madler@alumni.caltech.edu
*/
/*
* See puff.c for purpose and usage.
*/
#ifndef NIL
# define NIL ((unsigned char*)0) /* for no output option */
#endif
int puff(unsigned char* dest, /* pointer to destination pointer */
unsigned long* destlen, /* amount of output space */
const unsigned char* source, /* pointer to source data pointer */
unsigned long* sourcelen); /* amount of input available */

View file

@ -2,7 +2,7 @@
#include <LibCore/CTimer.h>
#include <LibGUI/GWidget.h>
#include <SharedGraphics/TextAlignment.h>
#include <LibDraw/TextAlignment.h>
class GPainter;

View file

@ -9,7 +9,7 @@
#include <AK/WeakPtr.h>
#include <AK/Weakable.h>
#include <LibGUI/GShortcut.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
class GActionGroup;
class GButton;

View file

@ -4,7 +4,7 @@
#include <LibGUI/GActionGroup.h>
#include <LibGUI/GButton.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
GButton::GButton(GWidget* parent)
: GAbstractButton(parent)

View file

@ -3,9 +3,9 @@
#include <AK/AKString.h>
#include <AK/Function.h>
#include <LibGUI/GAbstractButton.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/StylePainter.h>
#include <SharedGraphics/TextAlignment.h>
#include <LibDraw/GraphicsBitmap.h>
#include <LibDraw/StylePainter.h>
#include <LibDraw/TextAlignment.h>
class GAction;

View file

@ -1,8 +1,8 @@
#include <Kernel/KeyCode.h>
#include <LibGUI/GCheckBox.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/CharacterBitmap.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/CharacterBitmap.h>
#include <LibDraw/StylePainter.h>
static const char* s_checked_bitmap_data = {
" "

View file

@ -3,7 +3,7 @@
#include <AK/AKString.h>
#include <AK/Badge.h>
#include <AK/Function.h>
#include <SharedGraphics/Rect.h>
#include <LibDraw/Rect.h>
class GWindowServerConnection;

View file

@ -4,7 +4,7 @@
#include <LibCore/CDirIterator.h>
#include <LibCore/CLock.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
#include <dirent.h>
#include <grp.h>
#include <pwd.h>

View file

@ -3,8 +3,8 @@
#include <Kernel/KeyCode.h>
#include <LibCore/CEvent.h>
#include <LibGUI/GWindowType.h>
#include <SharedGraphics/Point.h>
#include <SharedGraphics/Rect.h>
#include <LibDraw/Point.h>
#include <LibDraw/Rect.h>
class CObject;

View file

@ -11,7 +11,7 @@
#include <LibGUI/GSortingProxyModel.h>
#include <LibGUI/GTextBox.h>
#include <LibGUI/GToolBar.h>
#include <SharedGraphics/PNGLoader.h>
#include <LibDraw/PNGLoader.h>
Optional<String> GFilePicker::get_open_filepath()
{

View file

@ -1,6 +1,6 @@
#include <LibCore/CDirIterator.h>
#include <LibGUI/GFontDatabase.h>
#include <SharedGraphics/Font.h>
#include <LibDraw/Font.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

View file

@ -1,6 +1,6 @@
#include <LibGUI/GFrame.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
GFrame::GFrame(GWidget* parent)
: GWidget(parent)

View file

@ -1,7 +1,7 @@
#pragma once
#include <LibGUI/GWidget.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
class GFrame : public GWidget {
public:

View file

@ -1,6 +1,6 @@
#include <LibGUI/GGroupBox.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
GGroupBox::GGroupBox(GWidget* parent)
: GGroupBox({}, parent)

View file

@ -1,7 +1,7 @@
#pragma once
#include <AK/HashMap.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
class GIconImpl : public RefCounted<GIconImpl> {
public:

View file

@ -1,6 +1,6 @@
#include "GLabel.h"
#include <LibGUI/GPainter.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
GLabel::GLabel(GWidget* parent)
: GFrame(parent)

View file

@ -1,7 +1,7 @@
#pragma once
#include <LibGUI/GFrame.h>
#include <SharedGraphics/TextAlignment.h>
#include <LibDraw/TextAlignment.h>
class GraphicsBitmap;

View file

@ -7,7 +7,7 @@
#include <AK/RefCounted.h>
#include <LibGUI/GModelIndex.h>
#include <LibGUI/GVariant.h>
#include <SharedGraphics/TextAlignment.h>
#include <LibDraw/TextAlignment.h>
class Font;
class GAbstractView;

View file

@ -1,6 +1,6 @@
#pragma once
#include <SharedGraphics/Painter.h>
#include <LibDraw/Painter.h>
class GWidget;
class GraphicsBitmap;

View file

@ -1,6 +1,6 @@
#include <LibGUI/GPainter.h>
#include <LibGUI/GRadioButton.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
static RefPtr<GraphicsBitmap> s_unfilled_circle_bitmap;
static RefPtr<GraphicsBitmap> s_filled_circle_bitmap;

View file

@ -1,7 +1,7 @@
#include <LibGUI/GPainter.h>
#include <LibGUI/GResizeCorner.h>
#include <LibGUI/GWindow.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
#include <WindowServer/WSAPITypes.h>
GResizeCorner::GResizeCorner(GWidget* parent)

View file

@ -1,8 +1,8 @@
#include <LibGUI/GPainter.h>
#include <LibGUI/GScrollBar.h>
#include <SharedGraphics/CharacterBitmap.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/CharacterBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
#include <LibDraw/StylePainter.h>
static const char* s_up_arrow_bitmap_data = {
" "

View file

@ -1,6 +1,6 @@
#include <LibGUI/GPainter.h>
#include <LibGUI/GSlider.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
GSlider::GSlider(GWidget* parent)
: GWidget(parent)

View file

@ -3,7 +3,7 @@
#include <LibGUI/GPainter.h>
#include <LibGUI/GResizeCorner.h>
#include <LibGUI/GStatusBar.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
GStatusBar::GStatusBar(GWidget* parent)
: GWidget(parent)

View file

@ -1,7 +1,7 @@
#include <LibGUI/GBoxLayout.h>
#include <LibGUI/GPainter.h>
#include <LibGUI/GTabWidget.h>
#include <SharedGraphics/StylePainter.h>
#include <LibDraw/StylePainter.h>
GTabWidget::GTabWidget(GWidget* parent)
: GWidget(parent)

View file

@ -3,7 +3,7 @@
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <LibGUI/GScrollableWidget.h>
#include <SharedGraphics/TextAlignment.h>
#include <LibDraw/TextAlignment.h>
class GAction;
class GMenu;

View file

@ -2,7 +2,7 @@
#include <AK/AKString.h>
#include <LibGUI/GIcon.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
namespace AK {
class JsonValue;

View file

@ -8,7 +8,7 @@
#include <LibGUI/GLayout.h>
#include <LibGUI/GMenu.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
#include <unistd.h>
GWidget::GWidget(GWidget* parent)

View file

@ -7,9 +7,9 @@
#include <LibCore/CObject.h>
#include <LibGUI/GEvent.h>
#include <LibGUI/GShortcut.h>
#include <SharedGraphics/Color.h>
#include <SharedGraphics/Font.h>
#include <SharedGraphics/Rect.h>
#include <LibDraw/Color.h>
#include <LibDraw/Font.h>
#include <LibDraw/Rect.h>
class GraphicsBitmap;
class GAction;

View file

@ -8,7 +8,7 @@
#include <LibC/stdlib.h>
#include <LibC/unistd.h>
#include <LibGUI/GPainter.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibDraw/GraphicsBitmap.h>
//#define UPDATE_COALESCING_DEBUG

View file

@ -5,8 +5,8 @@
#include <AK/WeakPtr.h>
#include <LibCore/CObject.h>
#include <LibGUI/GWindowType.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/Rect.h>
#include <LibDraw/GraphicsBitmap.h>
#include <LibDraw/Rect.h>
class GWidget;
class GWMEvent;

View file

@ -1,16 +1,6 @@
include ../../Makefile.common
SHAREDGRAPHICS_OBJS = \
../../SharedGraphics/Painter.o \
../../SharedGraphics/StylePainter.o \
../../SharedGraphics/Font.o \
../../SharedGraphics/Rect.o \
../../SharedGraphics/GraphicsBitmap.o \
../../SharedGraphics/CharacterBitmap.o \
../../SharedGraphics/Color.o \
../../SharedGraphics/PNGLoader.o
LIBGUI_OBJS = \
OBJS = \
GPainter.o \
GButton.o \
GCheckBox.o \
@ -64,8 +54,6 @@ LIBGUI_OBJS = \
GComboBox.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)
LIBRARY = libgui.a
DEFINES += -DUSERLAND

View file

@ -1,7 +1,7 @@
#pragma once
#include <LibHTML/DOM/Document.h>
#include <SharedGraphics/Size.h>
#include <LibDraw/Size.h>
class Frame {
public:

View file

@ -1,8 +1,8 @@
#pragma once
#include <LibHTML/CSS/LengthBox.h>
#include <SharedGraphics/Color.h>
#include <SharedGraphics/Size.h>
#include <LibDraw/Color.h>
#include <LibDraw/Size.h>
enum FontStyle {
Normal,

View file

@ -4,7 +4,7 @@
#include <AK/Vector.h>
#include <LibHTML/Layout/ComputedStyle.h>
#include <LibHTML/TreeNode.h>
#include <SharedGraphics/Rect.h>
#include <LibDraw/Rect.h>
class Node;
class LayoutBlock;