Squash commits for public release
This commit is contained in:
24
libs/libg/BUILD.gn
Normal file
24
libs/libg/BUILD.gn
Normal file
@@ -0,0 +1,24 @@
|
||||
import("//build/libs/TEMPLATE.gni")
|
||||
|
||||
xOS_static_library("libg") {
|
||||
sources = [
|
||||
"src/Color.cpp",
|
||||
"src/Context.cpp",
|
||||
"src/Font.cpp",
|
||||
"src/ImageLoaders/PNGLoader.cpp",
|
||||
"src/PixelBitmap.cpp",
|
||||
"src/Rect.cpp",
|
||||
]
|
||||
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libipc",
|
||||
"libfreetype",
|
||||
]
|
||||
configs = [ "//build/libs:libcxx_flags" ]
|
||||
|
||||
if (host == "llvm") {
|
||||
cflags = [ "-flto" ]
|
||||
}
|
||||
}
|
||||
122
libs/libg/include/libg/Color.h
Normal file
122
libs/libg/include/libg/Color.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <libfoundation/Logger.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
// We keep opacity as an opposite to alpha.
|
||||
// This is used for capability with BGA driver
|
||||
// and probably should be fixed.
|
||||
class Color final {
|
||||
public:
|
||||
enum Colors {
|
||||
Red,
|
||||
Blue,
|
||||
Green,
|
||||
White,
|
||||
Black,
|
||||
LightSystemText,
|
||||
LightSystemAccentText,
|
||||
LightSystemBackground,
|
||||
LightSystemButton,
|
||||
LightSystemAccentButton,
|
||||
LightSystemBlue,
|
||||
LightSystemOpaque,
|
||||
LightSystemOpaque128,
|
||||
DarkSystemBackground,
|
||||
DarkSystemText,
|
||||
Opaque,
|
||||
};
|
||||
|
||||
Color() = default;
|
||||
Color(Colors);
|
||||
Color(const Color& c) = default;
|
||||
|
||||
Color(uint32_t color)
|
||||
: m_opacity((color & 0xFF000000) >> 24)
|
||||
, m_r((color & 0x00FF0000) >> 16)
|
||||
, m_g((color & 0x0000FF00) >> 8)
|
||||
, m_b((color & 0x000000FF) >> 0)
|
||||
{
|
||||
}
|
||||
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t alpha = 255)
|
||||
: m_opacity(255 - alpha)
|
||||
, m_b(b)
|
||||
, m_g(g)
|
||||
, m_r(r)
|
||||
{
|
||||
}
|
||||
|
||||
~Color() = default;
|
||||
|
||||
Color& operator=(const Color& c) = default;
|
||||
|
||||
Color& operator=(Color&& c)
|
||||
{
|
||||
m_opacity = c.m_opacity;
|
||||
m_r = c.m_r;
|
||||
m_g = c.m_g;
|
||||
m_b = c.m_b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline uint8_t alpha() const { return 255 - m_opacity; }
|
||||
inline bool is_opaque() const { return m_opacity == 255; }
|
||||
|
||||
inline uint8_t red() const { return m_r; }
|
||||
inline uint8_t green() const { return m_g; }
|
||||
inline uint8_t blue() const { return m_b; }
|
||||
inline void set_alpha(uint8_t alpha) { m_opacity = 255 - alpha; }
|
||||
|
||||
inline uint32_t u32() const
|
||||
{
|
||||
uint32_t clr = (m_opacity << 24)
|
||||
| (m_b << 0)
|
||||
| (m_g << 8)
|
||||
| (m_r << 16);
|
||||
return clr;
|
||||
}
|
||||
|
||||
[[gnu::always_inline]] inline void mix_with(const Color& clr)
|
||||
{
|
||||
if (clr.is_opaque()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clr.alpha() == 255 || is_opaque()) {
|
||||
*this = clr;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t alpha_c = 255 * (alpha() + clr.alpha()) - alpha() * clr.alpha();
|
||||
size_t alpha_of_me = alpha() * (255 - clr.alpha());
|
||||
size_t alpha_of_it = 255 * clr.alpha();
|
||||
|
||||
m_r = (red() * alpha_of_me + clr.red() * alpha_of_it) / alpha_c;
|
||||
m_g = (green() * alpha_of_me + clr.green() * alpha_of_it) / alpha_c;
|
||||
m_b = (blue() * alpha_of_me + clr.blue() * alpha_of_it) / alpha_c;
|
||||
m_opacity = 255 - (alpha_c / 255);
|
||||
}
|
||||
|
||||
inline LG::Color darken(int percents) const
|
||||
{
|
||||
double multiplier = 1.0 - (double(percents) / 100.0);
|
||||
int r = int(red() * multiplier);
|
||||
int g = int(green() * multiplier);
|
||||
int b = int(blue() * multiplier);
|
||||
return LG::Color(r, g, b);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t m_b { 0 };
|
||||
uint8_t m_g { 0 };
|
||||
uint8_t m_r { 0 };
|
||||
uint8_t m_opacity { 0 };
|
||||
};
|
||||
|
||||
// Should that color size is 4 bytes, as window_server requires this.
|
||||
static_assert(sizeof(Color) == 4);
|
||||
|
||||
} // namespace LG
|
||||
55
libs/libg/include/libg/Context.h
Normal file
55
libs/libg/include/libg/Context.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <libg/Color.h>
|
||||
#include <libg/CornerMask.h>
|
||||
#include <libg/Font.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Point.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <libg/Shading.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
class Context {
|
||||
public:
|
||||
explicit Context(PixelBitmap&);
|
||||
~Context() = default;
|
||||
|
||||
void add_clip(const Rect& rect);
|
||||
void reset_clip();
|
||||
|
||||
void set(const Point<int>& start, const PixelBitmap& bitmap);
|
||||
void set_with_bounds(const Rect& rect, const PixelBitmap& bitmap);
|
||||
void draw(const Point<int>& start, const PixelBitmap& bitmap);
|
||||
void draw_with_bounds(const Rect& rect, const PixelBitmap& bitmap);
|
||||
void draw(const Point<int>& start, const Glyph& bitmap);
|
||||
void draw_rounded(const Point<int>& start, const PixelBitmap& bitmap, const CornerMask& mask = { 0, false, false });
|
||||
void draw_shading(const Rect& rect, const Shading& shading);
|
||||
void draw_box_shading(const Rect& rect, const Shading& shading, const CornerMask& mask = { 0, false, false });
|
||||
void fill(const Rect& rect);
|
||||
void fill_rounded(const Rect& rect, const CornerMask& mask = { 0, false, false });
|
||||
void mix(const Rect& rect);
|
||||
void add_ellipse(const Rect& rect);
|
||||
|
||||
void set_draw_offset(const Point<int>& offset) { m_draw_offset = offset; }
|
||||
Point<int>& draw_offset() { return m_draw_offset; }
|
||||
const Point<int>& draw_offset() const { return m_draw_offset; }
|
||||
void set_fill_color(const Color& clr) { m_color = clr; }
|
||||
|
||||
inline const Color& fill_color() const { return m_color; }
|
||||
|
||||
private:
|
||||
void fill_rounded_helper(const Point<int>& start, size_t radius);
|
||||
void draw_rounded_helper(const Point<int>& start, size_t radius, const PixelBitmap& bitmap);
|
||||
void shadow_rounded_helper(const Point<int>& start, size_t radius, const Shading& shading);
|
||||
|
||||
PixelBitmap& m_bitmap;
|
||||
Rect m_clip;
|
||||
const Rect m_origin_clip;
|
||||
Point<int> m_draw_offset { 0, 0 };
|
||||
Point<int> m_bitmap_offset { 0, 0 };
|
||||
Color m_color {};
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
36
libs/libg/include/libg/CornerMask.h
Normal file
36
libs/libg/include/libg/CornerMask.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
class CornerMask {
|
||||
public:
|
||||
static constexpr bool Masked = true;
|
||||
static constexpr bool NonMasked = false;
|
||||
static constexpr size_t SystemRadius = 4;
|
||||
|
||||
CornerMask() = default;
|
||||
CornerMask(size_t radius, bool top_rounded = Masked, bool bottom_rounded = Masked)
|
||||
: m_radius(radius)
|
||||
, m_top_rounded(top_rounded)
|
||||
, m_bottom_rounded(bottom_rounded)
|
||||
{
|
||||
}
|
||||
|
||||
~CornerMask() = default;
|
||||
|
||||
static CornerMask Standard() { return CornerMask(SystemRadius, Masked, Masked); }
|
||||
|
||||
size_t radius() const { return m_radius; }
|
||||
bool top_rounded() const { return m_top_rounded; }
|
||||
bool bottom_rounded() const { return m_bottom_rounded; }
|
||||
|
||||
private:
|
||||
size_t m_radius { 0 };
|
||||
bool m_top_rounded { NonMasked };
|
||||
bool m_bottom_rounded { NonMasked };
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
90
libs/libg/include/libg/Font.h
Normal file
90
libs/libg/include/libg/Font.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <libfreetype/freetype/freetype.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Glyph.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
class FontCacher {
|
||||
public:
|
||||
FontCacher() = default;
|
||||
~FontCacher() = default;
|
||||
|
||||
void cache(int ch, Glyph&& gl) { m_cache[ch] = std::move(gl); }
|
||||
const Glyph& get(int ch) const { return m_cache[ch]; }
|
||||
bool has(int ch) const { return !m_cache[ch].empty(); }
|
||||
|
||||
private:
|
||||
Glyph m_cache[256];
|
||||
};
|
||||
|
||||
class Font {
|
||||
public:
|
||||
static const int SystemDefaultSize = 10;
|
||||
static const int SystemTitleSize = 20;
|
||||
static const int SystemMaxSize = 36;
|
||||
|
||||
~Font() = default;
|
||||
|
||||
static Font& system_font(int of_size = SystemDefaultSize);
|
||||
static Font& system_bold_font(int of_size = SystemDefaultSize);
|
||||
static Font* load_from_file(const char* path);
|
||||
static Font* load_from_file_ttf(const char* path, size_t size);
|
||||
static Font* load_from_mem(uint8_t* path);
|
||||
|
||||
inline size_t size() const { return m_font_size; }
|
||||
inline const Glyph& glyph(size_t ch) const
|
||||
{
|
||||
if (!m_font_cache->has(ch)) {
|
||||
m_font_cache->cache(ch, load_glyph(ch));
|
||||
}
|
||||
return m_font_cache->get(ch);
|
||||
}
|
||||
|
||||
private:
|
||||
enum FontType {
|
||||
SerenityOS,
|
||||
FreeType,
|
||||
};
|
||||
|
||||
struct SerenityOSFontDesc final {
|
||||
uint32_t* raw_data;
|
||||
uint8_t* width_data;
|
||||
uint8_t height;
|
||||
uint8_t width;
|
||||
uint8_t spacing;
|
||||
bool dynamic_width;
|
||||
size_t count;
|
||||
|
||||
Glyph load_glyph(size_t ch) const;
|
||||
};
|
||||
|
||||
struct FreeTypeFontDesc final {
|
||||
FT_Face face;
|
||||
size_t height;
|
||||
|
||||
Glyph load_glyph(size_t ch) const;
|
||||
};
|
||||
|
||||
Font(const SerenityOSFontDesc& font_desc, size_t font_size);
|
||||
Font(const FreeTypeFontDesc& font_desc, size_t font_size);
|
||||
|
||||
Glyph load_glyph(size_t ch) const;
|
||||
|
||||
size_t m_font_size { 0 };
|
||||
size_t m_const_width { 0 }; // If const width is 0, width id calculated dynamicly for each glyph.
|
||||
|
||||
FontCacher* m_font_cache { nullptr };
|
||||
|
||||
FontType m_font_type;
|
||||
union {
|
||||
SerenityOSFontDesc serenity;
|
||||
FreeTypeFontDesc free_type;
|
||||
} m_font_desc;
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
128
libs/libg/include/libg/Glyph.h
Normal file
128
libs/libg/include/libg/Glyph.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <libfreetype/freetype/freetype.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
struct GlyphMetrics {
|
||||
uint8_t width { 0 };
|
||||
uint8_t height { 0 };
|
||||
uint8_t top_bearing { 0 };
|
||||
uint8_t left_bearing { 0 };
|
||||
uint8_t baseline { 0 };
|
||||
uint8_t advance { 0 };
|
||||
uint16_t font_size { 0 };
|
||||
};
|
||||
|
||||
class Font;
|
||||
class Glyph {
|
||||
public:
|
||||
friend class Font;
|
||||
enum class ConstDataMarker : int {}; // Pointer to the data is valid while thid glyph is valid.
|
||||
|
||||
enum Type {
|
||||
None,
|
||||
PlainBitmap,
|
||||
FreeType,
|
||||
};
|
||||
|
||||
Glyph() = default;
|
||||
Glyph(const void* data, const GlyphMetrics&);
|
||||
Glyph(const void* data, const GlyphMetrics&, ConstDataMarker);
|
||||
Glyph(Glyph&& gl);
|
||||
~Glyph();
|
||||
|
||||
LG::Glyph& operator=(const LG::Glyph& gl);
|
||||
LG::Glyph& operator=(LG::Glyph&& gl);
|
||||
|
||||
inline Type type() const { return m_type; }
|
||||
|
||||
inline size_t width() const { return m_metrics.width; }
|
||||
inline size_t height() const { return m_metrics.height; }
|
||||
|
||||
inline int top() const { return m_metrics.font_size - m_metrics.top_bearing - m_metrics.baseline; }
|
||||
inline int left() const { return m_metrics.left_bearing; }
|
||||
|
||||
template <typename T>
|
||||
inline const T* data() const { return (T*)m_data; }
|
||||
inline bool bit_at(int x, int y) const { return data<uint32_t>()[y] & (1 << x); }
|
||||
inline uint8_t alpha_at(int x, int y) const { return data<uint8_t>()[y * m_metrics.width + x]; }
|
||||
|
||||
inline size_t advance() const { return m_metrics.advance; }
|
||||
|
||||
inline bool empty() const { return m_type == Type::None; }
|
||||
|
||||
private:
|
||||
void* m_data { nullptr };
|
||||
Type m_type { Type::None };
|
||||
GlyphMetrics m_metrics {};
|
||||
|
||||
bool m_owned_data { false };
|
||||
};
|
||||
|
||||
inline Glyph::Glyph(const void* data, const GlyphMetrics& metrics, ConstDataMarker)
|
||||
: m_data((void*)data)
|
||||
, m_metrics(metrics)
|
||||
, m_type(Type::PlainBitmap)
|
||||
{
|
||||
}
|
||||
|
||||
inline Glyph::Glyph(const void* data, const GlyphMetrics& metrics)
|
||||
: m_metrics(metrics)
|
||||
, m_type(Type::FreeType)
|
||||
, m_owned_data(true)
|
||||
{
|
||||
size_t bitmap_sz = (size_t)m_metrics.width * (size_t)m_metrics.height;
|
||||
m_data = new uint8_t[bitmap_sz];
|
||||
memcpy(m_data, data, bitmap_sz);
|
||||
}
|
||||
|
||||
inline Glyph::Glyph(Glyph&& gl)
|
||||
: m_data(gl.m_data)
|
||||
, m_metrics(gl.m_metrics)
|
||||
, m_type(gl.m_type)
|
||||
, m_owned_data(gl.m_owned_data)
|
||||
{
|
||||
gl.m_owned_data = false;
|
||||
gl.m_type = Type::None;
|
||||
gl.m_data = nullptr;
|
||||
}
|
||||
|
||||
inline Glyph::~Glyph()
|
||||
{
|
||||
if (m_owned_data) {
|
||||
free(m_data);
|
||||
}
|
||||
}
|
||||
|
||||
inline LG::Glyph& Glyph::operator=(LG::Glyph&& gl)
|
||||
{
|
||||
m_data = gl.m_data;
|
||||
m_metrics = gl.m_metrics;
|
||||
m_type = gl.m_type;
|
||||
m_owned_data = gl.m_owned_data;
|
||||
|
||||
gl.m_owned_data = false;
|
||||
gl.m_type = Type::None;
|
||||
gl.m_data = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline LG::Glyph& Glyph::operator=(const LG::Glyph& gl)
|
||||
{
|
||||
if (!gl.m_owned_data) {
|
||||
m_data = gl.m_data;
|
||||
} else {
|
||||
size_t bitmap_sz = (size_t)gl.m_metrics.width * (size_t)gl.m_metrics.height;
|
||||
m_data = new uint8_t[bitmap_sz];
|
||||
memcpy(m_data, gl.m_data, bitmap_sz);
|
||||
}
|
||||
m_metrics = gl.m_metrics;
|
||||
m_type = gl.m_type;
|
||||
m_owned_data = gl.m_owned_data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
154
libs/libg/include/libg/ImageLoaders/PNGLoader.h
Normal file
154
libs/libg/include/libg/ImageLoaders/PNGLoader.h
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <libfoundation/ByteOrder.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <string>
|
||||
|
||||
namespace LG {
|
||||
namespace PNG {
|
||||
|
||||
struct ChunkHeader {
|
||||
uint32_t len;
|
||||
uint8_t type[4];
|
||||
};
|
||||
|
||||
struct IHDRChunk {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint8_t depth;
|
||||
uint8_t color_type;
|
||||
uint8_t compression_method;
|
||||
uint8_t filter_method;
|
||||
uint8_t interlace_method;
|
||||
};
|
||||
|
||||
class DataStreamer {
|
||||
public:
|
||||
DataStreamer() = default;
|
||||
|
||||
DataStreamer(const void* data)
|
||||
: m_ptr((uint8_t*)data)
|
||||
{
|
||||
}
|
||||
|
||||
~DataStreamer() = default;
|
||||
|
||||
template <typename T>
|
||||
void read(T& val)
|
||||
{
|
||||
val = *(T*)m_ptr;
|
||||
m_ptr += sizeof(T);
|
||||
val = LFoundation::ByteOrder::from_network(val);
|
||||
}
|
||||
|
||||
void read(void* buffer, size_t cnt)
|
||||
{
|
||||
memcpy((uint8_t*)buffer, m_ptr, cnt);
|
||||
m_ptr += cnt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T at(int index) const
|
||||
{
|
||||
T val = *((T*)m_orig_ptr + index);
|
||||
val = LFoundation::ByteOrder::from_network(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void skip(size_t cnt) { m_ptr += cnt; }
|
||||
void set(const void* data) { m_orig_ptr = m_ptr = (uint8_t*)data; }
|
||||
const uint8_t* ptr() const { return m_ptr; }
|
||||
uint8_t* ptr() { return m_ptr; }
|
||||
|
||||
private:
|
||||
uint8_t* m_orig_ptr { nullptr };
|
||||
uint8_t* m_ptr { nullptr };
|
||||
};
|
||||
|
||||
class Scanline {
|
||||
public:
|
||||
Scanline() = default;
|
||||
Scanline(int type, void* ptr)
|
||||
: m_filter_type(type)
|
||||
, m_ptr(ptr)
|
||||
{
|
||||
}
|
||||
~Scanline() = default;
|
||||
|
||||
void set(int type, void* ptr) { m_ptr = ptr; }
|
||||
int filter_type() const { return m_filter_type; }
|
||||
uint8_t* data() const { return (uint8_t*)m_ptr; }
|
||||
|
||||
private:
|
||||
int m_filter_type { 0 };
|
||||
void* m_ptr { nullptr };
|
||||
size_t m_len { 0 };
|
||||
};
|
||||
|
||||
class ScanlineKeeper {
|
||||
public:
|
||||
ScanlineKeeper() = default;
|
||||
~ScanlineKeeper() { invalidate(); }
|
||||
|
||||
inline void init(void* ptr) { m_ptr = ptr; }
|
||||
inline void init(void* ptr, uint8_t color_length) { m_ptr = ptr, m_color_length = color_length; }
|
||||
|
||||
void invalidate()
|
||||
{
|
||||
if (m_ptr) {
|
||||
free(m_ptr);
|
||||
m_data.clear();
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline void add(Scanline&& el) { m_data.push_back(std::move(el)); }
|
||||
inline void set_color_length(uint8_t color_length) { m_color_length = color_length; }
|
||||
inline uint8_t color_length() const { return m_color_length; }
|
||||
|
||||
inline std::vector<Scanline>& scanlines() { return m_data; }
|
||||
inline const std::vector<Scanline>& scanlines() const { return m_data; }
|
||||
|
||||
private:
|
||||
uint8_t m_color_length { 0 };
|
||||
void* m_ptr { nullptr };
|
||||
std::vector<Scanline> m_data;
|
||||
};
|
||||
|
||||
class PNGLoader {
|
||||
public:
|
||||
PNGLoader() = default;
|
||||
~PNGLoader() = default;
|
||||
|
||||
PixelBitmap load_from_file(const std::string& path);
|
||||
PixelBitmap load_from_mem(const uint8_t* ptr);
|
||||
|
||||
inline DataStreamer& streamer() { return m_streamer; }
|
||||
|
||||
private:
|
||||
bool check_header(const uint8_t* ptr) const;
|
||||
|
||||
void proccess_stream(PixelBitmap& bitmap);
|
||||
void process_compressed_data(PixelBitmap& bitmap);
|
||||
bool read_chunk(PixelBitmap& bitmap);
|
||||
void read_IHDR(ChunkHeader& header, PixelBitmap& bitmap);
|
||||
void read_TEXT(ChunkHeader& header, PixelBitmap& bitmap);
|
||||
void read_PHYS(ChunkHeader& header, PixelBitmap& bitmap);
|
||||
void read_ORNT(ChunkHeader& header, PixelBitmap& bitmap);
|
||||
void read_gAMA(ChunkHeader& header, PixelBitmap& bitmap);
|
||||
void read_IDAT(ChunkHeader& header, PixelBitmap& bitmap);
|
||||
|
||||
uint8_t paeth_predictor(int a, int b, int c);
|
||||
void unfilter_scanlines();
|
||||
void copy_scanlines_to_bitmap(PixelBitmap& bitmap);
|
||||
|
||||
std::vector<uint8_t> m_compressed_data;
|
||||
DataStreamer m_streamer;
|
||||
IHDRChunk m_ihdr_chunk;
|
||||
ScanlineKeeper m_scanline_keeper;
|
||||
};
|
||||
|
||||
} // namespace PNG
|
||||
} // namespace LG
|
||||
101
libs/libg/include/libg/PixelBitmap.h
Normal file
101
libs/libg/include/libg/PixelBitmap.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <libg/Size.h>
|
||||
#include <new>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
enum PixelBitmapFormat {
|
||||
RGB,
|
||||
RGBA,
|
||||
};
|
||||
|
||||
class PixelBitmap {
|
||||
public:
|
||||
PixelBitmap() = default;
|
||||
PixelBitmap(size_t width, size_t height, PixelBitmapFormat format = RGB);
|
||||
PixelBitmap(Color* buffer, size_t width, size_t height, PixelBitmapFormat format = RGB);
|
||||
PixelBitmap(const PixelBitmap& bitmap);
|
||||
PixelBitmap(PixelBitmap&& moved_bitmap) noexcept;
|
||||
~PixelBitmap()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (m_should_free) {
|
||||
delete m_data;
|
||||
}
|
||||
m_data = nullptr;
|
||||
m_bounds.set_width(0);
|
||||
m_bounds.set_height(0);
|
||||
}
|
||||
|
||||
PixelBitmap& operator=(const PixelBitmap& bitmap)
|
||||
{
|
||||
clear();
|
||||
m_bounds = bitmap.bounds();
|
||||
m_should_free = bitmap.m_should_free;
|
||||
m_format = bitmap.m_format;
|
||||
if (m_should_free) {
|
||||
size_t len = width() * height() * sizeof(Color);
|
||||
m_data = (Color*)malloc(len);
|
||||
memcpy((uint8_t*)m_data, (uint8_t*)bitmap.m_data, len);
|
||||
} else {
|
||||
m_data = bitmap.m_data;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
PixelBitmap& operator=(PixelBitmap&& moved_bitmap) noexcept
|
||||
{
|
||||
m_data = moved_bitmap.m_data;
|
||||
m_bounds = moved_bitmap.bounds();
|
||||
m_should_free = moved_bitmap.m_should_free;
|
||||
m_format = moved_bitmap.m_format;
|
||||
moved_bitmap.m_data = nullptr;
|
||||
moved_bitmap.bounds().set_width(0);
|
||||
moved_bitmap.bounds().set_height(0);
|
||||
moved_bitmap.m_should_free = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline size_t width() const { return m_bounds.width(); }
|
||||
inline size_t height() const { return m_bounds.height(); }
|
||||
inline LG::Rect& bounds() { return m_bounds; }
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
inline void set_size(LG::Size size) { m_bounds.set_height(size.height()), m_bounds.set_width(size.width()); }
|
||||
|
||||
inline Color* data() const { return m_data; }
|
||||
inline void set_data(Color* data)
|
||||
{
|
||||
if (m_should_free) {
|
||||
delete m_data;
|
||||
}
|
||||
m_data = data;
|
||||
m_should_free = false;
|
||||
}
|
||||
|
||||
inline Color* line(size_t i) { return m_data + i * width(); }
|
||||
inline const Color* line(size_t i) const { return m_data + i * width(); }
|
||||
inline Color* operator[](size_t i) { return line(i); }
|
||||
inline const Color* operator[](size_t i) const { return line(i); }
|
||||
void resize(size_t width, size_t height);
|
||||
|
||||
inline void set_format(PixelBitmapFormat format) { m_format = format; }
|
||||
inline PixelBitmapFormat format() const { return m_format; }
|
||||
inline bool has_alpha_channel() const { return m_format == RGBA; }
|
||||
|
||||
private:
|
||||
Color* m_data { nullptr };
|
||||
LG::Rect m_bounds { 0, 0, 0, 0 };
|
||||
bool m_should_free { false };
|
||||
PixelBitmapFormat m_format { RGB };
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
82
libs/libg/include/libg/Point.h
Normal file
82
libs/libg/include/libg/Point.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <libipc/Decodable.h>
|
||||
#include <libipc/Encodable.h>
|
||||
#include <libipc/Encoder.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
template <typename T>
|
||||
class Point : public Encodable<Point<T>>, public Decodable<Point<T>> {
|
||||
public:
|
||||
Point() = default;
|
||||
|
||||
Point(T x, T y)
|
||||
: m_x(x)
|
||||
, m_y(y)
|
||||
{
|
||||
}
|
||||
~Point() = default;
|
||||
|
||||
Point& operator=(const Point& p)
|
||||
{
|
||||
m_x = p.x(), m_y = p.y();
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void set(const Point& p) { m_x = p.x(), m_y = p.y(); }
|
||||
inline void set_x(int x) { m_x = x; }
|
||||
inline void set_y(int y) { m_y = y; }
|
||||
|
||||
inline T x() const { return m_x; }
|
||||
inline T y() const { return m_y; }
|
||||
|
||||
inline void offset_by(int x, int y) { m_x += x, m_y += y; }
|
||||
inline void offset_by(const Point& p) { offset_by(p.x(), p.y()); }
|
||||
|
||||
Point operator-() const { return { -m_x, -m_y }; }
|
||||
Point operator-(const Point& p) const { return { m_x - p.m_x, m_y - p.m_y }; }
|
||||
Point& operator-=(const Point& p)
|
||||
{
|
||||
m_x -= p.m_x;
|
||||
m_y -= p.m_y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Point& operator+=(const Point& p)
|
||||
{
|
||||
m_x += p.m_x;
|
||||
m_y += p.m_y;
|
||||
return *this;
|
||||
}
|
||||
Point operator+(const Point& p) const { return { m_x + p.m_x, m_y + p.m_y }; }
|
||||
|
||||
bool operator==(const Point& p) const
|
||||
{
|
||||
return m_x == p.m_x && m_y == p.m_y;
|
||||
}
|
||||
|
||||
bool operator!=(const Point& p) const
|
||||
{
|
||||
return !(*this == p);
|
||||
}
|
||||
|
||||
void encode(EncodedMessage& buf) const override
|
||||
{
|
||||
Encoder::append(buf, m_x);
|
||||
Encoder::append(buf, m_y);
|
||||
}
|
||||
|
||||
void decode(const char* buf, size_t& offset) override
|
||||
{
|
||||
Encoder::decode(buf, offset, m_x);
|
||||
Encoder::decode(buf, offset, m_y);
|
||||
}
|
||||
|
||||
private:
|
||||
T m_x {};
|
||||
T m_y {};
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
163
libs/libg/include/libg/Rect.h
Normal file
163
libs/libg/include/libg/Rect.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <libg/Point.h>
|
||||
#include <libg/Size.h>
|
||||
#include <libipc/Decodable.h>
|
||||
#include <libipc/Encodable.h>
|
||||
#include <libipc/Encoder.h>
|
||||
#include <ostream>
|
||||
#include <sys/types.h>
|
||||
#include <utility>
|
||||
|
||||
namespace LG {
|
||||
|
||||
class Rect : public Encodable<Rect>, public Decodable<Rect> {
|
||||
public:
|
||||
Rect() = default;
|
||||
Rect(int x, int y, size_t width, size_t height);
|
||||
|
||||
~Rect() = default;
|
||||
|
||||
Rect& operator=(const Rect& r)
|
||||
{
|
||||
m_origin = r.origin(), m_width = r.width(), m_height = r.height();
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline size_t width() const { return m_width; }
|
||||
inline size_t height() const { return m_height; }
|
||||
inline size_t square() const { return width() * height(); }
|
||||
inline LG::Size size() const { return LG::Size(m_width, m_height); }
|
||||
inline int min_x() const { return m_origin.x(); }
|
||||
inline int mid_x() const { return m_origin.x() + width() / 2; }
|
||||
inline int max_x() const { return m_origin.x() + width() - 1; }
|
||||
inline int min_y() const { return m_origin.y(); }
|
||||
inline int mid_y() const { return m_origin.y() + height() / 2; }
|
||||
inline int max_y() const { return m_origin.y() + height() - 1; }
|
||||
inline bool empty() const { return !width() || !height(); }
|
||||
|
||||
inline void set_x(int new_x) { m_origin.set_x(new_x); }
|
||||
inline void set_y(int new_y) { m_origin.set_y(new_y); }
|
||||
inline void set_width(int new_w) { m_width = new_w; }
|
||||
inline void set_height(int new_h) { m_height = new_h; }
|
||||
|
||||
inline bool contains(int x, int y) const { return min_x() <= x && x <= max_x() && min_y() <= y && y <= max_y(); }
|
||||
inline bool contains(const Point<int>& p) const { return contains(p.x(), p.y()); }
|
||||
inline bool contains(const Rect& p) const { return contains(p.min_x(), p.min_y()) && contains(p.max_x(), p.max_y()); }
|
||||
inline void offset_by(int x, int y) { m_origin.offset_by(x, y); }
|
||||
inline void offset_by(const Point<int>& p) { m_origin.offset_by(p); }
|
||||
|
||||
inline void set_origin(const Point<int>& origin) { m_origin = origin; }
|
||||
inline void set_origin(Point<int>&& origin) { m_origin = std::move(origin); }
|
||||
inline Point<int>& origin() { return m_origin; }
|
||||
inline const Point<int>& origin() const { return m_origin; }
|
||||
|
||||
void unite(const Rect& other);
|
||||
LG::Rect union_of(const Rect& other) const;
|
||||
void intersect(const Rect& other);
|
||||
bool intersects(const Rect& other) const;
|
||||
LG::Rect intersection(const Rect& other) const;
|
||||
|
||||
void encode(EncodedMessage& buf) const override;
|
||||
void decode(const char* buf, size_t& offset) override;
|
||||
|
||||
bool operator==(const Rect& r) const
|
||||
{
|
||||
return m_width == r.m_width && m_height == r.m_height && m_origin == r.m_origin;
|
||||
}
|
||||
|
||||
bool operator!=(const Rect& r) const
|
||||
{
|
||||
return !(*this == r);
|
||||
}
|
||||
|
||||
private:
|
||||
Point<int> m_origin;
|
||||
size_t m_width { 0 };
|
||||
size_t m_height { 0 };
|
||||
};
|
||||
|
||||
// Implementation
|
||||
|
||||
inline void Rect::unite(const Rect& other)
|
||||
{
|
||||
// (a, b) - top left corner
|
||||
// (c, d) - bottom right corner
|
||||
int a = std::min(min_x(), other.min_x());
|
||||
int b = std::min(min_y(), other.min_y());
|
||||
int c = std::max(max_x(), other.max_x());
|
||||
int d = std::max(max_y(), other.max_y());
|
||||
|
||||
set_x(a);
|
||||
set_y(b);
|
||||
set_width(c - a + 1);
|
||||
set_height(d - b + 1);
|
||||
}
|
||||
|
||||
inline LG::Rect Rect::union_of(const Rect& other) const
|
||||
{
|
||||
// (a, b) - top left corner
|
||||
// (c, d) - bottom right corner
|
||||
int a = std::min(min_x(), other.min_x());
|
||||
int b = std::min(min_y(), other.min_y());
|
||||
int c = std::max(max_x(), other.max_x());
|
||||
int d = std::max(max_y(), other.max_y());
|
||||
return LG::Rect(a, b, c - a + 1, d - b + 1);
|
||||
}
|
||||
|
||||
inline void Rect::intersect(const Rect& other)
|
||||
{
|
||||
// (a, b) - top left corner
|
||||
// (c, d) - bottom right corner
|
||||
int a = std::max(min_x(), other.min_x());
|
||||
int b = std::max(min_y(), other.min_y());
|
||||
int c = std::min(max_x(), other.max_x());
|
||||
int d = std::min(max_y(), other.max_y());
|
||||
|
||||
if (a > c || b > d) {
|
||||
set_width(0);
|
||||
set_height(0);
|
||||
return;
|
||||
}
|
||||
|
||||
set_x(a);
|
||||
set_y(b);
|
||||
set_width(c - a + 1);
|
||||
set_height(d - b + 1);
|
||||
}
|
||||
|
||||
inline bool Rect::intersects(const Rect& other) const
|
||||
{
|
||||
// (a, b) - top left corner
|
||||
// (c, d) - bottom right corner
|
||||
int a = std::max(min_x(), other.min_x());
|
||||
int b = std::max(min_y(), other.min_y());
|
||||
int c = std::min(max_x(), other.max_x());
|
||||
int d = std::min(max_y(), other.max_y());
|
||||
return c >= a && d >= b;
|
||||
}
|
||||
|
||||
inline LG::Rect Rect::intersection(const Rect& other) const
|
||||
{
|
||||
// (a, b) - top left corner
|
||||
// (c, d) - bottom right corner
|
||||
int a = std::max(min_x(), other.min_x());
|
||||
int b = std::max(min_y(), other.min_y());
|
||||
int c = std::min(max_x(), other.max_x());
|
||||
int d = std::min(max_y(), other.max_y());
|
||||
|
||||
if (a > c || b > d) {
|
||||
return LG::Rect(0, 0, 0, 0);
|
||||
}
|
||||
return LG::Rect(a, b, c - a + 1, d - b + 1);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits>
|
||||
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const LG::Rect& rect)
|
||||
{
|
||||
os << "Rect(" << rect.origin().x() << ", " << rect.origin().y() << " : " << rect.width() << "w " << rect.height() << "h)";
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
56
libs/libg/include/libg/Shading.h
Normal file
56
libs/libg/include/libg/Shading.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
class Shading {
|
||||
public:
|
||||
enum Type {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
TopToBottom,
|
||||
BottomToTop,
|
||||
Deg45,
|
||||
Deg135,
|
||||
Deg225,
|
||||
Deg315,
|
||||
Box,
|
||||
};
|
||||
static const int SystemSpread = 4;
|
||||
|
||||
Shading() = default;
|
||||
explicit Shading(Type type)
|
||||
: m_type(type)
|
||||
, m_final_alpha(0)
|
||||
, m_spread(3)
|
||||
{
|
||||
}
|
||||
|
||||
Shading(Type type, uint8_t final_alpha)
|
||||
: m_type(type)
|
||||
, m_final_alpha(final_alpha)
|
||||
, m_spread(SystemSpread)
|
||||
{
|
||||
}
|
||||
|
||||
Shading(Type type, uint8_t final_alpha, int spread)
|
||||
: m_type(type)
|
||||
, m_final_alpha(final_alpha)
|
||||
, m_spread(spread)
|
||||
{
|
||||
}
|
||||
|
||||
~Shading() = default;
|
||||
|
||||
inline Type type() const { return m_type; }
|
||||
inline uint8_t final_alpha() const { return m_final_alpha; }
|
||||
inline int spread() const { return m_spread; }
|
||||
|
||||
private:
|
||||
Type m_type { Box };
|
||||
uint8_t m_final_alpha { 0 };
|
||||
int m_spread { 0 };
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
55
libs/libg/include/libg/Size.h
Normal file
55
libs/libg/include/libg/Size.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <libipc/Decodable.h>
|
||||
#include <libipc/Encodable.h>
|
||||
#include <libipc/Encoder.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
class Size : public Encodable<Size>, public Decodable<Size> {
|
||||
public:
|
||||
Size() = default;
|
||||
|
||||
Size(size_t width, size_t height)
|
||||
: m_width(width)
|
||||
, m_height(height)
|
||||
{
|
||||
}
|
||||
~Size() = default;
|
||||
|
||||
inline void set(const Size& p) { m_width = p.width(), m_height = p.height(); }
|
||||
inline void set_width(int w) { m_width = w; }
|
||||
inline void set_height(int h) { m_height = h; }
|
||||
|
||||
inline size_t width() const { return m_width; }
|
||||
inline size_t height() const { return m_height; }
|
||||
|
||||
void encode(EncodedMessage& buf) const override
|
||||
{
|
||||
Encoder::append(buf, m_width);
|
||||
Encoder::append(buf, m_height);
|
||||
}
|
||||
|
||||
void decode(const char* buf, size_t& offset) override
|
||||
{
|
||||
Encoder::decode(buf, offset, m_width);
|
||||
Encoder::decode(buf, offset, m_height);
|
||||
}
|
||||
|
||||
bool operator==(const Size& r) const
|
||||
{
|
||||
return m_width == r.m_width && m_height == r.m_height;
|
||||
}
|
||||
|
||||
bool operator!=(const Size& r) const
|
||||
{
|
||||
return !(*this == r);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_width { 0 };
|
||||
size_t m_height { 0 };
|
||||
};
|
||||
|
||||
} // namespace LG
|
||||
72
libs/libg/src/Color.cpp
Normal file
72
libs/libg/src/Color.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <libg/Color.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
Color::Color(Colors clr)
|
||||
{
|
||||
struct rgb {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t o;
|
||||
} rgb_color;
|
||||
|
||||
switch (clr) {
|
||||
case Red:
|
||||
rgb_color = { 255, 0, 0, 0 };
|
||||
break;
|
||||
case Blue:
|
||||
rgb_color = { 0, 0, 255, 0 };
|
||||
break;
|
||||
case Green:
|
||||
rgb_color = { 0, 255, 0, 0 };
|
||||
break;
|
||||
case White:
|
||||
rgb_color = { 255, 255, 255, 0 };
|
||||
break;
|
||||
case Black:
|
||||
rgb_color = { 0, 0, 0, 0 };
|
||||
break;
|
||||
case LightSystemBackground:
|
||||
rgb_color = { 243, 243, 243, 0 };
|
||||
break;
|
||||
case LightSystemText:
|
||||
rgb_color = { 243, 243, 243, 0 };
|
||||
break;
|
||||
case LightSystemAccentText:
|
||||
rgb_color = { 36, 93, 150, 0 };
|
||||
break;
|
||||
case LightSystemButton:
|
||||
rgb_color = { 235, 235, 235, 0 };
|
||||
break;
|
||||
case LightSystemAccentButton:
|
||||
rgb_color = { 209, 231, 253, 0 };
|
||||
break;
|
||||
case LightSystemBlue:
|
||||
rgb_color = { 62, 119, 233, 0 };
|
||||
break;
|
||||
case LightSystemOpaque:
|
||||
rgb_color = { 243, 243, 243, 30 };
|
||||
break;
|
||||
case LightSystemOpaque128:
|
||||
rgb_color = { 243, 243, 243, 128 };
|
||||
break;
|
||||
case DarkSystemBackground:
|
||||
rgb_color = { 25, 26, 26, 0 };
|
||||
break;
|
||||
case DarkSystemText:
|
||||
rgb_color = { 25, 26, 26, 0 };
|
||||
break;
|
||||
case Opaque:
|
||||
rgb_color = { 0, 0, 0, 255 };
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_r = rgb_color.r;
|
||||
m_g = rgb_color.g;
|
||||
m_b = rgb_color.b;
|
||||
m_opacity = rgb_color.o;
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
806
libs/libg/src/Context.cpp
Normal file
806
libs/libg/src/Context.cpp
Normal file
@@ -0,0 +1,806 @@
|
||||
#include <algorithm>
|
||||
#include <libfoundation/Math.h>
|
||||
#include <libfoundation/Memory.h>
|
||||
#include <libg/Context.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
Context::Context(PixelBitmap& bitmap)
|
||||
: m_bitmap(bitmap)
|
||||
, m_origin_clip(0, 0, bitmap.width(), bitmap.height())
|
||||
, m_clip(0, 0, bitmap.width(), bitmap.height())
|
||||
, m_color()
|
||||
{
|
||||
}
|
||||
|
||||
void Context::add_clip(const Rect& rect)
|
||||
{
|
||||
auto r = rect;
|
||||
r.offset_by(m_draw_offset);
|
||||
m_clip.intersect(r);
|
||||
}
|
||||
|
||||
void Context::reset_clip()
|
||||
{
|
||||
m_clip = m_origin_clip;
|
||||
}
|
||||
|
||||
void Context::set(const Point<int>& start, const PixelBitmap& bitmap)
|
||||
{
|
||||
Rect draw_bounds(start.x() + m_draw_offset.x(), start.y() + m_draw_offset.y(), bitmap.width(), bitmap.height());
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int offset_x = -start.x() - m_draw_offset.x() + m_bitmap_offset.x();
|
||||
int offset_y = -start.y() - m_draw_offset.y() + m_bitmap_offset.y();
|
||||
int bitmap_x = min_x + offset_x;
|
||||
int bitmap_y = min_y + offset_y;
|
||||
int len_x = max_x - min_x + 1;
|
||||
for (int y = min_y; y <= max_y; y++, bitmap_y++) {
|
||||
LFoundation::fast_copy((uint32_t*)&m_bitmap[y][min_x], (uint32_t*)&bitmap[bitmap_y][bitmap_x], len_x);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::set_with_bounds(const Rect& rect, const PixelBitmap& bitmap)
|
||||
{
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int offset_x = -rect.min_x() - m_draw_offset.x();
|
||||
int offset_y = -rect.min_y() - m_draw_offset.y();
|
||||
int bitmap_x = min_x + offset_x + m_bitmap_offset.x();
|
||||
int bitmap_y = min_y + offset_y + m_bitmap_offset.y();
|
||||
int len_x = max_x - min_x + 1;
|
||||
for (int y = min_y; y <= max_y; y++, bitmap_y++) {
|
||||
LFoundation::fast_copy((uint32_t*)&m_bitmap[y][min_x], (uint32_t*)&bitmap[bitmap_y][bitmap_x], len_x);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::draw(const Point<int>& start, const PixelBitmap& bitmap)
|
||||
{
|
||||
if (!bitmap.has_alpha_channel()) {
|
||||
set(start, bitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
Rect draw_bounds(start.x() + m_draw_offset.x(), start.y() + m_draw_offset.y(), bitmap.width(), bitmap.height());
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int offset_x = -start.x() - m_draw_offset.x() + m_bitmap_offset.x();
|
||||
int offset_y = -start.y() - m_draw_offset.y() + m_bitmap_offset.y();
|
||||
int bitmap_y = min_y + offset_y;
|
||||
for (int y = min_y; y <= max_y; y++, bitmap_y++) {
|
||||
int bitmap_x = min_x + offset_x;
|
||||
for (int x = min_x; x <= max_x; x++, bitmap_x++) {
|
||||
m_bitmap[y][x].mix_with(bitmap[bitmap_y][bitmap_x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::draw_with_bounds(const Rect& rect, const PixelBitmap& bitmap)
|
||||
{
|
||||
if (!bitmap.has_alpha_channel()) {
|
||||
set_with_bounds(rect, bitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int offset_x = -rect.min_x() - m_draw_offset.x() + m_bitmap_offset.x();
|
||||
int offset_y = -rect.min_y() - m_draw_offset.y() + m_bitmap_offset.y();
|
||||
int bitmap_y = min_y + offset_y;
|
||||
for (int y = min_y; y <= max_y; y++, bitmap_y++) {
|
||||
int bitmap_x = min_x + offset_x;
|
||||
for (int x = min_x; x <= max_x; x++, bitmap_x++) {
|
||||
m_bitmap[y][x].mix_with(bitmap[bitmap_y][bitmap_x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::draw(const Point<int>& start, const Glyph& bitmap)
|
||||
{
|
||||
if (!bitmap.data<uint8_t>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect draw_bounds(start.x() + m_draw_offset.x() + bitmap.left(), start.y() + m_draw_offset.y() + bitmap.top(), bitmap.width(), bitmap.height());
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto color = fill_color();
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int offset_x = -start.x() - m_draw_offset.x() - bitmap.left();
|
||||
int offset_y = -start.y() - m_draw_offset.y() - bitmap.top();
|
||||
int bitmap_y = min_y + offset_y;
|
||||
|
||||
switch (bitmap.type()) {
|
||||
case Glyph::Type::PlainBitmap:
|
||||
for (int y = min_y; y <= max_y; y++, bitmap_y++) {
|
||||
int bitmap_x = min_x + offset_x;
|
||||
for (int x = min_x; x <= max_x; x++, bitmap_x++) {
|
||||
if (bitmap.bit_at(bitmap_x, bitmap_y)) {
|
||||
m_bitmap[y][x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
case Glyph::Type::FreeType:
|
||||
for (int y = min_y; y <= max_y; y++, bitmap_y++) {
|
||||
int bitmap_x = min_x + offset_x;
|
||||
for (int x = min_x; x <= max_x; x++, bitmap_x++) {
|
||||
color.set_alpha((fill_color().alpha() * bitmap.alpha_at(bitmap_x, bitmap_y)) / 255);
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
}
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::flatten]] void Context::draw_rounded(const Point<int>& start, const PixelBitmap& bitmap, const CornerMask& mask)
|
||||
{
|
||||
Rect rect(start.x(), start.y(), bitmap.width(), bitmap.height());
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t radius = std::min(mask.radius(), rect.height() / 2);
|
||||
radius = std::min(radius, rect.width() / 2);
|
||||
|
||||
size_t top_radius = radius;
|
||||
size_t bottom_radius = radius;
|
||||
if (!mask.top_rounded()) {
|
||||
top_radius = 0;
|
||||
}
|
||||
if (!mask.bottom_rounded()) {
|
||||
bottom_radius = 0;
|
||||
}
|
||||
|
||||
int width = rect.width();
|
||||
int top_rwidth = rect.width() - 2 * top_radius;
|
||||
int bottom_rwidth = rect.width() - 2 * bottom_radius;
|
||||
int rheight = rect.height() - top_radius - bottom_radius;
|
||||
|
||||
int min_x = rect.min_x();
|
||||
int min_y = rect.min_y();
|
||||
int top_min_rx = rect.min_x() + top_radius;
|
||||
int top_max_rx = rect.max_x() - top_radius;
|
||||
int bottom_min_rx = rect.min_x() + bottom_radius;
|
||||
int bottom_max_rx = rect.max_x() - bottom_radius;
|
||||
int min_ry = rect.min_y() + top_radius;
|
||||
int max_ry = rect.max_y() - bottom_radius;
|
||||
int bitmap_bottom_x_offset = rect.width() - bottom_radius;
|
||||
int bitmap_bottom_y_offset = rect.height() - bottom_radius;
|
||||
|
||||
m_bitmap_offset = { (int)top_radius, 0 };
|
||||
draw_with_bounds(LG::Rect(top_min_rx, min_y, top_rwidth, top_radius), bitmap);
|
||||
m_bitmap_offset = { 0, (int)top_radius };
|
||||
draw_with_bounds(LG::Rect(min_x, min_ry, width, rheight), bitmap);
|
||||
m_bitmap_offset = { (int)bottom_radius, bitmap_bottom_y_offset };
|
||||
draw_with_bounds(LG::Rect(bottom_min_rx, max_ry + 1, bottom_rwidth, bottom_radius), bitmap);
|
||||
|
||||
auto add_instant_clip = [&](int x, int y, size_t width, size_t height) {
|
||||
add_clip(LG::Rect(x, y, width, height));
|
||||
};
|
||||
|
||||
auto orig_clip = m_clip;
|
||||
auto reset_instant_clip = [&]() {
|
||||
m_clip = orig_clip;
|
||||
};
|
||||
|
||||
int bitmap_bottom_x_circle_offset = rect.width() - bottom_radius - bottom_radius - 1;
|
||||
int bitmap_bottom_y_circle_offset = rect.height() - bottom_radius - bottom_radius - 1;
|
||||
add_instant_clip(min_x, min_y, top_radius, top_radius);
|
||||
m_bitmap_offset = { 0, 0 };
|
||||
draw_rounded_helper({ top_min_rx, min_ry }, top_radius, bitmap);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(top_max_rx + 1, min_y, top_radius, top_radius);
|
||||
m_bitmap_offset = { bitmap_bottom_x_circle_offset, 0 };
|
||||
draw_rounded_helper({ top_max_rx, min_ry }, top_radius, bitmap);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(min_x, max_ry + 1, bottom_radius, bottom_radius);
|
||||
m_bitmap_offset = { 0, bitmap_bottom_y_circle_offset };
|
||||
draw_rounded_helper({ bottom_min_rx, max_ry }, bottom_radius, bitmap);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(bottom_max_rx + 1, max_ry + 1, bottom_radius, bottom_radius);
|
||||
m_bitmap_offset = { bitmap_bottom_x_circle_offset, bitmap_bottom_y_circle_offset };
|
||||
draw_rounded_helper({ bottom_max_rx, max_ry }, bottom_radius, bitmap);
|
||||
reset_instant_clip();
|
||||
m_bitmap_offset = { 0, 0 };
|
||||
}
|
||||
|
||||
void Context::mix(const Rect& rect)
|
||||
{
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
draw_bounds.intersect(m_clip);
|
||||
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
const auto& color = fill_color();
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::fill(const Rect& rect)
|
||||
{
|
||||
if (fill_color().is_opaque()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fill_color().alpha() != 255) {
|
||||
mix(rect);
|
||||
return;
|
||||
}
|
||||
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
draw_bounds.intersect(m_clip);
|
||||
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto color = fill_color().u32();
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int len_x = max_x - min_x + 1;
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
LFoundation::fast_set((uint32_t*)&m_bitmap[y][min_x], color, len_x);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::draw_rounded_helper(const Point<int>& start, size_t radius, const PixelBitmap& bitmap)
|
||||
{
|
||||
if (!radius) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto center = start;
|
||||
center.offset_by(m_draw_offset);
|
||||
auto draw_bounds = Rect(center.x() - radius, center.y() - radius, radius * 2 + 1, radius * 2 + 1);
|
||||
draw_bounds.intersect(m_clip);
|
||||
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int offset_x = -(start.x() - radius) - m_draw_offset.x() + m_bitmap_offset.x();
|
||||
int offset_y = -(start.y() - radius) - m_draw_offset.y() + m_bitmap_offset.y();
|
||||
int bitmap_y = min_y + offset_y;
|
||||
size_t radius2 = radius * radius;
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
int bitmap_x = min_x + offset_x;
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
size_t x2 = (x - center.x()) * (x - center.x());
|
||||
size_t y2 = (y - center.y()) * (y - center.y());
|
||||
size_t dist = x2 + y2;
|
||||
if (dist <= radius2) {
|
||||
m_bitmap[y][x].mix_with(bitmap[bitmap_y][bitmap_x]);
|
||||
} else {
|
||||
auto color = bitmap[bitmap_y][bitmap_x];
|
||||
float fdist = 0.6 - 0.4 * (LFoundation::fast_sqrt((float)(dist)) - radius);
|
||||
fdist = std::max(std::min(fdist, 1.0f), 0.0f);
|
||||
int alpha = int(color.alpha() * fdist);
|
||||
color.set_alpha(alpha);
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::fill_rounded_helper(const Point<int>& start, size_t radius)
|
||||
{
|
||||
if (!radius) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto center = start;
|
||||
center.offset_by(m_draw_offset);
|
||||
auto draw_bounds = Rect(center.x() - radius, center.y() - radius, radius * 2 + 1, radius * 2 + 1);
|
||||
draw_bounds.intersect(m_clip);
|
||||
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto color = fill_color();
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
size_t radius2 = radius * radius;
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
size_t x2 = (x - center.x()) * (x - center.x());
|
||||
size_t y2 = (y - center.y()) * (y - center.y());
|
||||
size_t dist = x2 + y2;
|
||||
if (dist <= radius2) {
|
||||
m_bitmap[y][x].mix_with(fill_color());
|
||||
} else {
|
||||
float fdist = 0.6 - 0.4 * (LFoundation::fast_sqrt((float)(dist)) - radius);
|
||||
fdist = std::max(std::min(fdist, 1.0f), 0.0f);
|
||||
int alpha = int(fill_color().alpha() * fdist);
|
||||
color.set_alpha(alpha);
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::shadow_rounded_helper(const Point<int>& start, size_t radius, const Shading& shading)
|
||||
{
|
||||
if (!radius) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto center = start;
|
||||
center.offset_by(m_draw_offset);
|
||||
uint32_t hw = shading.spread() * 2 + radius * 2 + 1;
|
||||
auto draw_bounds = Rect(center.x() - radius - shading.spread(), center.y() - radius - shading.spread(), hw, hw);
|
||||
draw_bounds.intersect(m_clip);
|
||||
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto color = fill_color();
|
||||
float shading_spread = shading.spread();
|
||||
float std_alpha = color.alpha();
|
||||
std_alpha /= (shading_spread - 1);
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int radius2 = radius * radius;
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
int x2 = (x - center.x()) * (x - center.x());
|
||||
int y2 = (y - center.y()) * (y - center.y());
|
||||
int dist = x2 + y2;
|
||||
if (dist > radius2) {
|
||||
float fdist = LFoundation::fast_sqrt((float)(dist)) - radius;
|
||||
if (fdist > 0.1) {
|
||||
fdist = shading_spread - fdist;
|
||||
fdist = std::max(fdist, 0.0f);
|
||||
int alpha = std_alpha * fdist;
|
||||
color.set_alpha(alpha);
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::flatten]] void Context::fill_rounded(const Rect& rect, const CornerMask& mask)
|
||||
{
|
||||
if (mask.radius() == 0) {
|
||||
return fill(rect);
|
||||
}
|
||||
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
draw_bounds.intersect(m_clip);
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t radius = std::min(mask.radius(), rect.height() / 2);
|
||||
radius = std::min(radius, rect.width() / 2);
|
||||
|
||||
size_t top_radius = radius;
|
||||
size_t bottom_radius = radius;
|
||||
if (!mask.top_rounded()) {
|
||||
top_radius = 0;
|
||||
}
|
||||
if (!mask.bottom_rounded()) {
|
||||
bottom_radius = 0;
|
||||
}
|
||||
|
||||
int width = rect.width();
|
||||
int top_rwidth = rect.width() - 2 * top_radius;
|
||||
int bottom_rwidth = rect.width() - 2 * bottom_radius;
|
||||
int rheight = rect.height() - top_radius - bottom_radius;
|
||||
|
||||
int min_x = rect.min_x();
|
||||
int min_y = rect.min_y();
|
||||
int top_min_rx = rect.min_x() + top_radius;
|
||||
int top_max_rx = rect.max_x() - top_radius;
|
||||
int bottom_min_rx = rect.min_x() + bottom_radius;
|
||||
int bottom_max_rx = rect.max_x() - bottom_radius;
|
||||
int min_ry = rect.min_y() + top_radius;
|
||||
int max_ry = rect.max_y() - bottom_radius;
|
||||
|
||||
fill(LG::Rect(top_min_rx, min_y, top_rwidth, top_radius));
|
||||
fill(LG::Rect(min_x, min_ry, width, rheight));
|
||||
fill(LG::Rect(bottom_min_rx, max_ry + 1, bottom_rwidth, bottom_radius));
|
||||
|
||||
auto add_instant_clip = [&](int x, int y, size_t width, size_t height) {
|
||||
add_clip(LG::Rect(x, y, width, height));
|
||||
};
|
||||
|
||||
auto orig_clip = m_clip;
|
||||
auto reset_instant_clip = [&]() {
|
||||
m_clip = orig_clip;
|
||||
};
|
||||
|
||||
add_instant_clip(min_x, min_y, top_radius, top_radius);
|
||||
fill_rounded_helper({ top_min_rx, min_ry }, top_radius);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(top_max_rx + 1, min_y, top_radius, top_radius);
|
||||
fill_rounded_helper({ top_max_rx, min_ry }, top_radius);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(min_x, max_ry + 1, bottom_radius, bottom_radius);
|
||||
fill_rounded_helper({ bottom_min_rx, max_ry }, bottom_radius);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(bottom_max_rx + 1, max_ry + 1, bottom_radius, bottom_radius);
|
||||
fill_rounded_helper({ bottom_max_rx, max_ry }, bottom_radius);
|
||||
reset_instant_clip();
|
||||
}
|
||||
|
||||
void Context::draw_shading(const Rect& rect, const Shading& shading)
|
||||
{
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.offset_by(m_draw_offset);
|
||||
auto orig_bounds = draw_bounds;
|
||||
draw_bounds.intersect(m_clip);
|
||||
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
auto color = fill_color();
|
||||
|
||||
int alpha_diff = color.alpha() - shading.final_alpha();
|
||||
int step, skipped_steps, end_x;
|
||||
|
||||
switch (shading.type()) {
|
||||
case Shading::Type::TopToBottom:
|
||||
step = alpha_diff / (orig_bounds.height());
|
||||
skipped_steps = min_y - orig_bounds.min_y();
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::BottomToTop:
|
||||
step = alpha_diff / (orig_bounds.height());
|
||||
skipped_steps = orig_bounds.max_y() - max_y;
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
|
||||
for (int y = max_y; y >= min_y; y--) {
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::LeftToRight:
|
||||
step = alpha_diff / orig_bounds.width();
|
||||
skipped_steps = min_x - orig_bounds.min_x();
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
|
||||
for (int x = min_x; x <= max_x; x++) {
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::RightToLeft:
|
||||
step = alpha_diff / orig_bounds.width();
|
||||
skipped_steps = orig_bounds.max_x() - max_x;
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
|
||||
for (int x = max_x; x >= min_x; x--) {
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
m_bitmap[y][x].mix_with(color);
|
||||
}
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::Deg45:
|
||||
step = alpha_diff / (orig_bounds.height() - 1);
|
||||
skipped_steps = orig_bounds.max_y() - max_y + min_x - orig_bounds.min_x();
|
||||
if (skipped_steps >= orig_bounds.height()) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
end_x = std::min(min_x + (int)orig_bounds.height() - skipped_steps, max_x);
|
||||
|
||||
for (int y = max_y; y >= min_y; y--) {
|
||||
auto cur_color = color;
|
||||
for (int x = min_x; x <= end_x; x++) {
|
||||
m_bitmap[y][x].mix_with(cur_color);
|
||||
cur_color.set_alpha(cur_color.alpha() - step);
|
||||
}
|
||||
end_x--;
|
||||
if (!end_x) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::Deg315:
|
||||
step = alpha_diff / (orig_bounds.height() - 1);
|
||||
skipped_steps = min_y - orig_bounds.min_y() + min_x - orig_bounds.min_x();
|
||||
if (skipped_steps >= orig_bounds.height()) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
end_x = std::min(min_x + (int)orig_bounds.height() - skipped_steps, max_x);
|
||||
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
auto cur_color = color;
|
||||
for (int x = min_x; x <= end_x; x++) {
|
||||
m_bitmap[y][x].mix_with(cur_color);
|
||||
cur_color.set_alpha(cur_color.alpha() - step);
|
||||
}
|
||||
end_x--;
|
||||
if (!end_x) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::Deg135:
|
||||
step = alpha_diff / (orig_bounds.height() - 1);
|
||||
skipped_steps = orig_bounds.max_y() - max_y + orig_bounds.max_x() - max_x;
|
||||
if (skipped_steps >= orig_bounds.height()) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
end_x = std::max(max_x - ((int)orig_bounds.height() - skipped_steps), min_x);
|
||||
|
||||
for (int y = max_y; y >= min_y; y--) {
|
||||
auto cur_color = color;
|
||||
for (int x = max_x; x >= end_x; x--) {
|
||||
m_bitmap[y][x].mix_with(cur_color);
|
||||
cur_color.set_alpha(cur_color.alpha() - step);
|
||||
}
|
||||
end_x++;
|
||||
if (end_x == max_x) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
case Shading::Type::Deg225:
|
||||
step = alpha_diff / (orig_bounds.height() - 1);
|
||||
skipped_steps = min_y - orig_bounds.min_y() + orig_bounds.max_x() - max_x;
|
||||
if (skipped_steps >= orig_bounds.height()) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - skipped_steps * step);
|
||||
end_x = std::max(max_x - ((int)orig_bounds.height() - skipped_steps), min_x);
|
||||
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
auto cur_color = color;
|
||||
for (int x = max_x; x >= end_x; x--) {
|
||||
m_bitmap[y][x].mix_with(cur_color);
|
||||
cur_color.set_alpha(cur_color.alpha() - step);
|
||||
}
|
||||
end_x++;
|
||||
if (end_x == max_x) {
|
||||
return;
|
||||
}
|
||||
|
||||
color.set_alpha(color.alpha() - step);
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Context::draw_box_shading(const Rect& rect, const Shading& shading, const CornerMask& mask)
|
||||
{
|
||||
size_t top_radius = mask.radius();
|
||||
size_t bottom_radius = mask.radius();
|
||||
if (!mask.top_rounded()) {
|
||||
top_radius = 0;
|
||||
}
|
||||
if (!mask.bottom_rounded()) {
|
||||
bottom_radius = 0;
|
||||
}
|
||||
|
||||
int shading_spread = shading.spread();
|
||||
if (shading_spread == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rwidth = rect.width() - 2 * top_radius;
|
||||
int rheight = rect.height() - top_radius - bottom_radius;
|
||||
|
||||
int top_min_rx = rect.min_x() + top_radius;
|
||||
int top_min_ry = rect.min_y() + top_radius;
|
||||
int bottom_min_rx = rect.min_x() + bottom_radius;
|
||||
int bottom_max_ry = rect.max_y() - bottom_radius;
|
||||
int top_max_rx = rect.max_x() - top_radius;
|
||||
int bottom_max_rx = rect.max_x() - bottom_radius;
|
||||
int min_shading_x = rect.min_x() - shading_spread;
|
||||
int max_shading_x = rect.max_x() + 1;
|
||||
int min_shading_y = rect.min_y() - shading_spread;
|
||||
int max_shading_y = rect.max_y() + 1;
|
||||
|
||||
draw_shading(LG::Rect(top_min_rx, min_shading_y, rwidth - 1, shading_spread), LG::Shading(LG::Shading::Type::BottomToTop, shading.final_alpha()));
|
||||
draw_shading(LG::Rect(top_min_rx, max_shading_y, rwidth - 1, shading_spread), LG::Shading(LG::Shading::Type::TopToBottom, shading.final_alpha()));
|
||||
draw_shading(LG::Rect(min_shading_x, top_min_ry, shading_spread, rheight - 1), LG::Shading(LG::Shading::Type::RightToLeft, shading.final_alpha()));
|
||||
draw_shading(LG::Rect(max_shading_x, top_min_ry, shading_spread, rheight - 1), LG::Shading(LG::Shading::Type::LeftToRight, shading.final_alpha()));
|
||||
|
||||
auto add_instant_clip = [&](int x, int y, size_t width, size_t height) {
|
||||
add_clip(LG::Rect(x, y, width, height));
|
||||
};
|
||||
|
||||
auto orig_clip = m_clip;
|
||||
auto reset_instant_clip = [&]() {
|
||||
m_clip = orig_clip;
|
||||
};
|
||||
|
||||
int top_shading_dims = top_radius + shading_spread;
|
||||
int bottom_shading_dims = bottom_radius + shading_spread;
|
||||
|
||||
if (mask.top_rounded()) {
|
||||
add_instant_clip(min_shading_x, min_shading_y, top_shading_dims, top_shading_dims);
|
||||
shadow_rounded_helper({ top_min_rx, top_min_ry }, top_radius, shading);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(top_max_rx, min_shading_y, top_shading_dims, top_shading_dims);
|
||||
shadow_rounded_helper({ top_max_rx, top_min_ry }, top_radius, shading);
|
||||
reset_instant_clip();
|
||||
} else {
|
||||
draw_shading(LG::Rect(min_shading_x, min_shading_y, shading_spread, shading_spread), LG::Shading(LG::Shading::Type::Deg135, 0));
|
||||
draw_shading(LG::Rect(top_max_rx, min_shading_y, shading_spread, shading_spread), LG::Shading(LG::Shading::Type::Deg45, 0));
|
||||
}
|
||||
|
||||
if (mask.bottom_rounded()) {
|
||||
add_instant_clip(min_shading_x, bottom_max_ry, bottom_shading_dims, bottom_shading_dims);
|
||||
shadow_rounded_helper({ bottom_min_rx, bottom_max_ry }, bottom_radius, shading);
|
||||
reset_instant_clip();
|
||||
|
||||
add_instant_clip(bottom_max_rx, bottom_max_ry, bottom_shading_dims, bottom_shading_dims);
|
||||
shadow_rounded_helper({ bottom_max_rx, bottom_max_ry }, bottom_radius, shading);
|
||||
reset_instant_clip();
|
||||
} else {
|
||||
draw_shading(LG::Rect(min_shading_x, bottom_max_ry, shading_spread, shading_spread), LG::Shading(LG::Shading::Type::Deg225, 0));
|
||||
draw_shading(LG::Rect(bottom_max_rx, bottom_max_ry, shading_spread, shading_spread), LG::Shading(LG::Shading::Type::Deg315, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void Context::add_ellipse(const Rect& rect)
|
||||
{
|
||||
int rx = rect.width() / 2;
|
||||
int ry = rect.height() / 2;
|
||||
int xc = rect.mid_x();
|
||||
int yc = rect.mid_y();
|
||||
|
||||
double dx, dy, d1, d2, x, y;
|
||||
double tmp_d1, tmp_d2;
|
||||
x = 0;
|
||||
y = ry;
|
||||
|
||||
d1 = (ry * ry) - (rx * rx * ry) + (0.25 * rx * rx);
|
||||
dx = 2 * ry * ry * x;
|
||||
dy = 2 * rx * rx * y;
|
||||
|
||||
while (dx < dy) {
|
||||
m_bitmap[y + yc][(int)x + xc] = fill_color();
|
||||
m_bitmap[y + yc][(int)-x + xc] = fill_color();
|
||||
m_bitmap[-y + yc][(int)x + xc] = fill_color();
|
||||
m_bitmap[-y + yc][(int)-x + xc] = fill_color();
|
||||
|
||||
x++;
|
||||
dx += 2 * ry * ry;
|
||||
tmp_d1 = d1;
|
||||
d1 += ry * ry + dx;
|
||||
if (tmp_d1 >= 0) {
|
||||
y--;
|
||||
dy -= 2 * rx * rx;
|
||||
d1 -= dy;
|
||||
}
|
||||
}
|
||||
|
||||
d2 = ((ry * ry) * ((x + 0.5) * (x + 0.5))) + ((rx * rx) * ((y - 1) * (y - 1))) - (rx * rx * ry * ry);
|
||||
|
||||
while (y >= 0) {
|
||||
m_bitmap[y + yc][(int)x + xc] = fill_color();
|
||||
m_bitmap[y + yc][(int)-x + xc] = fill_color();
|
||||
m_bitmap[-y + yc][(int)x + xc] = fill_color();
|
||||
m_bitmap[-y + yc][(int)-x + xc] = fill_color();
|
||||
|
||||
y--;
|
||||
dy -= 2 * rx * rx;
|
||||
tmp_d2 = d2;
|
||||
d2 += rx * rx - dy;
|
||||
if (tmp_d2 <= 0) {
|
||||
x++;
|
||||
dx += 2 * ry * ry;
|
||||
d2 += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
214
libs/libg/src/Font.cpp
Normal file
214
libs/libg/src/Font.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include <cassert>
|
||||
#include <fcntl.h>
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Font.h>
|
||||
#include <new>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
/* SerenityOS font header */
|
||||
struct [[gnu::packed]] FontFileHeader {
|
||||
char magic[4];
|
||||
uint8_t glyph_width;
|
||||
uint8_t glyph_height;
|
||||
uint16_t range_mask_size;
|
||||
uint8_t is_variable_width;
|
||||
uint8_t glyph_spacing;
|
||||
uint8_t baseline;
|
||||
uint8_t mean_line;
|
||||
uint8_t presentation_size;
|
||||
uint16_t weight;
|
||||
uint8_t slope;
|
||||
char name[32];
|
||||
char family[32];
|
||||
};
|
||||
|
||||
Font& Font::system_font(int size)
|
||||
{
|
||||
static Font* s_system_font_ptr[SystemMaxSize + 1];
|
||||
if (!s_system_font_ptr[size]) {
|
||||
s_system_font_ptr[size] = Font::load_from_file_ttf("/res/fonts/system.font/truetype/regular.ttf", size);
|
||||
}
|
||||
return *s_system_font_ptr[size];
|
||||
}
|
||||
|
||||
Font& Font::system_bold_font(int size)
|
||||
{
|
||||
static Font* s_system_font_ptr[SystemMaxSize + 1];
|
||||
if (!s_system_font_ptr[size]) {
|
||||
s_system_font_ptr[size] = Font::load_from_file_ttf("/res/fonts/system.font/truetype/bold.ttf", size);
|
||||
}
|
||||
return *s_system_font_ptr[size];
|
||||
}
|
||||
|
||||
Font::Font(const SerenityOSFontDesc& desc, size_t font_size)
|
||||
: m_font_type(FontType::SerenityOS)
|
||||
, m_font_size(font_size)
|
||||
, m_const_width(desc.dynamic_width ? 0 : desc.width)
|
||||
{
|
||||
// TODO: SerenityOS bitmap fonts does not requier a cache.
|
||||
m_font_desc.serenity = desc;
|
||||
m_font_cache = new FontCacher();
|
||||
}
|
||||
|
||||
Font::Font(const FreeTypeFontDesc& desc, size_t font_size)
|
||||
: m_font_type(FontType::FreeType)
|
||||
, m_font_size(font_size)
|
||||
, m_const_width(0)
|
||||
{
|
||||
m_font_desc.free_type = desc;
|
||||
m_font_cache = new FontCacher();
|
||||
}
|
||||
|
||||
Font* Font::load_from_file(const char* path)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stat_t stat;
|
||||
fstat(fd, &stat);
|
||||
|
||||
uint8_t* ptr = (uint8_t*)mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
auto* res = Font::load_from_mem(ptr);
|
||||
|
||||
close(fd);
|
||||
return res;
|
||||
}
|
||||
|
||||
Font* Font::load_from_mem(uint8_t* font_data)
|
||||
{
|
||||
if (!font_data) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FontFileHeader& header = *(FontFileHeader*)font_data;
|
||||
|
||||
if (memcmp((uint8_t*)header.magic, (const uint8_t*)"!Fnt", 4)) {
|
||||
Logger::debug << "Font unsupported" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
uint8_t* range_mask = const_cast<uint8_t*>(font_data + sizeof(FontFileHeader));
|
||||
for (size_t i = 0; i < header.range_mask_size; ++i)
|
||||
count += 256 * __builtin_popcount(range_mask[i]);
|
||||
|
||||
size_t bytes_per_glyph = sizeof(uint32_t) * header.glyph_height;
|
||||
|
||||
uint32_t* raw_data = (uint32_t*)(range_mask + header.range_mask_size);
|
||||
uint8_t* width_data = nullptr;
|
||||
if (header.is_variable_width) {
|
||||
width_data = (uint8_t*)((uint8_t*)(raw_data) + count * bytes_per_glyph);
|
||||
}
|
||||
|
||||
SerenityOSFontDesc desc = {
|
||||
.raw_data = raw_data,
|
||||
.width_data = width_data,
|
||||
.height = header.glyph_height,
|
||||
.width = header.glyph_width,
|
||||
.spacing = header.glyph_spacing,
|
||||
.dynamic_width = (bool)header.is_variable_width,
|
||||
.count = count,
|
||||
};
|
||||
return new Font(desc, header.glyph_height);
|
||||
}
|
||||
|
||||
Font* Font::load_from_file_ttf(const char* path, size_t size)
|
||||
{
|
||||
FT_Library library;
|
||||
FT_Face face;
|
||||
|
||||
int error = FT_Init_FreeType(&library);
|
||||
if (error) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
error = FT_New_Face(library, path, 0, &face);
|
||||
if (error) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
error = FT_Set_Pixel_Sizes(face, 0, size);
|
||||
if (error) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FreeTypeFontDesc desc = {
|
||||
.face = face,
|
||||
.height = size,
|
||||
};
|
||||
return new Font(desc, size);
|
||||
}
|
||||
|
||||
Glyph Font::SerenityOSFontDesc::load_glyph(size_t ch) const
|
||||
{
|
||||
// Check if ch is present, to not go out-of-bounds.
|
||||
if (ch >= count) {
|
||||
assert(((int)'?') < count);
|
||||
ch = '?';
|
||||
}
|
||||
|
||||
size_t w = dynamic_width ? width_data[ch] : width;
|
||||
GlyphMetrics metrics = {
|
||||
.width = (uint8_t)w,
|
||||
.height = (uint8_t)height,
|
||||
.top_bearing = (uint8_t)height,
|
||||
.left_bearing = 0,
|
||||
.baseline = 0,
|
||||
.advance = (uint8_t)(w + spacing),
|
||||
.font_size = (uint16_t)height
|
||||
};
|
||||
return Glyph(&raw_data[ch * height], metrics, Glyph::ConstDataMarker {});
|
||||
}
|
||||
|
||||
Glyph Font::FreeTypeFontDesc::load_glyph(size_t ch) const
|
||||
{
|
||||
if (ch == ' ') {
|
||||
size_t width = height / 2;
|
||||
GlyphMetrics metrics = {
|
||||
.width = (uint8_t)width,
|
||||
.height = (uint8_t)height,
|
||||
.top_bearing = 0,
|
||||
.left_bearing = 0,
|
||||
.baseline = 0,
|
||||
.advance = (uint8_t)width,
|
||||
.font_size = (uint16_t)height
|
||||
};
|
||||
return Glyph(nullptr, metrics, Glyph::ConstDataMarker {});
|
||||
}
|
||||
|
||||
FT_Load_Char(face, ch, FT_LOAD_RENDER);
|
||||
FT_GlyphSlot glyph = face->glyph;
|
||||
FT_Bitmap bitmap = glyph->bitmap;
|
||||
|
||||
GlyphMetrics metrics = {
|
||||
.width = (uint8_t)bitmap.width,
|
||||
.height = (uint8_t)bitmap.rows,
|
||||
.top_bearing = (uint8_t)glyph->bitmap_top,
|
||||
.left_bearing = (uint8_t)glyph->bitmap_left,
|
||||
.baseline = 2,
|
||||
.advance = (uint8_t)(glyph->advance.x >> 6),
|
||||
.font_size = (uint16_t)height
|
||||
};
|
||||
return Glyph(bitmap.buffer, metrics);
|
||||
}
|
||||
|
||||
Glyph Font::load_glyph(size_t ch) const
|
||||
{
|
||||
switch (m_font_type) {
|
||||
case FontType::SerenityOS:
|
||||
return m_font_desc.serenity.load_glyph(ch);
|
||||
case FontType::FreeType:
|
||||
return m_font_desc.free_type.load_glyph(ch);
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
301
libs/libg/src/ImageLoaders/PNGLoader.cpp
Normal file
301
libs/libg/src/ImageLoaders/PNGLoader.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libfoundation/compress/puff.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <memory>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// #define PNGLOADER_DEGUG
|
||||
|
||||
namespace LG {
|
||||
namespace PNG {
|
||||
|
||||
constexpr int png_header_size = 8;
|
||||
static uint8_t png_header[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
|
||||
PixelBitmap PNGLoader::load_from_file(const std::string& path)
|
||||
{
|
||||
int fd = open(path.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
Logger::debug << "PNGLoader: cant open" << std::endl;
|
||||
return PixelBitmap();
|
||||
}
|
||||
|
||||
stat_t stat;
|
||||
fstat(fd, &stat);
|
||||
|
||||
uint8_t* ptr = (uint8_t*)mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
PixelBitmap bitmap = load_from_mem(ptr);
|
||||
|
||||
munmap(ptr, stat.st_size);
|
||||
close(fd);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
bool PNGLoader::check_header(const uint8_t* ptr) const
|
||||
{
|
||||
return memcmp(ptr, (uint8_t*)png_header, png_header_size) == 0;
|
||||
}
|
||||
|
||||
void PNGLoader::read_IHDR(ChunkHeader& header, PixelBitmap& bitmap)
|
||||
{
|
||||
streamer().read(m_ihdr_chunk.width);
|
||||
streamer().read(m_ihdr_chunk.height);
|
||||
streamer().read(m_ihdr_chunk.depth);
|
||||
streamer().read(m_ihdr_chunk.color_type);
|
||||
streamer().read(m_ihdr_chunk.compression_method);
|
||||
streamer().read(m_ihdr_chunk.filter_method);
|
||||
streamer().read(m_ihdr_chunk.interlace_method);
|
||||
|
||||
bitmap.resize(m_ihdr_chunk.width, m_ihdr_chunk.height);
|
||||
|
||||
#ifdef PNGLOADER_DEGUG
|
||||
Logger::debug << "IHDR: " << m_ihdr_chunk.width << " " << m_ihdr_chunk.depth << " " << m_ihdr_chunk.compression_method << " " << m_ihdr_chunk.filter_method << " " << m_ihdr_chunk.color_type << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PNGLoader::read_TEXT(ChunkHeader& header, PixelBitmap& bitmap)
|
||||
{
|
||||
streamer().skip(header.len);
|
||||
}
|
||||
|
||||
void PNGLoader::read_PHYS(ChunkHeader& header, PixelBitmap& bitmap)
|
||||
{
|
||||
streamer().skip(header.len);
|
||||
}
|
||||
|
||||
void PNGLoader::read_ORNT(ChunkHeader& header, PixelBitmap& bitmap)
|
||||
{
|
||||
streamer().skip(header.len);
|
||||
}
|
||||
|
||||
void PNGLoader::read_gAMA(ChunkHeader& header, PixelBitmap& bitmap)
|
||||
{
|
||||
streamer().skip(header.len);
|
||||
}
|
||||
|
||||
// TODO: Currently support only comprssion type 0
|
||||
void PNGLoader::read_IDAT(ChunkHeader& header, PixelBitmap& bitmap)
|
||||
{
|
||||
size_t buf_size = m_compressed_data.size();
|
||||
m_compressed_data.resize(buf_size + header.len);
|
||||
memcpy(&m_compressed_data.data()[buf_size], streamer().ptr(), header.len);
|
||||
streamer().skip(header.len);
|
||||
}
|
||||
|
||||
void PNGLoader::process_compressed_data(PixelBitmap& bitmap)
|
||||
{
|
||||
size_t datalen = m_compressed_data.size();
|
||||
size_t destlen = 0;
|
||||
int ret = puff(0, &destlen, m_compressed_data.data() + 2, &datalen);
|
||||
uint8_t* unzipped_data = (uint8_t*)malloc(destlen);
|
||||
puff(unzipped_data, &destlen, m_compressed_data.data() + 2, &datalen);
|
||||
DataStreamer local_streamer(unzipped_data);
|
||||
m_scanline_keeper.init(unzipped_data);
|
||||
|
||||
if (m_ihdr_chunk.color_type == 2) {
|
||||
m_scanline_keeper.set_color_length(3);
|
||||
if (m_ihdr_chunk.depth == 8) {
|
||||
for (int i = 0; i < m_ihdr_chunk.height; i++) {
|
||||
uint8_t scanline_filter;
|
||||
local_streamer.read(scanline_filter);
|
||||
|
||||
if (scanline_filter > 4) {
|
||||
Logger::debug << "Invalid PNG filter: " << scanline_filter << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t len_of_scanline = (m_scanline_keeper.color_length() * m_ihdr_chunk.width * m_ihdr_chunk.depth + 7) / 8;
|
||||
m_scanline_keeper.add(Scanline(scanline_filter, local_streamer.ptr()));
|
||||
local_streamer.skip(len_of_scanline);
|
||||
}
|
||||
}
|
||||
} else if (m_ihdr_chunk.color_type == 6) {
|
||||
m_scanline_keeper.set_color_length(4);
|
||||
if (m_ihdr_chunk.depth == 8) {
|
||||
for (int i = 0; i < m_ihdr_chunk.height; i++) {
|
||||
uint8_t scanline_filter;
|
||||
local_streamer.read(scanline_filter);
|
||||
|
||||
if (scanline_filter > 4) {
|
||||
Logger::debug << "Invalid PNG filter: " << scanline_filter << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t len_of_scanline = (m_scanline_keeper.color_length() * m_ihdr_chunk.width * m_ihdr_chunk.depth + 7) / 8;
|
||||
m_scanline_keeper.add(Scanline(scanline_filter, local_streamer.ptr()));
|
||||
local_streamer.skip(len_of_scanline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unfilter_scanlines();
|
||||
copy_scanlines_to_bitmap(bitmap);
|
||||
m_scanline_keeper.invalidate();
|
||||
}
|
||||
|
||||
uint8_t PNGLoader::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;
|
||||
}
|
||||
|
||||
void PNGLoader::unfilter_scanlines()
|
||||
{
|
||||
for (int i = 0; i < m_ihdr_chunk.height; i++) {
|
||||
if (m_scanline_keeper.scanlines()[i].filter_type() == 1) { // Sub
|
||||
int width = m_ihdr_chunk.width * m_scanline_keeper.color_length();
|
||||
for (int j = 0; j < width; j++) {
|
||||
int prev = 0;
|
||||
if (j >= m_scanline_keeper.color_length()) {
|
||||
prev = m_scanline_keeper.scanlines()[i].data()[j - m_scanline_keeper.color_length()];
|
||||
}
|
||||
m_scanline_keeper.scanlines()[i].data()[j] += prev;
|
||||
}
|
||||
} else if (m_scanline_keeper.scanlines()[i].filter_type() == 2) { // Up
|
||||
int width = m_ihdr_chunk.width * m_scanline_keeper.color_length();
|
||||
for (int j = 0; j < width; j++) {
|
||||
int prev = 0;
|
||||
if (i > 0) {
|
||||
prev = m_scanline_keeper.scanlines()[i - 1].data()[j];
|
||||
}
|
||||
m_scanline_keeper.scanlines()[i].data()[j] += prev;
|
||||
}
|
||||
} else if (m_scanline_keeper.scanlines()[i].filter_type() == 3) { // Average
|
||||
int width = m_ihdr_chunk.width * m_scanline_keeper.color_length();
|
||||
for (int j = 0; j < width; j++) {
|
||||
int prev = 0;
|
||||
int prior = 0;
|
||||
if (i > 0) {
|
||||
prior = m_scanline_keeper.scanlines()[i - 1].data()[j];
|
||||
}
|
||||
if (j >= m_scanline_keeper.color_length()) {
|
||||
prev = m_scanline_keeper.scanlines()[i].data()[j - m_scanline_keeper.color_length()];
|
||||
}
|
||||
m_scanline_keeper.scanlines()[i].data()[j] += (prev + prior) / 2;
|
||||
}
|
||||
} else if (m_scanline_keeper.scanlines()[i].filter_type() == 4) { // Paeth
|
||||
int width = m_ihdr_chunk.width * m_scanline_keeper.color_length();
|
||||
for (int j = 0; j < width; j++) {
|
||||
int a = 0;
|
||||
int b = 0;
|
||||
int c = 0;
|
||||
if (i > 0) {
|
||||
b = m_scanline_keeper.scanlines()[i - 1].data()[j];
|
||||
}
|
||||
if (j >= m_scanline_keeper.color_length()) {
|
||||
a = m_scanline_keeper.scanlines()[i].data()[j - m_scanline_keeper.color_length()];
|
||||
if (i > 0) {
|
||||
c = m_scanline_keeper.scanlines()[i - 1].data()[j - m_scanline_keeper.color_length()];
|
||||
}
|
||||
}
|
||||
m_scanline_keeper.scanlines()[i].data()[j] += paeth_predictor(a, b, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PNGLoader::copy_scanlines_to_bitmap(PixelBitmap& bitmap)
|
||||
{
|
||||
if (m_ihdr_chunk.color_type == 2) {
|
||||
bitmap.set_format(PixelBitmapFormat::RGB);
|
||||
for (int i = 0; i < m_ihdr_chunk.height; i++) {
|
||||
auto& scanline = m_scanline_keeper.scanlines()[i];
|
||||
for (int j = 0, bit = 0; j < m_ihdr_chunk.width; j++) {
|
||||
int r = scanline.data()[bit++];
|
||||
int g = scanline.data()[bit++];
|
||||
int b = scanline.data()[bit++];
|
||||
int alpha = 255;
|
||||
bitmap[i][j] = Color(r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_ihdr_chunk.color_type == 6) {
|
||||
bitmap.set_format(PixelBitmapFormat::RGBA);
|
||||
for (int i = 0; i < m_ihdr_chunk.height; i++) {
|
||||
auto& scanline = m_scanline_keeper.scanlines()[i];
|
||||
for (int j = 0, bit = 0; j < m_ihdr_chunk.width; j++) {
|
||||
int r = scanline.data()[bit++];
|
||||
int g = scanline.data()[bit++];
|
||||
int b = scanline.data()[bit++];
|
||||
int alpha = scanline.data()[bit++];
|
||||
bitmap[i][j] = Color(r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PNGLoader::read_chunk(PixelBitmap& bitmap)
|
||||
{
|
||||
ChunkHeader header;
|
||||
streamer().read(header.len);
|
||||
streamer().read(header.type, 4);
|
||||
|
||||
if (memcmp(header.type, (uint8_t*)"IHDR", 4) == 0) {
|
||||
read_IHDR(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"tEXt", 4) == 0) {
|
||||
read_TEXT(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"zTXt", 4) == 0) {
|
||||
read_TEXT(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"pHYs", 4) == 0) {
|
||||
read_PHYS(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"sRGB", 4) == 0) {
|
||||
read_PHYS(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"eXIf", 4) == 0) {
|
||||
read_PHYS(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"orNT", 4) == 0) {
|
||||
read_ORNT(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"IDAT", 4) == 0) {
|
||||
read_IDAT(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"gAMA", 4) == 0) {
|
||||
read_gAMA(header, bitmap);
|
||||
} else if (memcmp(header.type, (uint8_t*)"IEND", 4) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
Logger::debug << "PNGLoader: Unexpected header type: " << (char*)header.type << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int crc;
|
||||
streamer().read(crc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PNGLoader::proccess_stream(PixelBitmap& bitmap)
|
||||
{
|
||||
int len = 0;
|
||||
while (read_chunk(bitmap)) { }
|
||||
process_compressed_data(bitmap);
|
||||
}
|
||||
|
||||
PixelBitmap PNGLoader::load_from_mem(const uint8_t* ptr)
|
||||
{
|
||||
auto bitmap = PixelBitmap();
|
||||
if (!ptr) {
|
||||
Logger::debug << "PNGLoader: nullptr" << std::endl;
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
if (!check_header(ptr)) {
|
||||
Logger::debug << "PNGLoader: not png" << std::endl;
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
streamer().set(ptr + png_header_size);
|
||||
proccess_stream(bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
} // namespace PNG
|
||||
} // namespace LG
|
||||
57
libs/libg/src/PixelBitmap.cpp
Normal file
57
libs/libg/src/PixelBitmap.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <libg/PixelBitmap.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
PixelBitmap::PixelBitmap(Color* buffer, size_t width, size_t height, PixelBitmapFormat format)
|
||||
: m_data(buffer)
|
||||
, m_bounds(0, 0, width, height)
|
||||
, m_should_free(false)
|
||||
, m_format(format)
|
||||
{
|
||||
}
|
||||
|
||||
PixelBitmap::PixelBitmap(size_t width, size_t height, PixelBitmapFormat format)
|
||||
: m_bounds(0, 0, width, height)
|
||||
, m_data((Color*)malloc(sizeof(Color) * width * height))
|
||||
, m_should_free(true)
|
||||
, m_format(format)
|
||||
{
|
||||
}
|
||||
|
||||
PixelBitmap::PixelBitmap(const PixelBitmap& bitmap)
|
||||
: m_data(nullptr)
|
||||
, m_bounds(bitmap.m_bounds)
|
||||
, m_should_free(bitmap.m_should_free)
|
||||
, m_format(bitmap.m_format)
|
||||
{
|
||||
if (m_should_free) {
|
||||
size_t len = width() * height() * sizeof(Color);
|
||||
m_data = (Color*)malloc(len);
|
||||
memcpy((uint8_t*)m_data, (uint8_t*)bitmap.m_data, len);
|
||||
} else {
|
||||
m_data = bitmap.m_data;
|
||||
}
|
||||
}
|
||||
|
||||
PixelBitmap::PixelBitmap(PixelBitmap&& moved_bitmap) noexcept
|
||||
: m_data(moved_bitmap.m_data)
|
||||
, m_bounds(moved_bitmap.m_bounds)
|
||||
, m_should_free(moved_bitmap.m_should_free)
|
||||
, m_format(moved_bitmap.m_format)
|
||||
{
|
||||
moved_bitmap.m_data = nullptr;
|
||||
moved_bitmap.bounds().set_width(0);
|
||||
moved_bitmap.bounds().set_height(0);
|
||||
moved_bitmap.m_should_free = false;
|
||||
}
|
||||
|
||||
void PixelBitmap::resize(size_t width, size_t height)
|
||||
{
|
||||
clear();
|
||||
bounds().set_width(width);
|
||||
bounds().set_height(height);
|
||||
m_data = (Color*)malloc(sizeof(Color) * width * height);
|
||||
m_should_free = true;
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
26
libs/libg/src/Rect.cpp
Normal file
26
libs/libg/src/Rect.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <libg/Rect.h>
|
||||
|
||||
namespace LG {
|
||||
|
||||
Rect::Rect(int x, int y, size_t width, size_t height)
|
||||
: m_origin(x, y)
|
||||
, m_width(width)
|
||||
, m_height(height)
|
||||
{
|
||||
}
|
||||
|
||||
void Rect::encode(EncodedMessage& buf) const
|
||||
{
|
||||
Encoder::append(buf, m_origin);
|
||||
Encoder::append(buf, m_width);
|
||||
Encoder::append(buf, m_height);
|
||||
}
|
||||
|
||||
void Rect::decode(const char* buf, size_t& offset)
|
||||
{
|
||||
Encoder::decode(buf, offset, m_origin);
|
||||
Encoder::decode(buf, offset, m_width);
|
||||
Encoder::decode(buf, offset, m_height);
|
||||
}
|
||||
|
||||
} // namespace LG
|
||||
Reference in New Issue
Block a user