mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 07:27:45 +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:
parent
2167f60235
commit
1c0669f010
120 changed files with 201 additions and 190 deletions
16
Libraries/LibDraw/CharacterBitmap.cpp
Normal file
16
Libraries/LibDraw/CharacterBitmap.cpp
Normal 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));
|
||||
}
|
24
Libraries/LibDraw/CharacterBitmap.h
Normal file
24
Libraries/LibDraw/CharacterBitmap.h
Normal 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;
|
||||
};
|
81
Libraries/LibDraw/Color.cpp
Normal file
81
Libraries/LibDraw/Color.cpp
Normal 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
143
Libraries/LibDraw/Color.h
Normal 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 };
|
||||
};
|
45
Libraries/LibDraw/DisjointRectSet.cpp
Normal file
45
Libraries/LibDraw/DisjointRectSet.cpp
Normal 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);
|
||||
}
|
28
Libraries/LibDraw/DisjointRectSet.h
Normal file
28
Libraries/LibDraw/DisjointRectSet.h
Normal 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
175
Libraries/LibDraw/Font.cpp
Normal 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
95
Libraries/LibDraw/Font.h
Normal 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 };
|
||||
};
|
102
Libraries/LibDraw/GraphicsBitmap.cpp
Normal file
102
Libraries/LibDraw/GraphicsBitmap.cpp
Normal 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());
|
||||
}
|
||||
}
|
189
Libraries/LibDraw/GraphicsBitmap.h
Normal file
189
Libraries/LibDraw/GraphicsBitmap.h
Normal 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();
|
||||
}
|
||||
}
|
35
Libraries/LibDraw/Makefile
Normal file
35
Libraries/LibDraw/Makefile
Normal 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
|
457
Libraries/LibDraw/PNGLoader.cpp
Normal file
457
Libraries/LibDraw/PNGLoader.cpp
Normal 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;
|
||||
}
|
5
Libraries/LibDraw/PNGLoader.h
Normal file
5
Libraries/LibDraw/PNGLoader.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibDraw/GraphicsBitmap.h>
|
||||
|
||||
RefPtr<GraphicsBitmap> load_png(const StringView& path);
|
808
Libraries/LibDraw/Painter.cpp
Normal file
808
Libraries/LibDraw/Painter.cpp
Normal 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();
|
||||
}
|
97
Libraries/LibDraw/Painter.h
Normal file
97
Libraries/LibDraw/Painter.h
Normal 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
79
Libraries/LibDraw/Point.h
Normal 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();
|
||||
}
|
98
Libraries/LibDraw/Rect.cpp
Normal file
98
Libraries/LibDraw/Rect.cpp
Normal 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
252
Libraries/LibDraw/Rect.h
Normal 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
65
Libraries/LibDraw/Size.h
Normal 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();
|
||||
}
|
228
Libraries/LibDraw/StylePainter.cpp
Normal file
228
Libraries/LibDraw/StylePainter.cpp
Normal 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);
|
||||
}
|
31
Libraries/LibDraw/StylePainter.h
Normal file
31
Libraries/LibDraw/StylePainter.h
Normal 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&);
|
||||
};
|
18
Libraries/LibDraw/TextAlignment.h
Normal file
18
Libraries/LibDraw/TextAlignment.h
Normal 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;
|
||||
}
|
||||
}
|
6
Libraries/LibDraw/TextElision.h
Normal file
6
Libraries/LibDraw/TextElision.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
enum class TextElision {
|
||||
None,
|
||||
Right,
|
||||
};
|
4
Libraries/LibDraw/install.sh
Executable file
4
Libraries/LibDraw/install.sh
Executable 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
840
Libraries/LibDraw/puff.c
Normal 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
34
Libraries/LibDraw/puff.h
Normal 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 */
|
Loading…
Add table
Add a link
Reference in a new issue