Compare commits

...

2 Commits

Author SHA1 Message Date
d9d974a285 ignroe images
Signed-off-by: Slendi <slendi@socopon.com>
2025-09-08 05:33:05 +03:00
23f5a4386e update
Signed-off-by: Slendi <slendi@socopon.com>
2025-09-08 05:32:43 +03:00
2 changed files with 316 additions and 433 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,6 @@ bfl
.cache .cache
.direnv .direnv
compile_commands.json compile_commands.json
*.png
*.bfl

735
bfl.cpp
View File

@@ -1,73 +1,58 @@
#include <cassert> #include <cassert>
#include <cstdint>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <optional> #include <optional>
#include <print> #include <print>
#include <span> #include <span>
#include <string_view>
#include <vector> #include <vector>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" #include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h" #include "stb_image_write.h"
// Helpers template <class T> inline void put_le(std::vector<uint8_t> &buf, T v) {
static_assert(std::is_unsigned_v<T>);
inline void store_le16(std::vector<uint8_t> &buf, uint16_t val) { for (size_t i{}; i < sizeof(T); i++)
buf.push_back(static_cast<uint8_t>(val & 0xFF)); buf.push_back(static_cast<uint8_t>((v >> (i * 8)) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 8) & 0xFF)); }
template <class T> inline T get_le(std::span<uint8_t const> s, size_t off) {
static_assert(std::is_unsigned_v<T>);
T v = 0;
for (size_t i{}; i < sizeof(T); i++)
v |= (static_cast<T>(s[off + i]) << (i * 8));
return v;
} }
inline void store_le32(std::vector<uint8_t> &buf, uint32_t val) { inline std::vector<uint8_t> read_file(std::filesystem::path const &p) {
buf.push_back(static_cast<uint8_t>(val & 0xFF)); std::ifstream f(p, std::ios::binary);
buf.push_back(static_cast<uint8_t>((val >> 8) & 0xFF)); return std::vector<uint8_t>((std::istreambuf_iterator<char>(f)),
buf.push_back(static_cast<uint8_t>((val >> 16) & 0xFF)); std::istreambuf_iterator<char>());
buf.push_back(static_cast<uint8_t>((val >> 24) & 0xFF)); }
inline void write_file(std::filesystem::path const &p,
std::span<uint8_t const> d) {
std::ofstream f(p, std::ios::binary);
f.write(reinterpret_cast<char const *>(d.data()),
static_cast<std::streamsize>(d.size()));
} }
inline void store_le64(std::vector<uint8_t> &buf, uint64_t val) { static std::vector<uint8_t> lzss_compress(std::span<uint8_t const> in) {
buf.push_back(static_cast<uint8_t>(val & 0xFF)); int const W{4096}, LA{18}, MIN{3};
buf.push_back(static_cast<uint8_t>((val >> 8) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 16) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 24) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 32) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 40) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 48) & 0xFF));
buf.push_back(static_cast<uint8_t>((val >> 56) & 0xFF));
}
static inline uint16_t read_le16(std::span<const uint8_t> s, size_t off) {
return static_cast<uint16_t>(s[off] | (s[off + 1] << 8));
}
static inline uint32_t read_le32(std::span<const uint8_t> s, size_t off) {
return static_cast<uint32_t>(s[off] | (s[off + 1] << 8) | (s[off + 2] << 16) |
(s[off + 3] << 24));
}
static std::vector<uint8_t> lzss_compress(std::span<const uint8_t> in) {
const int W = 4096;
const int LA = 18;
const int MIN = 3;
std::vector<uint8_t> out; std::vector<uint8_t> out;
size_t i = 0; out.reserve(in.size() / 8 + 16);
size_t i{};
while (i < in.size()) { while (i < in.size()) {
uint8_t flag = 0; uint8_t flag{};
size_t flag_pos = out.size(); size_t flag_pos{out.size()};
out.push_back(0); out.push_back(0);
for (int bit{}; bit < 8 && i < in.size(); bit++) {
for (int bit = 0; bit < 8 && i < in.size(); ++bit) { size_t best_len{}, best_off{};
size_t best_len = 0; size_t wnd_start{(i > (size_t)W) ? i - W : 0};
size_t best_off = 0; size_t max_len{std::min((size_t)LA, in.size() - i)};
for (size_t p{i}; p-- > wnd_start;) {
size_t wnd_start = (i > (size_t)W) ? i - W : 0; size_t l{};
size_t max_len = std::min((size_t)LA, in.size() - i);
for (size_t p = i; p-- > wnd_start;) {
size_t l = 0;
while (l < max_len && in[p + l] == in[i + l]) while (l < max_len && in[p + l] == in[i + l])
l++; l++;
if (l >= (size_t)MIN && l > best_len) { if (l >= (size_t)MIN && l > best_len) {
@@ -77,157 +62,188 @@ static std::vector<uint8_t> lzss_compress(std::span<const uint8_t> in) {
break; break;
} }
} }
if (best_len >= (size_t)MIN && best_off >= 1 && best_off <= 4095) { if (best_len >= (size_t)MIN && best_off >= 1 && best_off <= 4095) {
uint8_t b0 = uint8_t b0 =
static_cast<uint8_t>(((best_len - 3) << 4) | (best_off >> 8)); static_cast<uint8_t>(((best_len - 3) << 4) | (best_off >> 8));
uint8_t b1 = static_cast<uint8_t>(best_off & 0xFF); uint8_t b1{static_cast<uint8_t>(best_off & 0xFF)};
out.push_back(b0); out.push_back(b0);
out.push_back(b1); out.push_back(b1);
i += best_len; i += best_len;
} else { } else {
flag |= (1u << bit); flag |= (1u << bit);
out.push_back(in[i]); out.push_back(in[i++]);
++i;
} }
} }
out[flag_pos] = flag; out[flag_pos] = flag;
} }
return out; return out;
} }
static std::vector<uint8_t> lzss_decompress(std::span<uint8_t const> in) {
// "Library"
constexpr uint8_t FLAG_HAS_ALPHA = 0x01;
constexpr uint8_t FLAG_IMG_RAW = 0x02; // pre-LZSS image was RAW bitpack
constexpr uint8_t FLAG_TRA_RAW = 0x04; // pre-LZSS mask was RAW bitpack
constexpr uint8_t FLAG_IMG_NOLZ = 0x08; // image stream stored without LZSS
constexpr uint8_t FLAG_TRA_NOLZ = 0x10; // mask stream stored without LZSS
static std::vector<uint8_t>
rle_encode_with_transparency(const std::vector<bool> &data, bool initial,
const std::vector<bool> *transparency,
bool allow_trick) {
std::vector<uint8_t> out; std::vector<uint8_t> out;
out.push_back(static_cast<uint8_t>(initial)); out.reserve(in.size() * 2);
size_t i{};
bool state = initial;
size_t count = 0;
size_t i = 0;
auto flush_emit = [&](uint8_t n) { out.push_back(n); };
while (i < data.size()) {
bool is_transparent = transparency && (*transparency)[i];
if (allow_trick && is_transparent) {
while (i < data.size() && (*transparency)[i]) {
if (count == 255) {
flush_emit(255);
state = !state;
count = 0;
}
++count;
++i;
}
continue;
}
bool bit = data[i];
if (bit == state) {
++count;
++i;
if (count == 255 && i < data.size()) {
flush_emit(255);
out.push_back(0);
count = 0;
}
} else {
flush_emit(static_cast<uint8_t>(count));
state = !state;
count = 1;
++i;
}
}
if (count > 0)
flush_emit(static_cast<uint8_t>(count));
return out;
}
static std::vector<uint8_t> lzss_decompress(std::span<const uint8_t> in) {
std::vector<uint8_t> out;
size_t i = 0;
while (i < in.size()) { while (i < in.size()) {
uint8_t flag = in[i++]; uint8_t flag{in[i++]};
for (int bit = 0; bit < 8 && i < in.size(); ++bit) { for (int bit{}; bit < 8 && i < in.size(); bit++) {
bool is_lit = (flag >> bit) & 1; if ((flag >> bit) & 1) {
if (is_lit) {
out.push_back(in[i++]); out.push_back(in[i++]);
} else { } else {
if (i + 1 >= in.size()) if (i + 1 >= in.size())
return out; return out;
uint8_t b0 = in[i++]; uint8_t b0{in[i++]}, b1{in[i++]};
uint8_t b1 = in[i++]; size_t len{static_cast<size_t>((b0 >> 4) + 3)};
size_t len = (b0 >> 4) + 3; size_t off{static_cast<size_t>(((b0 & 0x0F) << 8) | b1)};
size_t off = ((b0 & 0x0F) << 8) | b1; // 1..4095
if (off == 0 || off > out.size()) if (off == 0 || off > out.size())
return out; // invalid return out;
size_t src = out.size() - off; size_t src{out.size() - off};
for (size_t k = 0; k < len; ++k) { for (size_t k{}; k < len; k++)
out.push_back(out[src + k]); out.push_back(out[src + k]);
} }
} }
} }
}
return out; return out;
} }
static std::vector<bool> rle_decode(std::span<const uint8_t> in, enum : uint8_t {
FLAG_HAS_ALPHA = 0x01,
FLAG_IMG_RAW = 0x02,
FLAG_TRA_RAW = 0x04,
FLAG_IMG_NOLZ = 0x08,
FLAG_TRA_NOLZ = 0x10,
};
static std::vector<uint8_t> bits_to_bytes(std::vector<bool> const &data) {
std::vector<uint8_t> bytes((data.size() + 7) / 8, 0);
for (size_t i{}; i < data.size(); i++)
if (data[i])
bytes[i / 8] |= (1u << (i % 8));
return bytes;
}
static std::vector<bool> decode_raw(std::span<uint8_t const> in,
size_t total_bits) {
std::vector<bool> out(total_bits, false);
for (size_t i{}; i < total_bits; i++) {
uint8_t byte = (i / 8 < in.size()) ? in[i / 8] : 0;
out[i] = ((byte >> (i % 8)) & 1) != 0;
}
return out;
}
static std::vector<uint8_t>
rle_encode_with_transparency(std::vector<bool> const &data, bool initial,
std::vector<bool> const *transparency,
bool allow_trick) {
std::vector<uint8_t> out;
out.reserve(data.size() / 4 + 8);
out.push_back(static_cast<uint8_t>(initial));
bool state{initial};
size_t count{}, i{};
auto flush{[&](uint8_t n) { out.push_back(n); }};
while (i < data.size()) {
bool is_transparent{transparency && (*transparency)[i]};
if (allow_trick && is_transparent) {
while (i < data.size() && (*transparency)[i]) {
if (count == 255) {
flush(255);
state = !state;
count = 0;
}
count++;
i++;
}
continue;
}
bool bit{data[i]};
if (bit == state) {
count++;
i++;
if (count == 255 && i < data.size()) {
flush(255);
out.push_back(0);
count = 0;
}
} else {
flush(static_cast<uint8_t>(count));
state = !state;
count = 1;
i++;
}
}
if (count)
flush(static_cast<uint8_t>(count));
return out;
}
static std::vector<bool> rle_decode(std::span<uint8_t const> in,
size_t total_bits) { size_t total_bits) {
std::vector<bool> out(total_bits, false); std::vector<bool> out(total_bits, false);
if (in.size() < 2) if (in.size() < 2)
return out; return out;
bool state = in[0] != 0; bool state{in[0] != 0};
size_t produced = 0; size_t produced{0}, j{1};
size_t j = 1;
while (produced < total_bits && j < in.size()) { while (produced < total_bits && j < in.size()) {
uint8_t n = in[j++]; uint8_t n{in[j++]};
if (n == 0)
if (n == 0) {
continue; continue;
} size_t emit{std::min<size_t>(n, total_bits - produced)};
for (size_t k{}; k < emit; k++)
size_t emit = std::min<size_t>(n, total_bits - produced);
for (size_t k = 0; k < emit; ++k)
out[produced + k] = state; out[produced + k] = state;
produced += emit; produced += emit;
if (j < in.size() && in[j] == 0)
if (j < in.size() && in[j] == 0) { j++;
++j; else
} else {
state = !state; state = !state;
} }
}
return out; return out;
} }
static std::vector<bool> raw_decode(std::span<const uint8_t> in, struct BFLHeader {
size_t total_bits) { uint16_t w{}, h{};
std::vector<bool> out(total_bits, false); uint8_t flags{};
for (size_t i = 0; i < total_bits; ++i) { uint32_t img_len{}, tra_len{};
uint8_t byte = (i / 8 < in.size()) ? in[i / 8] : 0; };
out[i] = ((byte >> (i % 8)) & 1) != 0;
} struct BFLView {
return out; BFLHeader hdr{};
std::span<uint8_t const> img_c{};
std::span<uint8_t const> tra_c{};
};
static std::optional<BFLView> parse_bfl(std::span<uint8_t const> data) {
if (data.size() < 3 + 2 + 2 + 1 + 4 + 4)
return std::nullopt;
if (!(data[0] == 'B' && data[1] == 'F' && data[2] == 'L'))
return std::nullopt;
size_t off{3};
BFLView v;
v.hdr.w = get_le<uint16_t>(data, off);
off += 2;
v.hdr.h = get_le<uint16_t>(data, off);
off += 2;
v.hdr.flags = data[off++];
v.hdr.img_len = get_le<uint32_t>(data, off);
off += 4;
v.hdr.tra_len = get_le<uint32_t>(data, off);
off += 4;
if (off + v.hdr.img_len > data.size())
return std::nullopt;
if (off + v.hdr.img_len + v.hdr.tra_len > data.size())
return std::nullopt;
v.img_c = data.subspan(off, v.hdr.img_len);
off += v.hdr.img_len;
v.tra_c = data.subspan(off, v.hdr.tra_len);
return v;
} }
struct Bitmap { struct Bitmap {
@@ -235,261 +251,149 @@ struct Bitmap {
std::vector<bool> image_data; std::vector<bool> image_data;
std::optional<std::vector<bool>> transparency_data; std::optional<std::vector<bool>> transparency_data;
auto to_rgba() -> std::vector<uint32_t>; std::vector<uint32_t> to_rgba() const {
auto encode() -> std::vector<uint8_t> { auto mk{[](uint8_t r, uint8_t g, uint8_t b, uint8_t a) -> uint32_t {
auto const raw_encode = return (uint32_t)r | ((uint32_t)g << 8) | ((uint32_t)b << 16) |
[](std::vector<bool> const &data) -> std::vector<uint8_t> { ((uint32_t)a << 24);
std::vector<uint8_t> bytes((data.size() + 7) / 8, 0); }};
for (size_t i = 0; i < data.size(); ++i) { size_t n{(size_t)width * (size_t)height};
if (data[i]) std::vector<uint32_t> out(n, 0);
bytes[i / 8] |= (1u << (i % 8)); for (size_t i{}; i < n; i++) {
bool tr{transparency_data && i < transparency_data->size() &&
(*transparency_data)[i]};
bool wh{i < image_data.size() && image_data[i]};
out[i] = tr ? mk(0, 0, 0, 0)
: (wh ? mk(255, 255, 255, 255) : mk(0, 0, 0, 255));
}
return out;
} }
return bytes;
};
std::vector<uint8_t> ret; std::vector<uint8_t> encode() const {
auto choose_rle_or_raw{[&](std::vector<bool> const &bits, bool &raw_flag,
// header
ret.push_back('B');
ret.push_back('F');
ret.push_back('L');
store_le16(ret, width);
store_le16(ret, height);
auto encode_best = [&](std::vector<bool> const &data, bool &raw_flag,
bool allow_trick) { bool allow_trick) {
const std::vector<bool> *mask = std::vector<bool> const *mask{transparency_data ? &(*transparency_data)
transparency_data ? &(*transparency_data) : nullptr; : nullptr};
auto a{rle_encode_with_transparency(bits, false, mask, allow_trick)};
auto init_false = auto b{rle_encode_with_transparency(bits, true, mask, allow_trick)};
rle_encode_with_transparency(data, false, mask, allow_trick); std::vector<uint8_t> best{(a.size() <= b.size()) ? std::move(a)
auto init_true = : std::move(b)};
rle_encode_with_transparency(data, true, mask, allow_trick); if (best.size() * 8 > bits.size()) {
best = bits_to_bytes(bits);
std::vector<uint8_t> result = (init_false.size() <= init_true.size())
? std::move(init_false)
: std::move(init_true);
if (result.size() * 8 > data.size()) {
result = raw_encode(data);
raw_flag = true; raw_flag = true;
} }
return result; return best;
}; }};
std::vector<uint8_t> image_bytes, trans_bytes; std::vector<uint8_t> out;
bool image_raw = false, trans_raw = false; out.push_back('B');
out.push_back('F');
out.push_back('L');
put_le<uint16_t>(out, width);
put_le<uint16_t>(out, height);
image_bytes = bool img_raw{}, tra_raw{};
encode_best(image_data, image_raw, transparency_data.has_value()); auto img_bytes =
if (transparency_data) { choose_rle_or_raw(image_data, img_raw, transparency_data.has_value());
trans_bytes = encode_best(*transparency_data, trans_raw, false); std::vector<uint8_t> tra_bytes;
} if (transparency_data)
tra_bytes = choose_rle_or_raw(*transparency_data, tra_raw, false);
bool img_no_lz = false, tra_no_lz = false;
std::vector<uint8_t> img_stream_lz = lzss_compress(image_bytes);
std::vector<uint8_t> img_stream =
(img_stream_lz.size() < image_bytes.size())
? std::move(img_stream_lz)
: (img_no_lz = true, image_bytes);
auto maybe_lz{[](std::vector<uint8_t> v, bool &no_lz) {
auto c{lzss_compress(v)};
if (c.size() < v.size())
return c;
no_lz = true;
return v;
}};
bool img_no_lz{}, tra_no_lz{};
auto img_stream{maybe_lz(std::move(img_bytes), img_no_lz)};
std::vector<uint8_t> tra_stream; std::vector<uint8_t> tra_stream;
if (!trans_bytes.empty()) { if (!tra_bytes.empty())
auto tra_stream_lz = lzss_compress(trans_bytes); tra_stream = maybe_lz(std::move(tra_bytes), tra_no_lz);
tra_stream = (tra_stream_lz.size() < trans_bytes.size())
? std::move(tra_stream_lz)
: (tra_no_lz = true, trans_bytes);
}
// flags uint8_t flags{};
uint8_t flags = 0;
if (transparency_data) if (transparency_data)
flags |= FLAG_HAS_ALPHA; flags |= FLAG_HAS_ALPHA;
if (image_raw) if (img_raw)
flags |= FLAG_IMG_RAW; flags |= FLAG_IMG_RAW;
if (trans_raw) if (tra_raw)
flags |= FLAG_TRA_RAW; flags |= FLAG_TRA_RAW;
if (img_no_lz) if (img_no_lz)
flags |= FLAG_IMG_NOLZ; flags |= FLAG_IMG_NOLZ;
if (tra_no_lz) if (tra_no_lz)
flags |= FLAG_TRA_NOLZ; flags |= FLAG_TRA_NOLZ;
ret.push_back(flags);
store_le32(ret, static_cast<uint32_t>(img_stream.size())); out.push_back(flags);
store_le32(ret, static_cast<uint32_t>(tra_stream.size())); put_le<uint32_t>(out, static_cast<uint32_t>(img_stream.size()));
put_le<uint32_t>(out, static_cast<uint32_t>(tra_stream.size()));
// payload out.insert(out.end(), img_stream.begin(), img_stream.end());
ret.insert(ret.end(), img_stream.begin(), img_stream.end()); out.insert(out.end(), tra_stream.begin(), tra_stream.end());
ret.insert(ret.end(), tra_stream.begin(), tra_stream.end()); return out;
return ret;
} }
static auto from_rgba(std::span<uint32_t const> const &data, int width, static Bitmap from_rgba(std::span<uint32_t const> data, int w, int h) {
int height) -> Bitmap;
static auto decode(std::span<uint8_t const> const &data) -> Bitmap;
};
auto Bitmap::decode(std::span<uint8_t const> const &data) -> Bitmap {
Bitmap bm{};
if (data.size() < 3 + 2 + 2 + 1 + 4 + 4)
return bm;
if (!(data[0] == 'B' && data[1] == 'F' && data[2] == 'L'))
return bm;
size_t off = 3;
uint16_t w = read_le16(data, off);
off += 2;
uint16_t h = read_le16(data, off);
off += 2;
uint8_t flags = data[off++];
uint32_t img_c_len = read_le32(data, off);
off += 4;
uint32_t tra_c_len = read_le32(data, off);
off += 4;
if (off + img_c_len > data.size())
return bm;
if (off + img_c_len + tra_c_len > data.size())
return bm;
auto img_c = data.subspan(off, img_c_len);
off += img_c_len;
auto tra_c = data.subspan(off, tra_c_len);
bool has_alpha = (flags & FLAG_HAS_ALPHA) != 0;
bool img_was_raw = (flags & FLAG_IMG_RAW) != 0;
bool tra_was_raw = (flags & FLAG_TRA_RAW) != 0;
bool img_no_lz = (flags & FLAG_IMG_NOLZ) != 0;
bool tra_no_lz = (flags & FLAG_TRA_NOLZ) != 0;
std::vector<uint8_t> img_stream =
img_no_lz ? std::vector<uint8_t>(img_c.begin(), img_c.end())
: lzss_decompress(img_c);
std::vector<uint8_t> tra_stream =
(has_alpha ? (tra_no_lz ? std::vector<uint8_t>(tra_c.begin(), tra_c.end())
: lzss_decompress(tra_c))
: std::vector<uint8_t>{});
size_t total_bits = static_cast<size_t>(w) * static_cast<size_t>(h);
std::vector<bool> img_bits = img_was_raw ? raw_decode(img_stream, total_bits)
: rle_decode(img_stream, total_bits);
std::optional<std::vector<bool>> tra_bits;
if (has_alpha) {
tra_bits = (tra_was_raw ? raw_decode(tra_stream, total_bits)
: rle_decode(tra_stream, total_bits));
}
bm.width = w;
bm.height = h;
bm.image_data = std::move(img_bits);
bm.transparency_data = std::move(tra_bits);
return bm;
}
auto Bitmap::from_rgba(std::span<uint32_t const> const &data, int w, int h)
-> Bitmap {
assert(w > 0 && h > 0); assert(w > 0 && h > 0);
assert(static_cast<int>(data.size()) == w * h); assert(static_cast<int>(data.size()) == w * h);
Bitmap bm; Bitmap bm;
bm.width = w; bm.width = (uint16_t)w;
bm.height = h; bm.height = (uint16_t)h;
bm.image_data.resize(static_cast<size_t>(w) * static_cast<size_t>(h)); size_t n{(size_t)w * (size_t)h};
bm.transparency_data = std::vector<bool>{}; bm.image_data.resize(n);
bm.transparency_data->resize(static_cast<size_t>(w) * static_cast<size_t>(h)); bm.transparency_data.emplace();
bm.transparency_data->resize(n);
for (size_t i = 0; i < data.size(); ++i) { for (size_t i{}; i < n; i++) {
uint32_t px = data[i]; uint32_t px{data[i]};
uint8_t r = (px >> 0) & 0xFF, g = (px >> 8) & 0xFF, b = (px >> 16) & 0xFF,
// little-endian: 0xAABBGGRR a = (px >> 24) & 0xFF;
uint8_t r = static_cast<uint8_t>((px >> 0) & 0xFF); bool white{(r == 255 && g == 255 && b == 255 && a != 0)};
uint8_t g = static_cast<uint8_t>((px >> 8) & 0xFF); bool black{(r == 0 && g == 0 && b == 0 && a != 0)};
uint8_t b = static_cast<uint8_t>((px >> 16) & 0xFF); bool tr{(!white && !black) || (a == 0)};
uint8_t a = static_cast<uint8_t>((px >> 24) & 0xFF); (*bm.transparency_data)[i] = tr;
bm.image_data[i] = white;
bool is_white = (r == 255 && g == 255 && b == 255 && a != 0);
bool is_black = (r == 0 && g == 0 && b == 0 && a != 0);
bool is_transparent = (!is_white && !is_black) || (a == 0);
(*bm.transparency_data)[i] = is_transparent;
bm.image_data[i] = is_white;
} }
return bm; return bm;
} }
auto Bitmap::to_rgba() -> std::vector<uint32_t> { static Bitmap decode(std::span<uint8_t const> data) {
std::vector<uint32_t> out; Bitmap bm{};
out.resize(static_cast<size_t>(width) * static_cast<size_t>(height)); auto view{parse_bfl(data)};
if (!view)
return bm;
auto make_rgba = [](uint8_t r, uint8_t g, uint8_t b, uint8_t a) -> uint32_t { auto &h{view->hdr};
return (static_cast<uint32_t>(r) << 0) | (static_cast<uint32_t>(g) << 8) | bool has_alpha{(h.flags & FLAG_HAS_ALPHA) != 0};
(static_cast<uint32_t>(b) << 16) | (static_cast<uint32_t>(a) << 24); bool img_was_raw{(h.flags & FLAG_IMG_RAW) != 0};
bool tra_was_raw{(h.flags & FLAG_TRA_RAW) != 0};
bool img_no_lz{(h.flags & FLAG_IMG_NOLZ) != 0};
bool tra_no_lz{(h.flags & FLAG_TRA_NOLZ) != 0};
std::vector<uint8_t> img_stream =
img_no_lz ? std::vector<uint8_t>(view->img_c.begin(), view->img_c.end())
: lzss_decompress(view->img_c);
std::vector<uint8_t> tra_stream =
has_alpha ? (tra_no_lz ? std::vector<uint8_t>(view->tra_c.begin(),
view->tra_c.end())
: lzss_decompress(view->tra_c))
: std::vector<uint8_t>{};
size_t total_bits{(size_t)h.w * (size_t)h.h};
auto img_bits{img_was_raw ? decode_raw(img_stream, total_bits)
: rle_decode(img_stream, total_bits)};
std::optional<std::vector<bool>> tra_bits;
if (has_alpha)
tra_bits = tra_was_raw ? decode_raw(tra_stream, total_bits)
: rle_decode(tra_stream, total_bits);
bm.width = h.w;
bm.height = h.h;
bm.image_data = std::move(img_bits);
bm.transparency_data = std::move(tra_bits);
return bm;
}
}; };
for (size_t i = 0; i < out.size(); ++i) {
if ((transparency_data && i >= transparency_data->size()) ||
i >= image_data.size()) {
out[i] = make_rgba(0, 0, 0, 0); // safety
continue;
}
if (transparency_data && (*transparency_data)[i]) {
out[i] = make_rgba(0, 0, 0, 0);
} else {
if (image_data[i]) {
out[i] = make_rgba(255, 255, 255, 255);
} else {
out[i] = make_rgba(0, 0, 0, 255);
}
}
}
return out;
}
struct BFLHeader {
uint16_t w{};
uint16_t h{};
uint8_t flags{};
uint32_t img_len{};
uint32_t tra_len{};
};
static bool parse_bfl_header(std::span<const uint8_t> data, BFLHeader &hdr) {
// minimal header: "BFL" + w + h + flags + lenI + lenT
if (data.size() < 3 + 2 + 2 + 1 + 4 + 4)
return false;
if (!(data[0] == 'B' && data[1] == 'F' && data[2] == 'L'))
return false;
size_t off = 3;
hdr.w = read_le16(data, off);
off += 2;
hdr.h = read_le16(data, off);
off += 2;
hdr.flags = data[off++];
hdr.img_len = read_le32(data, off);
off += 4;
hdr.tra_len = read_le32(data, off);
off += 4;
if (off + hdr.img_len > data.size())
return false;
if (off + hdr.img_len + hdr.tra_len > data.size())
return false;
return true;
}
static void print_bfl_header(BFLHeader const &h) { static void print_bfl_header(BFLHeader const &h) {
std::println("BFL header:"); std::println("BFL header:");
std::println(" width : {}", h.w); std::println(" width : {}", h.w);
@@ -504,23 +408,7 @@ static void print_bfl_header(BFLHeader const &h) {
std::println(" alpha bytes : {}", h.tra_len); std::println(" alpha bytes : {}", h.tra_len);
} }
// CLI int main(int argc, char const *argv[]) {
auto read_entire_file(std::filesystem::path const &path)
-> std::vector<uint8_t> {
std::ifstream stream(path, std::ios::in | std::ios::binary);
std::vector<uint8_t> const contents((std::istreambuf_iterator<char>(stream)),
std::istreambuf_iterator<char>());
return contents;
}
auto write_entire_file(std::filesystem::path const &path,
std::span<uint8_t const> const &data) -> void {
std::ofstream stream(path, std::ios::out | std::ios::binary);
stream.write(reinterpret_cast<char const *>(data.data()), data.size());
}
auto main(int const argc, char const *argv[]) -> int {
if (argc < 2) { if (argc < 2) {
std::println(stderr, std::println(stderr,
"Usage:\n" "Usage:\n"
@@ -531,14 +419,13 @@ auto main(int const argc, char const *argv[]) -> int {
} }
if (argc == 3 && std::string_view(argv[1]) == "header") { if (argc == 3 && std::string_view(argv[1]) == "header") {
std::filesystem::path filepath{argv[2]}; auto bytes{read_file(argv[2])};
auto bytes = read_entire_file(filepath); auto v{parse_bfl(bytes)};
BFLHeader hdr{}; if (!v) {
if (!parse_bfl_header(bytes, hdr)) { std::println(stderr, "Invalid or truncated BFL: {}", argv[2]);
std::println(stderr, "Invalid or truncated BFL: {}", filepath.string());
return 2; return 2;
} }
print_bfl_header(hdr); print_bfl_header(v->hdr);
return 0; return 0;
} }
@@ -551,37 +438,31 @@ auto main(int const argc, char const *argv[]) -> int {
return 1; return 1;
} }
Bitmap input; std::filesystem::path in{argv[1]}, outp{argv[2]};
Bitmap bmp;
std::filesystem::path output_filepath{argv[2]}; if (in.extension() == ".bfl") {
std::filesystem::path filepath{argv[1]}; bmp = Bitmap::decode(read_file(in));
if (filepath.extension() == ".bfl") {
input = Bitmap::decode(read_entire_file(filepath));
} else { } else {
int width, height, comp; int w, h, comp;
unsigned char *raw_data = unsigned char *raw{stbi_load(in.string().c_str(), &w, &h, &comp, 4)};
stbi_load(filepath.string().c_str(), &width, &height, &comp, 4); if (!raw) {
if (!raw_data) { std::println(stderr, "stbi_load failed on {}", in.string());
std::println(stderr, "stbi_load failed on {}", filepath.string());
return 1; return 1;
} }
std::span<uint32_t const> px{reinterpret_cast<uint32_t const *>(raw),
std::span<uint32_t const> data{reinterpret_cast<uint32_t const *>(raw_data), (size_t)w * (size_t)h};
static_cast<size_t>(width) * bmp = Bitmap::from_rgba(px, w, h);
static_cast<size_t>(height)}; stbi_image_free(raw);
input = Bitmap::from_rgba(data, width, height);
stbi_image_free(raw_data);
} }
if (output_filepath.extension() == ".bfl") { if (outp.extension() == ".bfl") {
auto const input_bin = input.encode(); auto bin{bmp.encode()};
std::span<uint8_t const> d{input_bin.data(), input_bin.size()}; write_file(outp, std::span<uint8_t const>(bin.data(), bin.size()));
write_entire_file(output_filepath, d);
} else { } else {
auto rgba = input.to_rgba(); auto rgba{bmp.to_rgba()};
stbi_write_png(output_filepath.string().c_str(), input.width, input.height, stbi_write_png(outp.string().c_str(), bmp.width, bmp.height, 4, rgba.data(),
4, rgba.data(), input.width * 4); bmp.width * 4);
} }
return 0; return 0;
} }