mirror of
https://github.com/NoelFB/blah.git
synced 2025-07-18 19:41:52 +08:00
restructured project to match a more standard cmake setup
This commit is contained in:
471
src/images/aseprite.cpp
Normal file
471
src/images/aseprite.cpp
Normal file
@ -0,0 +1,471 @@
|
||||
#include <blah/images/aseprite.h>
|
||||
#include <blah/streams/filestream.h>
|
||||
#include <blah/core/filesystem.h>
|
||||
#include <blah/core/log.h>
|
||||
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_ONLY_ZLIB
|
||||
#include "../third_party/stb_image.h"
|
||||
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define MUL_UN8(a, b, t) \
|
||||
((t) = (a) * (uint16_t)(b) + 0x80, ((((t) >> 8) + (t) ) >> 8))
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
Aseprite::Aseprite()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Aseprite::Aseprite(const char* path)
|
||||
{
|
||||
FileStream fs(path, FileMode::Read);
|
||||
parse(fs);
|
||||
}
|
||||
|
||||
Aseprite::Aseprite(Stream& stream)
|
||||
{
|
||||
parse(stream);
|
||||
}
|
||||
|
||||
Aseprite::Aseprite(const Aseprite& src)
|
||||
{
|
||||
mode = src.mode;
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
layers = src.layers;
|
||||
frames = src.frames;
|
||||
tags = src.tags;
|
||||
slices = src.slices;
|
||||
palette = src.palette;
|
||||
}
|
||||
|
||||
Aseprite::Aseprite(Aseprite&& src) noexcept
|
||||
{
|
||||
mode = src.mode;
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
layers = std::move(src.layers);
|
||||
frames = std::move(src.frames);
|
||||
tags = std::move(src.tags);
|
||||
slices = std::move(src.slices);
|
||||
palette = std::move(src.palette);
|
||||
}
|
||||
|
||||
Aseprite& Aseprite::operator=(const Aseprite& src)
|
||||
{
|
||||
mode = src.mode;
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
layers = src.layers;
|
||||
frames = src.frames;
|
||||
tags = src.tags;
|
||||
slices = src.slices;
|
||||
palette = src.palette;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Aseprite& Aseprite::operator=(Aseprite&& src) noexcept
|
||||
{
|
||||
mode = src.mode;
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
layers = std::move(src.layers);
|
||||
frames = std::move(src.frames);
|
||||
tags = std::move(src.tags);
|
||||
slices = std::move(src.slices);
|
||||
palette = std::move(src.palette);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Aseprite::~Aseprite()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Aseprite::parse(Stream& stream)
|
||||
{
|
||||
if (!stream.is_readable())
|
||||
{
|
||||
BLAH_ERROR("Stream is not readable");
|
||||
return;
|
||||
}
|
||||
|
||||
int frame_count = 0;
|
||||
|
||||
// header
|
||||
{
|
||||
// filesize
|
||||
stream.read<uint32_t>(Endian::Little);
|
||||
|
||||
// magic number
|
||||
auto magic = stream.read<uint16_t>(Endian::Little);
|
||||
if (magic != 0xA5E0)
|
||||
{
|
||||
BLAH_ERROR("File is not a valid Aseprite file");
|
||||
return;
|
||||
}
|
||||
|
||||
// main info
|
||||
frame_count = stream.read<uint16_t>(Endian::Little);
|
||||
width = stream.read<uint16_t>(Endian::Little);
|
||||
height = stream.read<uint16_t>(Endian::Little);
|
||||
mode = static_cast<Aseprite::Modes>(stream.read<uint16_t>(Endian::Little) / 8);
|
||||
|
||||
// don't care about other info
|
||||
stream.read<uint32_t>(Endian::Little); // Flags
|
||||
stream.read<uint16_t>(Endian::Little); // Speed (deprecated)
|
||||
stream.read<uint32_t>(Endian::Little); // Should be 0
|
||||
stream.read<uint32_t>(Endian::Little); // Should be 0
|
||||
stream.read<uint8_t>(Endian::Little); // Palette entry
|
||||
stream.seek(stream.position() + 3); // Ignore these bytes
|
||||
stream.read<uint16_t>(Endian::Little); // Number of colors (0 means 256 for old sprites)
|
||||
stream.read<int8_t>(Endian::Little); // Pixel width
|
||||
stream.read<int8_t>(Endian::Little); // Pixel height
|
||||
stream.seek(stream.position() + 92); // For Future
|
||||
}
|
||||
|
||||
frames.resize(frame_count);
|
||||
|
||||
// frames
|
||||
for (int i = 0; i < frame_count; i++)
|
||||
{
|
||||
auto frameStart = stream.position();
|
||||
auto frameEnd = frameStart + stream.read<uint32_t>(Endian::Little);
|
||||
unsigned int chunks = 0;
|
||||
|
||||
// frame header
|
||||
{
|
||||
auto magic = stream.read<uint16_t>(Endian::Little); // magic number
|
||||
if (magic != 0xF1FA)
|
||||
{
|
||||
BLAH_ERROR("File is not a valid Aseprite file");
|
||||
return;
|
||||
}
|
||||
|
||||
auto old_chunk_count = stream.read<uint16_t>(Endian::Little);
|
||||
frames[i].duration = stream.read<uint16_t>(Endian::Little);
|
||||
stream.seek(stream.position() + 2); // for future
|
||||
auto new_chunk_count = stream.read<uint32_t>(Endian::Little);
|
||||
|
||||
if (old_chunk_count == 0xFFFF)
|
||||
chunks = new_chunk_count;
|
||||
else
|
||||
chunks = old_chunk_count;
|
||||
}
|
||||
|
||||
// make frame image
|
||||
frames[i].image = Image(width, height);
|
||||
|
||||
// frame chunks
|
||||
for (unsigned int j = 0; j < chunks; j++)
|
||||
{
|
||||
auto chunkStart = stream.position();
|
||||
auto chunkEnd = chunkStart + stream.read<uint32_t>(Endian::Little);
|
||||
auto chunkType = static_cast<Chunks>(stream.read<uint16_t>(Endian::Little));
|
||||
|
||||
switch (chunkType)
|
||||
{
|
||||
case Chunks::Layer: parse_layer(stream, i); break;
|
||||
case Chunks::Cel: parse_cel(stream, i, chunkEnd); break;
|
||||
case Chunks::Palette: parse_palette(stream, i); break;
|
||||
case Chunks::UserData: parse_user_data(stream, i); break;
|
||||
case Chunks::FrameTags: parse_tag(stream, i); break;
|
||||
case Chunks::Slice: parse_slice(stream, i); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
stream.seek(chunkEnd);
|
||||
}
|
||||
|
||||
stream.seek(frameEnd);
|
||||
}
|
||||
}
|
||||
|
||||
void Aseprite::parse_layer(Stream& stream, int frame)
|
||||
{
|
||||
layers.emplace_back();
|
||||
|
||||
auto& layer = layers.back();
|
||||
layer.flag = static_cast<LayerFlags>(stream.read<uint16_t>(Endian::Little));
|
||||
layer.visible = ((int)layer.flag & (int)LayerFlags::Visible) == (int)LayerFlags::Visible;
|
||||
layer.type = static_cast<LayerTypes>(stream.read<uint16_t>(Endian::Little));
|
||||
layer.child_level = stream.read<uint16_t>(Endian::Little);
|
||||
stream.read<uint16_t>(Endian::Little); // width
|
||||
stream.read<uint16_t>(Endian::Little); // height
|
||||
layer.blendmode = stream.read<uint16_t>(Endian::Little);
|
||||
layer.alpha = stream.read<uint8_t>(Endian::Little);
|
||||
stream.seek(stream.position() + 3); // for future
|
||||
|
||||
layer.name.set_length(stream.read<uint16_t>(Endian::Little));
|
||||
stream.read(layer.name.cstr(), layer.name.length());
|
||||
|
||||
layer.userdata.color = 0xffffff;
|
||||
layer.userdata.text = "";
|
||||
m_last_userdata = &(layer.userdata);
|
||||
}
|
||||
|
||||
void Aseprite::parse_cel(Stream& stream, int frameIndex, size_t maxPosition)
|
||||
{
|
||||
Frame& frame = frames[frameIndex];
|
||||
|
||||
frame.cels.emplace_back();
|
||||
auto& cel = frame.cels.back();
|
||||
cel.layer_index = stream.read<uint16_t>(Endian::Little);
|
||||
cel.x = stream.read<uint16_t>(Endian::Little);
|
||||
cel.y = stream.read<uint16_t>(Endian::Little);
|
||||
cel.alpha = stream.read<uint8_t>(Endian::Little);
|
||||
cel.linked_frame_index = -1;
|
||||
|
||||
auto celType = stream.read<uint16_t>(Endian::Little);
|
||||
stream.seek(stream.position() + 7);
|
||||
|
||||
// RAW or DEFLATE
|
||||
if (celType == 0 || celType == 2)
|
||||
{
|
||||
auto width = stream.read<uint16_t>(Endian::Little);
|
||||
auto height = stream.read<uint16_t>(Endian::Little);
|
||||
auto count = width * height * (int)mode;
|
||||
|
||||
cel.image = Image(width, height);
|
||||
|
||||
// RAW
|
||||
if (celType == 0)
|
||||
{
|
||||
stream.read(cel.image.pixels, count);
|
||||
}
|
||||
// DEFLATE (zlib)
|
||||
else
|
||||
{
|
||||
// this could be optimized to use a buffer on the stack if we only read set chunks at a time
|
||||
// stbi's zlib doesn't have that functionality though
|
||||
auto size = maxPosition - stream.position();
|
||||
if (size > INT32_MAX)
|
||||
size = INT32_MAX;
|
||||
|
||||
char* buffer = new char[size];
|
||||
stream.read(buffer, size);
|
||||
|
||||
int olen = width * height * sizeof(Color);
|
||||
int res = stbi_zlib_decode_buffer((char*)cel.image.pixels, olen, buffer, (int)size);
|
||||
|
||||
delete[] buffer;
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
BLAH_ERROR("Unable to parse Aseprite file");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to pixels
|
||||
// note: we work in-place to save having to store stuff in a buffer
|
||||
if (mode == Modes::Grayscale)
|
||||
{
|
||||
auto src = (unsigned char*)cel.image.pixels;
|
||||
auto dst = cel.image.pixels;
|
||||
for (int d = width * height - 1, s = (width * height - 1) * 2; d >= 0; d--, s -= 2)
|
||||
dst[d] = Color(src[s], src[s], src[s], src[s + 1]);
|
||||
}
|
||||
else if (mode == Modes::Indexed)
|
||||
{
|
||||
auto src = (unsigned char*)cel.image.pixels;
|
||||
auto dst = cel.image.pixels;
|
||||
for (int i = width * height - 1; i >= 0; i--)
|
||||
dst[i] = palette[src[i]];
|
||||
}
|
||||
|
||||
}
|
||||
// REFERENCE
|
||||
// this cel directly references a previous cel
|
||||
else if (celType == 1)
|
||||
{
|
||||
cel.linked_frame_index = stream.read<uint16_t>(Endian::Little);
|
||||
}
|
||||
|
||||
// draw to frame if visible
|
||||
if ((int)layers[cel.layer_index].flag & (int)LayerFlags::Visible)
|
||||
{
|
||||
render_cel(&cel, &frame);
|
||||
}
|
||||
|
||||
cel.userdata.color = 0xffffff;
|
||||
cel.userdata.text = "";
|
||||
m_last_userdata = &(cel.userdata);
|
||||
}
|
||||
|
||||
void Aseprite::parse_palette(Stream& stream, int frame)
|
||||
{
|
||||
/* size */ stream.read<uint32_t>(Endian::Little);
|
||||
auto start = stream.read<uint32_t>(Endian::Little);
|
||||
auto end = stream.read<uint32_t>(Endian::Little);
|
||||
stream.seek(stream.position() + 8);
|
||||
|
||||
palette.resize(palette.size() + (end - start + 1));
|
||||
|
||||
for (int p = 0, len = static_cast<int>(end - start) + 1; p < len; p++)
|
||||
{
|
||||
auto hasName = stream.read<uint16_t>(Endian::Little);
|
||||
|
||||
palette[start + p] = stream.read<Color>(Endian::Little);
|
||||
|
||||
if (hasName & 0xF000)
|
||||
{
|
||||
int len = stream.read<uint16_t>(Endian::Little);
|
||||
stream.seek(stream.position() + len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Aseprite::parse_user_data(Stream& stream, int frame)
|
||||
{
|
||||
if (m_last_userdata != nullptr)
|
||||
{
|
||||
auto flags = stream.read<uint32_t>(Endian::Little);
|
||||
|
||||
// has text
|
||||
if (flags & (1 << 0))
|
||||
{
|
||||
m_last_userdata->text.set_length(stream.read<uint16_t>(Endian::Little));
|
||||
stream.read(m_last_userdata->text.cstr(), m_last_userdata->text.length());
|
||||
}
|
||||
|
||||
// has color
|
||||
if (flags & (1 << 1))
|
||||
m_last_userdata->color = stream.read<Color>(Endian::Little);
|
||||
}
|
||||
}
|
||||
|
||||
void Aseprite::parse_tag(Stream& stream, int frame)
|
||||
{
|
||||
auto count = stream.read<uint16_t>(Endian::Little);
|
||||
stream.seek(stream.position() + 8);
|
||||
|
||||
for (int t = 0; t < count; t++)
|
||||
{
|
||||
Tag tag;
|
||||
tag.from = stream.read<uint16_t>(Endian::Little);
|
||||
tag.to = stream.read<uint16_t>(Endian::Little);
|
||||
tag.loops = static_cast<LoopDirections>(stream.read<int8_t>(Endian::Little));
|
||||
|
||||
stream.seek(stream.position() + 8);
|
||||
tag.color = Color(stream.read<int8_t>(), stream.read<int8_t>(), stream.read<int8_t>(Endian::Little), 255);
|
||||
stream.seek(stream.position() + 1);
|
||||
|
||||
tag.name.set_length(stream.read<uint16_t>(Endian::Little));
|
||||
stream.read(tag.name.cstr(), tag.name.length());
|
||||
|
||||
tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
|
||||
void Aseprite::parse_slice(Stream& stream, int frame)
|
||||
{
|
||||
int count = stream.read<uint32_t>(Endian::Little);
|
||||
int flags = stream.read<uint32_t>(Endian::Little);
|
||||
stream.read<uint32_t>(Endian::Little); // reserved
|
||||
|
||||
String name;
|
||||
name.set_length(stream.read<uint16_t>(Endian::Little));
|
||||
stream.read(name.cstr(), name.length());
|
||||
|
||||
for (int s = 0; s < count; s++)
|
||||
{
|
||||
slices.emplace_back();
|
||||
|
||||
auto& slice = slices.back();
|
||||
slice.name = name;
|
||||
slice.frame = stream.read<uint32_t>(Endian::Little);
|
||||
slice.origin.x = stream.read<int32_t>(Endian::Little);
|
||||
slice.origin.y = stream.read<int32_t>(Endian::Little);
|
||||
slice.width = stream.read<uint32_t>(Endian::Little);
|
||||
slice.height = stream.read<uint32_t>(Endian::Little);
|
||||
|
||||
// 9 slice (ignored atm)
|
||||
if (flags & (1 << 0))
|
||||
{
|
||||
stream.read<int32_t>(Endian::Little);
|
||||
stream.read<int32_t>(Endian::Little);
|
||||
stream.read<uint32_t>(Endian::Little);
|
||||
stream.read<uint32_t>(Endian::Little);
|
||||
}
|
||||
|
||||
// pivot point
|
||||
slice.has_pivot = false;
|
||||
if (flags & (1 << 1))
|
||||
{
|
||||
slice.has_pivot = true;
|
||||
slice.pivot.x = stream.read<uint32_t>(Endian::Little);
|
||||
slice.pivot.y = stream.read<uint32_t>(Endian::Little);
|
||||
}
|
||||
|
||||
slice.userdata.color = 0xffffff;
|
||||
slice.userdata.text = "";
|
||||
m_last_userdata = &(slice.userdata);
|
||||
}
|
||||
}
|
||||
|
||||
void Aseprite::render_cel(Cel* cel, Frame* frame)
|
||||
{
|
||||
Layer& layer = layers[cel->layer_index];
|
||||
|
||||
while (cel->linked_frame_index >= 0)
|
||||
{
|
||||
auto& frame = frames[cel->linked_frame_index];
|
||||
for (auto& it : frame.cels)
|
||||
if (it.layer_index == cel->layer_index)
|
||||
{
|
||||
cel = ⁢
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int t;
|
||||
unsigned char opacity = MUL_UN8(cel->alpha, layer.alpha, t);
|
||||
if (opacity <= 0)
|
||||
return;
|
||||
|
||||
auto src = cel->image.pixels;
|
||||
auto srcX = cel->x;
|
||||
auto srcY = cel->y;
|
||||
auto srcW = cel->image.width;
|
||||
auto srcH = cel->image.height;
|
||||
auto dst = frame->image.pixels;
|
||||
auto dstW = frame->image.width;
|
||||
auto dstH = frame->image.height;
|
||||
|
||||
// blit pixels
|
||||
int left = MAX(0, srcX);
|
||||
int right = MIN(dstW, srcX + srcW);
|
||||
int top = MAX(0, srcY);
|
||||
int bottom = MIN(dstH, srcY + srcH);
|
||||
|
||||
if (layer.blendmode == 0)
|
||||
{
|
||||
for (int dx = left, sx = -MIN(srcX, 0); dx < right; dx++, sx++)
|
||||
{
|
||||
for (int dy = top, sy = -MIN(srcY, 0); dy < bottom; dy++, sy++)
|
||||
{
|
||||
Color* srcColor = (src + sx + sy * srcW);
|
||||
Color* dstColor = (dst + dx + dy * dstW);
|
||||
|
||||
if (srcColor->a != 0)
|
||||
{
|
||||
auto sa = MUL_UN8(srcColor->a, opacity, t);
|
||||
auto ra = dstColor->a + sa - MUL_UN8(dstColor->a, sa, t);
|
||||
|
||||
dstColor->r = (unsigned char)(dstColor->r + (srcColor->r - dstColor->r) * sa / ra);
|
||||
dstColor->g = (unsigned char)(dstColor->g + (srcColor->g - dstColor->g) * sa / ra);
|
||||
dstColor->b = (unsigned char)(dstColor->b + (srcColor->b - dstColor->b) * sa / ra);
|
||||
dstColor->a = (unsigned char)ra;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BLAH_ERROR("Aseprite blendmodes aren't implemented");
|
||||
}
|
||||
}
|
245
src/images/font.cpp
Normal file
245
src/images/font.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
#include <blah/images/font.h>
|
||||
#include <blah/streams/filestream.h>
|
||||
#include <blah/math/calc.h>
|
||||
#include <blah/core/log.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
#define STBTT_STATIC
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "../third_party/stb_truetype.h"
|
||||
|
||||
String GetName(stbtt_fontinfo* font, int nameId)
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
// get the name
|
||||
const uint16_t* ptr = (const uint16_t*)stbtt_GetFontNameStr(font, &length,
|
||||
STBTT_PLATFORM_ID_MICROSOFT,
|
||||
STBTT_MS_EID_UNICODE_BMP,
|
||||
STBTT_MS_LANG_ENGLISH,
|
||||
nameId);
|
||||
|
||||
// we want the size in wide chars
|
||||
length /= 2;
|
||||
|
||||
String str;
|
||||
if (length > 0)
|
||||
str.append_utf16(ptr, ptr + length, Calc::is_little_endian());
|
||||
return str;
|
||||
}
|
||||
|
||||
Font::Font()
|
||||
{
|
||||
m_font = nullptr;
|
||||
m_data = nullptr;
|
||||
m_ascent = 0;
|
||||
m_descent = 0;
|
||||
m_line_gap = 0;
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
Font::Font(Stream& stream) : Font()
|
||||
{
|
||||
load(stream);
|
||||
}
|
||||
|
||||
Font::Font(const char* path) : Font()
|
||||
{
|
||||
FileStream fs(path, FileMode::Read);
|
||||
if (fs.is_readable())
|
||||
load(fs);
|
||||
}
|
||||
|
||||
Font::Font(Font&& src) noexcept
|
||||
{
|
||||
m_font = src.m_font;
|
||||
m_data = src.m_data;
|
||||
m_family_name = src.m_family_name;
|
||||
m_style_name = src.m_style_name;
|
||||
m_ascent = src.m_ascent;
|
||||
m_descent = src.m_descent;
|
||||
m_line_gap = src.m_line_gap;
|
||||
m_valid = src.m_valid;
|
||||
|
||||
src.m_family_name.clear();
|
||||
src.m_style_name.clear();
|
||||
src.m_valid = false;
|
||||
src.m_font = nullptr;
|
||||
src.m_data = nullptr;
|
||||
}
|
||||
|
||||
Font& Font::operator=(Font&& src) noexcept
|
||||
{
|
||||
m_font = src.m_font;
|
||||
m_data = src.m_data;
|
||||
m_family_name = src.m_family_name;
|
||||
m_style_name = src.m_style_name;
|
||||
m_ascent = src.m_ascent;
|
||||
m_descent = src.m_descent;
|
||||
m_line_gap = src.m_line_gap;
|
||||
m_valid = src.m_valid;
|
||||
|
||||
src.m_family_name.clear();
|
||||
src.m_style_name.clear();
|
||||
src.m_valid = false;
|
||||
src.m_font = nullptr;
|
||||
src.m_data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Font::~Font()
|
||||
{
|
||||
dispose();
|
||||
}
|
||||
|
||||
void Font::load(Stream& stream)
|
||||
{
|
||||
dispose();
|
||||
|
||||
if (!stream.is_readable())
|
||||
{
|
||||
BLAH_ERROR("Unable to load a font as the Stream was not readable");
|
||||
return;
|
||||
}
|
||||
|
||||
// create data buffer
|
||||
auto size = stream.length();
|
||||
m_data = new unsigned char[size];
|
||||
stream.read(m_data, size);
|
||||
|
||||
// init font
|
||||
m_font = new stbtt_fontinfo();
|
||||
auto fn = (stbtt_fontinfo*)m_font;
|
||||
stbtt_InitFont(fn, m_data, 0);
|
||||
m_family_name = GetName(fn, 1);
|
||||
m_style_name = GetName(fn, 2);
|
||||
|
||||
// properties
|
||||
stbtt_GetFontVMetrics(fn, &m_ascent, &m_descent, &m_line_gap);
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
void Font::dispose()
|
||||
{
|
||||
delete (stbtt_fontinfo*)m_font;
|
||||
delete[] m_data;
|
||||
m_font = nullptr;
|
||||
m_data = nullptr;
|
||||
m_family_name.dispose();
|
||||
m_style_name.dispose();
|
||||
}
|
||||
|
||||
const char* Font::family_name() const
|
||||
{
|
||||
return m_family_name.cstr();
|
||||
}
|
||||
|
||||
const char* Font::style_name() const
|
||||
{
|
||||
return m_style_name.cstr();
|
||||
}
|
||||
|
||||
int Font::ascent() const
|
||||
{
|
||||
return m_ascent;
|
||||
}
|
||||
|
||||
int Font::descent() const
|
||||
{
|
||||
return m_descent;
|
||||
}
|
||||
|
||||
int Font::line_gap() const
|
||||
{
|
||||
return m_line_gap;
|
||||
}
|
||||
|
||||
int Font::height() const
|
||||
{
|
||||
return m_ascent - m_descent;
|
||||
}
|
||||
|
||||
int Font::line_height() const
|
||||
{
|
||||
return m_ascent - m_descent + m_line_gap;
|
||||
}
|
||||
|
||||
int Font::get_glyph(Codepoint codepoint) const
|
||||
{
|
||||
if (!m_font)
|
||||
return 0;
|
||||
return stbtt_FindGlyphIndex((stbtt_fontinfo*)m_font, codepoint);
|
||||
}
|
||||
|
||||
float Font::get_scale(float size) const
|
||||
{
|
||||
if (!m_font)
|
||||
return 0;
|
||||
|
||||
return stbtt_ScaleForMappingEmToPixels((stbtt_fontinfo*)m_font, size);
|
||||
}
|
||||
|
||||
float Font::get_kerning(int glyph1, int glyph2, float scale) const
|
||||
{
|
||||
if (!m_font)
|
||||
return 0;
|
||||
return stbtt_GetGlyphKernAdvance((stbtt_fontinfo*)m_font, glyph1, glyph2) * scale;
|
||||
}
|
||||
|
||||
Font::Char Font::get_character(int glyph, float scale) const
|
||||
{
|
||||
Char ch;
|
||||
|
||||
if (!m_font)
|
||||
return ch;
|
||||
|
||||
int advance, offsetX, x0, y0, x1, y1;
|
||||
|
||||
stbtt_GetGlyphHMetrics((stbtt_fontinfo*)m_font, glyph, &advance, &offsetX);
|
||||
stbtt_GetGlyphBitmapBox((stbtt_fontinfo*)m_font, glyph, scale, scale, &x0, &y0, &x1, &y1);
|
||||
|
||||
int w = (x1 - x0);
|
||||
int h = (y1 - y0);
|
||||
|
||||
// define character
|
||||
ch.glyph = glyph;
|
||||
ch.width = w;
|
||||
ch.height = h;
|
||||
ch.advance = advance * scale;
|
||||
ch.offset_x = offsetX * scale;
|
||||
ch.offset_y = (float)y0;
|
||||
ch.scale = scale;
|
||||
ch.has_glyph = (w > 0 && h > 0 && stbtt_IsGlyphEmpty((stbtt_fontinfo*)m_font, glyph) == 0);
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
bool Font::get_image(const Font::Char& ch, Color* pixels) const
|
||||
{
|
||||
if (ch.has_glyph)
|
||||
{
|
||||
// we actually use the image buffer as our temporary buffer, and fill the pixels out backwards after
|
||||
// kinda weird but it works & saves creating more memory
|
||||
unsigned char* src = (unsigned char*)pixels;
|
||||
stbtt_MakeGlyphBitmap((stbtt_fontinfo*)m_font, src, ch.width, ch.height, ch.width, ch.scale, ch.scale, ch.glyph);
|
||||
|
||||
int len = ch.width * ch.height;
|
||||
for (int a = (len - 1) * 4, b = (len - 1); b >= 0; a -= 4, b -= 1)
|
||||
{
|
||||
src[a + 0] = src[b];
|
||||
src[a + 1] = src[b];
|
||||
src[a + 2] = src[b];
|
||||
src[a + 3] = src[b];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Font::is_valid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
301
src/images/image.cpp
Normal file
301
src/images/image.cpp
Normal file
@ -0,0 +1,301 @@
|
||||
#include <blah/images/image.h>
|
||||
#include <blah/streams/stream.h>
|
||||
#include <blah/streams/filestream.h>
|
||||
#include <blah/core/log.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_ONLY_JPEG
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_ONLY_BMP
|
||||
#include "../third_party/stb_image.h"
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "../third_party/stb_image_write.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
int Blah_STBI_Read(void* user, char* data, int size)
|
||||
{
|
||||
int64_t read = ((Stream*)user)->read(data, size);
|
||||
return (int)read;
|
||||
}
|
||||
|
||||
void Blah_STBI_Skip(void* user, int n)
|
||||
{
|
||||
((Stream*)user)->seek(((Stream*)user)->position() + n);
|
||||
}
|
||||
|
||||
int Blah_STBI_Eof(void* user)
|
||||
{
|
||||
int64_t position = ((Stream*)user)->position();
|
||||
int64_t length = ((Stream*)user)->length();
|
||||
|
||||
if (position >= length)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Blah_STBI_Write(void* context, void* data, int size)
|
||||
{
|
||||
((Stream*)context)->write((char*)data, size);
|
||||
}
|
||||
}
|
||||
|
||||
Image::Image()
|
||||
{
|
||||
width = height = 0;
|
||||
pixels = nullptr;
|
||||
m_stbi_ownership = false;
|
||||
}
|
||||
|
||||
Image::Image(Stream& stream)
|
||||
{
|
||||
width = height = 0;
|
||||
pixels = nullptr;
|
||||
m_stbi_ownership = false;
|
||||
from_stream(stream);
|
||||
}
|
||||
|
||||
Image::Image(const char* file)
|
||||
{
|
||||
width = height = 0;
|
||||
pixels = nullptr;
|
||||
m_stbi_ownership = false;
|
||||
|
||||
FileStream fs(file, FileMode::Read);
|
||||
if (fs.is_readable())
|
||||
from_stream(fs);
|
||||
}
|
||||
|
||||
Image::Image(int w, int h)
|
||||
{
|
||||
BLAH_ASSERT(w >= 0 && h >= 0, "Image width and height must be larger than 0");
|
||||
|
||||
width = w;
|
||||
height = h;
|
||||
pixels = new Color[width * height];
|
||||
m_stbi_ownership = false;
|
||||
memset(pixels, 0, (size_t)width * (size_t)height * sizeof(Color));
|
||||
}
|
||||
|
||||
Image::Image(const Image& src)
|
||||
{
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
m_stbi_ownership = src.m_stbi_ownership;
|
||||
pixels = nullptr;
|
||||
|
||||
if (src.pixels != nullptr && width > 0 && height > 0)
|
||||
{
|
||||
pixels = new Color[width * height];
|
||||
memcpy(pixels, src.pixels, sizeof(Color) * width * height);
|
||||
}
|
||||
}
|
||||
|
||||
Image& Image::operator=(const Image& src)
|
||||
{
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
m_stbi_ownership = src.m_stbi_ownership;
|
||||
pixels = nullptr;
|
||||
|
||||
if (src.pixels != nullptr && width > 0 && height > 0)
|
||||
{
|
||||
pixels = new Color[width * height];
|
||||
memcpy(pixels, src.pixels, sizeof(Color) * width * height);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Image(Image&& src) noexcept
|
||||
{
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
pixels = src.pixels;
|
||||
m_stbi_ownership = src.m_stbi_ownership;
|
||||
src.width = src.height = 0;
|
||||
src.pixels = nullptr;
|
||||
src.m_stbi_ownership = false;
|
||||
}
|
||||
|
||||
Image& Image::operator=(Image&& src) noexcept
|
||||
{
|
||||
width = src.width;
|
||||
height = src.height;
|
||||
pixels = src.pixels;
|
||||
m_stbi_ownership = src.m_stbi_ownership;
|
||||
src.width = src.height = 0;
|
||||
src.pixels = nullptr;
|
||||
src.m_stbi_ownership = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::~Image()
|
||||
{
|
||||
dispose();
|
||||
}
|
||||
|
||||
void Image::from_stream(Stream& stream)
|
||||
{
|
||||
dispose();
|
||||
|
||||
if (!stream.is_readable())
|
||||
{
|
||||
BLAH_ERROR("Unable to load image as the Stream was not readable");
|
||||
return;
|
||||
}
|
||||
|
||||
stbi_io_callbacks callbacks;
|
||||
callbacks.eof = Blah_STBI_Eof;
|
||||
callbacks.read = Blah_STBI_Read;
|
||||
callbacks.skip = Blah_STBI_Skip;
|
||||
|
||||
int x, y, comps;
|
||||
uint8_t* data = stbi_load_from_callbacks(&callbacks, &stream, &x, &y, &comps, 4);
|
||||
|
||||
if (data == nullptr)
|
||||
{
|
||||
BLAH_ERROR("Unable to load image as the Stream's data was not a valid image");
|
||||
return;
|
||||
}
|
||||
|
||||
m_stbi_ownership = true;
|
||||
pixels = (Color*)data;
|
||||
width = x;
|
||||
height = y;
|
||||
}
|
||||
|
||||
void Image::dispose()
|
||||
{
|
||||
if (m_stbi_ownership)
|
||||
stbi_image_free(pixels);
|
||||
else
|
||||
delete[] pixels;
|
||||
pixels = nullptr;
|
||||
width = height = 0;
|
||||
m_stbi_ownership = false;
|
||||
}
|
||||
|
||||
void Image::premultiply()
|
||||
{
|
||||
if (pixels != nullptr)
|
||||
{
|
||||
for (int n = 0; n < width * height; n ++)
|
||||
{
|
||||
pixels[n].r = (uint8_t)(pixels[n].r * pixels[n].a / 255);
|
||||
pixels[n].g = (uint8_t)(pixels[n].g * pixels[n].a / 255);
|
||||
pixels[n].b = (uint8_t)(pixels[n].b * pixels[n].a / 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Image::set_pixels(const RectI& rect, Color* data)
|
||||
{
|
||||
for (int y = 0; y < rect.h; y++)
|
||||
{
|
||||
int to = rect.x + ((rect.y + y) * width);
|
||||
int from = (y * rect.w);
|
||||
memcpy(pixels + to, data + from, sizeof(Color) * rect.w);
|
||||
}
|
||||
}
|
||||
|
||||
bool Image::save_png(const char* file) const
|
||||
{
|
||||
FileStream fs(file, FileMode::Write);
|
||||
return save_png(fs);
|
||||
}
|
||||
|
||||
bool Image::save_png(Stream& stream) const
|
||||
{
|
||||
BLAH_ASSERT(pixels != nullptr, "Image Pixel data cannot be null");
|
||||
BLAH_ASSERT(width > 0 && height > 0, "Image Width and Height must be larger than 0");
|
||||
|
||||
if (stream.is_writable())
|
||||
{
|
||||
stbi_write_force_png_filter = 0;
|
||||
stbi_write_png_compression_level = 0;
|
||||
|
||||
if (stbi_write_png_to_func(Blah_STBI_Write, &stream, width, height, 4, pixels, width * 4) != 0)
|
||||
return true;
|
||||
else
|
||||
Log::error("stbi_write_png_to_func failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::error("Cannot save Image, the Stream is not writable");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Image::save_jpg(const char* file, int quality) const
|
||||
{
|
||||
FileStream fs(file, FileMode::Write);
|
||||
return save_jpg(fs, quality);
|
||||
}
|
||||
|
||||
bool Image::save_jpg(Stream& stream, int quality) const
|
||||
{
|
||||
BLAH_ASSERT(pixels != nullptr, "Image Pixel data cannot be null");
|
||||
BLAH_ASSERT(width > 0 && height > 0, "Image Width and Height must be larger than 0");
|
||||
|
||||
if (quality < 1)
|
||||
{
|
||||
Log::warn("jpg quality value should be between 1 and 100; input was %i", quality);
|
||||
quality = 1;
|
||||
}
|
||||
else if (quality > 100)
|
||||
{
|
||||
Log::warn("jpg quality value should be between 1 and 100; input was %i", quality);
|
||||
quality = 100;
|
||||
}
|
||||
|
||||
if (stream.is_writable())
|
||||
{
|
||||
if (stbi_write_jpg_to_func(Blah_STBI_Write, &stream, width, height, 4, pixels, quality) != 0)
|
||||
return true;
|
||||
else
|
||||
Log::error("stbi_write_jpg_to_func failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::error("Cannot save Image, the Stream is not writable");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Image::get_pixels(Color* dest, const Point& destPos, const Point& destSize, RectI sourceRect)
|
||||
{
|
||||
// can't be outside of the source image
|
||||
if (sourceRect.x < 0) sourceRect.x = 0;
|
||||
if (sourceRect.y < 0) sourceRect.y = 0;
|
||||
if (sourceRect.x + sourceRect.w > width) sourceRect.w = width - sourceRect.x;
|
||||
if (sourceRect.y + sourceRect.h > height) sourceRect.h = height - sourceRect.y;
|
||||
|
||||
// can't be larger than our destination
|
||||
if (sourceRect.w > destSize.x - destPos.x)
|
||||
sourceRect.w = destSize.x - destPos.x;
|
||||
if (sourceRect.h > destSize.y - destPos.y)
|
||||
sourceRect.h = destSize.y - destPos.y;
|
||||
|
||||
for (int y = 0; y < sourceRect.h; y++)
|
||||
{
|
||||
int to = destPos.x + (destPos.y + y) * destSize.x;
|
||||
int from = sourceRect.x + (sourceRect.y + y) * width;
|
||||
memcpy(dest + to, pixels + from, sizeof(Color) * (int)sourceRect.w);
|
||||
}
|
||||
}
|
||||
|
||||
Image Image::get_sub_image(const RectI& sourceRect)
|
||||
{
|
||||
Image img(sourceRect.w, sourceRect.h);
|
||||
get_pixels(img.pixels, Point::zero, Point(img.width, img.height), sourceRect);
|
||||
return img;
|
||||
}
|
334
src/images/packer.cpp
Normal file
334
src/images/packer.cpp
Normal file
@ -0,0 +1,334 @@
|
||||
#include <blah/images/packer.h>
|
||||
#include <blah/core/log.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
Packer::Packer()
|
||||
: max_size(8192), power_of_two(true), spacing(1), padding(1), m_dirty(false) { }
|
||||
|
||||
Packer::Packer(int max_size, int spacing, bool power_of_two)
|
||||
: max_size(max_size), power_of_two(power_of_two), spacing(spacing), padding(1), m_dirty(false) { }
|
||||
|
||||
Packer::Packer(Packer&& src) noexcept
|
||||
{
|
||||
max_size = src.max_size;
|
||||
power_of_two = src.power_of_two;
|
||||
spacing = src.spacing;
|
||||
padding = src.padding;
|
||||
m_dirty = src.m_dirty;
|
||||
pages = std::move(src.pages);
|
||||
entries = std::move(src.entries);
|
||||
m_buffer = std::move(src.m_buffer);
|
||||
}
|
||||
|
||||
Packer& Packer::operator=(Packer&& src) noexcept
|
||||
{
|
||||
max_size = src.max_size;
|
||||
power_of_two = src.power_of_two;
|
||||
spacing = src.spacing;
|
||||
padding = src.padding;
|
||||
m_dirty = src.m_dirty;
|
||||
pages = std::move(src.pages);
|
||||
entries = std::move(src.entries);
|
||||
m_buffer = std::move(src.m_buffer);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packer::~Packer()
|
||||
{
|
||||
dispose();
|
||||
}
|
||||
|
||||
void Packer::add(uint64_t id, int width, int height, const Color* pixels)
|
||||
{
|
||||
add_entry(id, width, height, pixels);
|
||||
}
|
||||
|
||||
void Packer::add(uint64_t id, const Image& image)
|
||||
{
|
||||
add_entry(id, image.width, image.height, image.pixels);
|
||||
}
|
||||
|
||||
void Packer::add(uint64_t id, const String& path)
|
||||
{
|
||||
add(id, Image(path.cstr()));
|
||||
}
|
||||
|
||||
void Packer::add_entry(uint64_t id, int w, int h, const Color* pixels)
|
||||
{
|
||||
m_dirty = true;
|
||||
|
||||
Entry entry(id, RectI(0, 0, w, h));
|
||||
|
||||
// trim
|
||||
int top = 0, left = 0, right = w, bottom = h;
|
||||
|
||||
// TOP:
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0, s = y * w; x < w; x++, s++)
|
||||
if (pixels[s].a > 0)
|
||||
{
|
||||
top = y;
|
||||
goto JUMP_LEFT;
|
||||
}
|
||||
JUMP_LEFT:
|
||||
for (int x = 0; x < w; x++)
|
||||
for (int y = top, s = x + y * w; y < h; y++, s += w)
|
||||
if (pixels[s].a > 0)
|
||||
{
|
||||
left = x;
|
||||
goto JUMP_RIGHT;
|
||||
}
|
||||
JUMP_RIGHT:
|
||||
for (int x = w - 1; x >= left; x--)
|
||||
for (int y = top, s = x + y * w; y < h; y++, s += w)
|
||||
if (pixels[s].a > 0)
|
||||
{
|
||||
right = x + 1;
|
||||
goto JUMP_BOTTOM;
|
||||
}
|
||||
JUMP_BOTTOM:
|
||||
for (int y = h - 1; y >= top; y--)
|
||||
for (int x = left, s = x + y * w; x < right; x++, s++)
|
||||
if (pixels[s].a > 0)
|
||||
{
|
||||
bottom = y + 1;
|
||||
goto JUMP_END;
|
||||
}
|
||||
JUMP_END:;
|
||||
|
||||
// pixels actually exist in this source
|
||||
if (right >= left && bottom >= top)
|
||||
{
|
||||
entry.empty = false;
|
||||
|
||||
// store size
|
||||
entry.frame.x = -left;
|
||||
entry.frame.y = -top;
|
||||
entry.packed.w = (right - left);
|
||||
entry.packed.h = (bottom - top);
|
||||
|
||||
// create pixel data
|
||||
entry.memory_index = m_buffer.position();
|
||||
|
||||
// copy pixels over
|
||||
if (entry.packed.w == w && entry.packed.h == h)
|
||||
{
|
||||
m_buffer.write((char*)pixels, sizeof(Color) * w * h);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < entry.packed.h; i++)
|
||||
m_buffer.write((char*)(pixels + left + (top + i) * entry.frame.w), sizeof(Color) * entry.packed.w);
|
||||
}
|
||||
}
|
||||
|
||||
entries.push_back(entry);
|
||||
}
|
||||
|
||||
void Packer::pack()
|
||||
{
|
||||
if (!m_dirty)
|
||||
return;
|
||||
|
||||
m_dirty = false;
|
||||
pages.clear();
|
||||
|
||||
// only if we have stuff to pack
|
||||
auto count = entries.size();
|
||||
if (count > 0)
|
||||
{
|
||||
// get all the sources sorted largest -> smallest
|
||||
Vector<Entry*> sources;
|
||||
{
|
||||
sources.resize(count);
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < entries.size(); i++)
|
||||
sources[index++] = &entries[i];
|
||||
|
||||
std::sort(sources.begin(), sources.end(), [](Packer::Entry* a, Packer::Entry* b)
|
||||
{
|
||||
return a->packed.w * a->packed.h > b->packed.w * b->packed.h;
|
||||
});
|
||||
}
|
||||
|
||||
// make sure the largest isn't too large
|
||||
if (sources[0]->packed.w + padding * 2 > max_size || sources[0]->packed.h + padding * 2 > max_size)
|
||||
{
|
||||
BLAH_ERROR("Source image is larger than max atlas size");
|
||||
return;
|
||||
}
|
||||
|
||||
// we should never need more nodes than source images * 3
|
||||
// if this causes problems we could change it to use push_back I suppose
|
||||
Vector<Node> nodes;
|
||||
nodes.resize(count * 4);
|
||||
|
||||
int packed = 0, page = 0;
|
||||
while (packed < count)
|
||||
{
|
||||
if (sources[packed]->empty)
|
||||
{
|
||||
packed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int from = packed;
|
||||
int index = 0;
|
||||
Node* root = nodes[index++].Reset(RectI(0, 0, sources[from]->packed.w + padding * 2 + spacing, sources[from]->packed.h + padding * 2 + spacing));
|
||||
|
||||
while (packed < count)
|
||||
{
|
||||
if (sources[packed]->empty)
|
||||
{
|
||||
packed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int w = sources[packed]->packed.w + padding * 2 + spacing;
|
||||
int h = sources[packed]->packed.h + padding * 2 + spacing;
|
||||
|
||||
Node* node = root->Find(w, h);
|
||||
|
||||
// try to expand
|
||||
if (node == nullptr)
|
||||
{
|
||||
bool canGrowDown = (w <= root->rect.w) && (root->rect.h + h < max_size);
|
||||
bool canGrowRight = (h <= root->rect.h) && (root->rect.w + w < max_size);
|
||||
bool shouldGrowRight = canGrowRight && (root->rect.h >= (root->rect.w + w));
|
||||
bool shouldGrowDown = canGrowDown && (root->rect.w >= (root->rect.h + h));
|
||||
|
||||
if (canGrowDown || canGrowRight)
|
||||
{
|
||||
// grow right
|
||||
if (shouldGrowRight || (!shouldGrowDown && canGrowRight))
|
||||
{
|
||||
Node* next = nodes[index++].Reset(RectI(0, 0, root->rect.w + w, root->rect.h));
|
||||
next->used = true;
|
||||
next->down = root;
|
||||
next->right = node = nodes[index++].Reset(RectI(root->rect.w, 0, w, root->rect.h));
|
||||
root = next;
|
||||
}
|
||||
// grow down
|
||||
else
|
||||
{
|
||||
Node* next = nodes[index++].Reset(RectI(0, 0, root->rect.w, root->rect.h + h));
|
||||
next->used = true;
|
||||
next->down = node = nodes[index++].Reset(RectI(0, root->rect.h, root->rect.w, h));
|
||||
next->right = root;
|
||||
root = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// doesn't fit
|
||||
if (node == nullptr)
|
||||
break;
|
||||
|
||||
// add
|
||||
node->used = true;
|
||||
node->down = nodes[index++].Reset(RectI(node->rect.x, node->rect.y + h, node->rect.w, node->rect.h - h));
|
||||
node->right = nodes[index++].Reset(RectI(node->rect.x + w, node->rect.y, node->rect.w - w, h));
|
||||
|
||||
sources[packed]->packed.x = node->rect.x + padding;
|
||||
sources[packed]->packed.y = node->rect.y + padding;
|
||||
packed++;
|
||||
}
|
||||
|
||||
// get page size
|
||||
int pageWidth, pageHeight;
|
||||
if (power_of_two)
|
||||
{
|
||||
pageWidth = 2;
|
||||
pageHeight = 2;
|
||||
while (pageWidth < root->rect.w)
|
||||
pageWidth *= 2;
|
||||
while (pageHeight < root->rect.h)
|
||||
pageHeight *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
pageWidth = root->rect.w;
|
||||
pageHeight = root->rect.h;
|
||||
}
|
||||
|
||||
// create each page
|
||||
{
|
||||
pages.emplace_back(pageWidth, pageHeight);
|
||||
|
||||
// copy image data to image
|
||||
for (int i = from; i < packed; i++)
|
||||
{
|
||||
sources[i]->page = page;
|
||||
if (!sources[i]->empty)
|
||||
{
|
||||
RectI dst = sources[i]->packed;
|
||||
Color* src = (Color*)(m_buffer.data() + sources[i]->memory_index);
|
||||
|
||||
// TODO:
|
||||
// Optimize this?
|
||||
if (padding > 0)
|
||||
{
|
||||
pages[page].set_pixels(RectI(dst.x - padding, dst.y, dst.w, dst.h), src);
|
||||
pages[page].set_pixels(RectI(dst.x + padding, dst.y, dst.w, dst.h), src);
|
||||
pages[page].set_pixels(RectI(dst.x, dst.y - padding, dst.w, dst.h), src);
|
||||
pages[page].set_pixels(RectI(dst.x, dst.y + padding, dst.w, dst.h), src);
|
||||
}
|
||||
|
||||
pages[page].set_pixels(dst, src);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Packer::clear()
|
||||
{
|
||||
pages.clear();
|
||||
entries.clear();
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void Packer::dispose()
|
||||
{
|
||||
pages.clear();
|
||||
entries.clear();
|
||||
m_buffer.close();
|
||||
max_size = 0;
|
||||
power_of_two = 0;
|
||||
spacing = 0;
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
Packer::Node::Node()
|
||||
: used(false), rect(0, 0, 0, 0), right(nullptr), down(nullptr) { }
|
||||
|
||||
Packer::Node* Packer::Node::Find(int w, int h)
|
||||
{
|
||||
if (used)
|
||||
{
|
||||
Packer::Node* r = right->Find(w, h);
|
||||
if (r != nullptr)
|
||||
return r;
|
||||
return down->Find(w, h);
|
||||
}
|
||||
else if (w <= rect.w && h <= rect.h)
|
||||
return this;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Packer::Node* Packer::Node::Reset(const RectI& rect)
|
||||
{
|
||||
used = false;
|
||||
this->rect = rect;
|
||||
right = nullptr;
|
||||
down = nullptr;
|
||||
return this;
|
||||
}
|
Reference in New Issue
Block a user