use new phosg::Image class

This commit is contained in:
Martin Michelsen
2025-07-01 09:56:42 -07:00
parent 072e647c7b
commit 0a5065707c
8 changed files with 65 additions and 87 deletions
+2
View File
@@ -14,6 +14,7 @@ CTestTestfile.cmake
install_manifest.txt install_manifest.txt
Makefile Makefile
Testing Testing
build
# Files modified by the user and/or server that don't have defaults # Files modified by the user and/or server that don't have defaults
system/config.json system/config.json
@@ -35,6 +36,7 @@ system/patch-bb/.metadata-cache.json
# repository # repository
files files
make_release.py make_release.py
notes-private
old-khyller old-khyller
old-newserv old-newserv
release release
+10 -11
View File
@@ -35,7 +35,7 @@ struct GVRHeader {
be_uint16_t height; be_uint16_t height;
} __packed_ws__(GVRHeader, 0x10); } __packed_ws__(GVRHeader, 0x10);
string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const string& internal_name, uint32_t global_index) { string encode_gvm(const phosg::ImageRGBA8888& img, GVRDataFormat data_format, const string& internal_name, uint32_t global_index) {
int8_t dimensions_field = -2; int8_t dimensions_field = -2;
{ {
size_t h = img.get_height(); size_t h = img.get_height();
@@ -90,17 +90,16 @@ string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const stri
for (size_t x = 0; x < img.get_width(); x += 4) { for (size_t x = 0; x < img.get_width(); x += 4) {
for (size_t yy = 0; yy < 4; yy++) { for (size_t yy = 0; yy < 4; yy++) {
for (size_t xx = 0; xx < 4; xx++) { for (size_t xx = 0; xx < 4; xx++) {
uint64_t a, r, g, b; uint32_t c = img.read(x + xx, y + yy);
img.read_pixel(x + xx, y + yy, &r, &g, &b, &a);
switch (data_format) { switch (data_format) {
case GVRDataFormat::RGB565: case GVRDataFormat::RGB565:
w.put_u16b(encode_rgb565(r, g, b)); w.put_u16b(phosg::rgb565_for_rgba8888(c));
break; break;
case GVRDataFormat::RGB5A3: case GVRDataFormat::RGB5A3:
w.put_u16b(encode_rgb5a3(r, g, b, a)); w.put_u16b(encode_rgb5a3(c));
break; break;
case GVRDataFormat::ARGB8888: case GVRDataFormat::ARGB8888:
w.put_u32b(encode_argb8888(r, g, b, a)); w.put_u32b(phosg::argb8888_for_rgba8888(c));
break; break;
default: default:
throw logic_error("cannot encode pixel format"); throw logic_error("cannot encode pixel format");
@@ -115,15 +114,15 @@ string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const stri
static const array<uint32_t, 4> fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF}; static const array<uint32_t, 4> fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF};
phosg::Image decode_fon(const string& data, size_t width) { phosg::ImageRGB888 decode_fon(const string& data, size_t width) {
size_t num_pixels = data.size() * 4; size_t num_pixels = data.size() * 4;
size_t height = num_pixels / width; size_t height = num_pixels / width;
phosg::Image ret(width, height); phosg::ImageRGB888 ret(width, height);
phosg::BitReader r(data); phosg::BitReader r(data);
for (size_t y = 0; y < height; y++) { for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) { for (size_t x = 0; x < width; x++) {
ret.write_pixel(x, y, fon_colors[r.read(2)]); ret.write(x, y, fon_colors[r.read(2)]);
} }
} }
return ret; return ret;
@@ -133,11 +132,11 @@ constexpr size_t uabs(size_t a, size_t b) {
return (a > b) ? (a - b) : (b - a); return (a > b) ? (a - b) : (b - a);
} }
string encode_fon(const phosg::Image& img) { string encode_fon(const phosg::ImageRGB888& img) {
phosg::BitWriter w; phosg::BitWriter w;
for (size_t y = 0; y < img.get_height(); y++) { for (size_t y = 0; y < img.get_height(); y++) {
for (size_t x = 0; x < img.get_width(); x++) { for (size_t x = 0; x < img.get_width(); x++) {
uint32_t color = img.read_pixel(x, y); uint32_t color = img.read(x, y);
size_t result_delta = 0x400; size_t result_delta = 0x400;
size_t result_index = 0; size_t result_index = 0;
+20 -34
View File
@@ -19,43 +19,29 @@ enum class GVRDataFormat : uint8_t {
DXT1 = 0x0E, DXT1 = 0x0E,
}; };
std::string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index); std::string encode_gvm(
phosg::Image decode_fon(const std::string& data, size_t width); const phosg::ImageRGBA8888& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index);
std::string encode_fon(const phosg::Image& img); phosg::ImageRGB888 decode_fon(const std::string& data, size_t width);
std::string encode_fon(const phosg::ImageRGB888& img);
constexpr uint16_t encode_rgb565(uint8_t r, uint8_t g, uint8_t b) { constexpr uint16_t encode_rgb5a3(uint32_t c) {
return ((r << 8) & 0xF800) | ((g << 3) & 0x07E0) | ((b >> 3) & 0x001F); if ((phosg::get_a(c) & 0xE0) == 0xE0) {
} return 0x8000 | ((phosg::get_r(c) << 7) & 0x7C00) | ((phosg::get_g(c) << 2) & 0x03E0) | ((phosg::get_b(c) >> 3) & 0x001F);
constexpr uint16_t encode_rgb5a3(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
if ((a & 0xE0) == 0xE0) {
return 0x8000 | ((r << 7) & 0x7C00) | ((g << 2) & 0x03E0) | ((b >> 3) & 0x001F);
} else { } else {
return ((a << 7) & 0x7000) | ((r << 4) & 0x0F00) | (g & 0x00F0) | ((b >> 4) & 0x000F); return ((phosg::get_a(c) << 7) & 0x7000) | ((phosg::get_r(c) << 4) & 0x0F00) | (phosg::get_g(c) & 0x00F0) | ((phosg::get_b(c) >> 4) & 0x000F);
} }
} }
constexpr uint32_t encode_argb8888(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { template <phosg::PixelFormat Format>
return (a << 24) | (r << 16) | (g << 8) | b; bool has_any_transparent_pixels(const phosg::Image<Format>& img) {
} if constexpr (phosg::Image<Format>::HAS_ALPHA) {
for (size_t y = 0; y < img.get_height(); y++) {
constexpr uint16_t encode_argb8888_to_argb1555(uint32_t argb8888) { for (size_t x = 0; x < img.get_height(); x++) {
// In: aaaaaaaarrrrrrrrggggggggbbbbbbbb if (phosg::get_a(img.read(x, y)) != 0xFF) {
// Out: arrrrrgggggbbbbb return true;
return ((argb8888 >> 9) & 0x7C00) | ((argb8888 >> 6) & 0x03E0) | ((argb8888 >> 3) & 0x001F) | ((argb8888 >> 16) & 0x8000); }
} }
}
constexpr uint16_t encode_rgba8888_to_argb1555(uint32_t rgba8888) { }
// In: rrrrrrrrggggggggbbbbbbbbaaaaaaaa return false;
// Out: arrrrrgggggbbbbb
return ((rgba8888 >> 17) & 0x7C00) | ((rgba8888 >> 14) & 0x03E0) | ((rgba8888 >> 11) & 0x001F) | ((rgba8888 << 8) & 0x8000);
}
constexpr uint32_t decode_argb1555_to_rgba8888(uint16_t argb1555) {
// In: arrrrrgggggbbbbb
// Out: rrrrrrrrggggggggbbbbbbbbaaaaaaaa
return ((argb1555 << 17) & 0xF8000000) | ((argb1555 << 12) & 0x07000000) |
((argb1555 << 14) & 0x00F80000) | ((argb1555 << 9) & 0x00070000) |
((argb1555 << 11) & 0x0000F800) | ((argb1555 << 6) & 0x00000700) |
((argb1555 & 0x8000) ? 0x000000FF : 0x00000000);
} }
+22 -21
View File
@@ -1166,7 +1166,7 @@ Action a_decode_gci_snapshot(
} }
auto img = file.decode_image(); auto img = file.decode_image();
string saved = img.save(phosg::Image::Format::WINDOWS_BITMAP); string saved = img.serialize(phosg::ImageFormat::WINDOWS_BITMAP);
write_output_data(args, saved.data(), saved.size(), "bmp"); write_output_data(args, saved.data(), saved.size(), "bmp");
}); });
@@ -1177,13 +1177,16 @@ Action a_encode_gvm(
GVM file can be used as an Episode 3 lobby banner.\n", GVM file can be used as an Episode 3 lobby banner.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
const string& input_filename = args.get<string>(1, false); const string& input_filename = args.get<string>(1, false);
phosg::Image img; string data;
if (!input_filename.empty() && (input_filename != "-")) { if (!input_filename.empty() && (input_filename != "-")) {
img = phosg::Image(input_filename); data = phosg::load_file(input_filename);
} else { } else {
img = phosg::Image(stdin); data = phosg::read_all(stdin);
} }
string encoded = encode_gvm(img, img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565, "image.gvr", 0); auto img = phosg::ImageRGBA8888::from_file_data(data);
// If the image has any transparent pixels at all, use RGB5A3
string encoded = encode_gvm(
img, has_any_transparent_pixels(img) ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565, "image.gvr", 0);
write_output_data(args, encoded.data(), encoded.size(), "gvm"); write_output_data(args, encoded.data(), encoded.size(), "gvm");
}); });
@@ -1196,7 +1199,7 @@ Action a_decode_bitmap_font(
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
std::string data = read_input_data(args); std::string data = read_input_data(args);
size_t width = args.get<size_t>("width"); size_t width = args.get<size_t>("width");
std::string bmp_data = decode_fon(data, width).save(phosg::Image::Format::WINDOWS_BITMAP); std::string bmp_data = decode_fon(data, width).serialize(phosg::ImageFormat::WINDOWS_BITMAP);
write_output_data(args, bmp_data.data(), bmp_data.size(), "bmp"); write_output_data(args, bmp_data.data(), bmp_data.size(), "bmp");
}); });
Action a_encode_bitmap_font( Action a_encode_bitmap_font(
@@ -1207,12 +1210,13 @@ Action a_encode_bitmap_font(
original fon\'s dimensions.\n", original fon\'s dimensions.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
const string& input_filename = args.get<string>(1, false); const string& input_filename = args.get<string>(1, false);
phosg::Image img; string data;
if (!input_filename.empty() && (input_filename != "-")) { if (!input_filename.empty() && (input_filename != "-")) {
img = phosg::Image(input_filename); data = phosg::load_file(input_filename);
} else { } else {
img = phosg::Image(stdin); data = phosg::read_all(stdin);
} }
auto img = phosg::ImageRGB888::from_file_data(data);
string encoded = encode_fon(img); string encoded = encode_fon(img);
write_output_data(args, encoded.data(), encoded.size(), "fon"); write_output_data(args, encoded.data(), encoded.size(), "fon");
}); });
@@ -2591,22 +2595,19 @@ Action a_generate_ep3_cards_html(
phosg::parallel_range<uint32_t>([&](uint32_t index, size_t) -> bool { phosg::parallel_range<uint32_t>([&](uint32_t index, size_t) -> bool {
auto& info = this->card_infos[index]; auto& info = this->card_infos[index];
if (!info.large_filename.empty()) { if (!info.large_filename.empty()) {
phosg::Image img(info.large_filename); auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(info.large_filename));
phosg::Image cropped(512, 399); img.resize(512, 399);
cropped.blit(img, 0, 0, 512, 399, 0, 0); info.large_data_url = img.serialize(phosg::ImageFormat::PNG_DATA_URL);
info.large_data_url = cropped.png_data_url();
} }
if (!info.medium_filename.empty()) { if (!info.medium_filename.empty()) {
phosg::Image img(info.medium_filename); auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(info.medium_filename));
phosg::Image cropped(184, 144); img.resize(184, 144);
cropped.blit(img, 0, 0, 184, 144, 0, 0); info.medium_data_url = img.serialize(phosg::ImageFormat::PNG_DATA_URL);
info.medium_data_url = cropped.png_data_url();
} }
if (!info.small_filename.empty()) { if (!info.small_filename.empty()) {
phosg::Image img(info.small_filename); auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(info.small_filename));
phosg::Image cropped(58, 43); img.resize(58, 43);
cropped.blit(img, 0, 0, 58, 43, 0, 0); info.small_data_url = img.serialize(phosg::ImageFormat::PNG_DATA_URL);
info.small_data_url = cropped.png_data_url();
} }
return false; return false;
}, },
+3 -13
View File
@@ -218,16 +218,7 @@ bool PSOGCSnapshotFile::checksum_correct() const {
return (crc == this->checksum); return (crc == this->checksum);
} }
static uint32_t decode_rgb565(uint16_t c) { phosg::ImageRGB888 PSOGCSnapshotFile::decode_image() const {
// Input bits: rrrrrggg gggbbbbb
// Output bits: rrrrrrrr gggggggg bbbbbbbb aaaaaaaa
return ((c << 16) & 0xF8000000) | ((c << 11) & 0x07000000) | // R
((c << 13) & 0x00FC0000) | ((c << 7) & 0x00030000) | // G
((c << 11) & 0x0000F800) | ((c << 6) & 0x00000700) | // B
0x000000FF; // A
}
phosg::Image PSOGCSnapshotFile::decode_image() const {
size_t width = this->width ? this->width.load() : 256; size_t width = this->width ? this->width.load() : 256;
size_t height = this->height ? this->height.load() : 192; size_t height = this->height ? this->height.load() : 192;
if (width != 256) { if (width != 256) {
@@ -238,14 +229,13 @@ phosg::Image PSOGCSnapshotFile::decode_image() const {
} }
// 4x4 blocks of pixels // 4x4 blocks of pixels
phosg::Image ret(width, height, false); phosg::ImageRGB888 ret(width, height);
size_t offset = 0; size_t offset = 0;
for (size_t y = 0; y < this->height; y += 4) { for (size_t y = 0; y < this->height; y += 4) {
for (size_t x = 0; x < this->width; x += 4) { for (size_t x = 0; x < this->width; x += 4) {
for (size_t yy = 0; yy < 4; yy++) { for (size_t yy = 0; yy < 4; yy++) {
for (size_t xx = 0; xx < 4; xx++) { for (size_t xx = 0; xx < 4; xx++) {
uint32_t color = decode_rgb565(this->pixels[offset++]); ret.write(x + xx, y + yy, phosg::rgba8888_for_rgb565(this->pixels[offset++]));
ret.write_pixel(x + xx, y + yy, color);
} }
} }
} }
+1 -1
View File
@@ -1066,7 +1066,7 @@ struct PSOGCSnapshotFile {
/* 1818C */ /* 1818C */
bool checksum_correct() const; bool checksum_correct() const;
phosg::Image decode_image() const; phosg::ImageRGB888 decode_image() const;
} __packed_ws__(PSOGCSnapshotFile, 0x1818C); } __packed_ws__(PSOGCSnapshotFile, 0x1818C);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
+2 -2
View File
@@ -966,10 +966,10 @@ void ServerState::load_config_early() {
} else if (lower_path.ends_with(".gvm")) { } else if (lower_path.ends_with(".gvm")) {
decompressed_gvm_data = phosg::load_file(path); decompressed_gvm_data = phosg::load_file(path);
} else if (lower_path.ends_with(".bmp")) { } else if (lower_path.ends_with(".bmp")) {
phosg::Image img(path); auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(path));
decompressed_gvm_data = encode_gvm( decompressed_gvm_data = encode_gvm(
img, img,
img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565, has_any_transparent_pixels(img) ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565,
std::format("bnr{}", banner_index), std::format("bnr{}", banner_index),
0x80 | banner_index); 0x80 | banner_index);
banner_index++; banner_index++;
+5 -5
View File
@@ -98,14 +98,14 @@ void TeamIndex::Team::save_config() const {
} }
void TeamIndex::Team::load_flag() { void TeamIndex::Team::load_flag() {
phosg::Image img(this->flag_filename()); auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(this->flag_filename()));
if (img.get_width() != 32 || img.get_height() != 32) { if (img.get_width() != 32 || img.get_height() != 32) {
throw runtime_error("incorrect flag image dimensions"); throw runtime_error("incorrect flag image dimensions");
} }
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>()); this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
for (size_t y = 0; y < 32; y++) { for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) { for (size_t x = 0; x < 32; x++) {
this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y)); this->flag_data->at(y * 0x20 + x) = phosg::argb1555_for_rgba8888(img.read(x, y));
} }
} }
} }
@@ -114,13 +114,13 @@ void TeamIndex::Team::save_flag() const {
if (!this->flag_data) { if (!this->flag_data) {
return; return;
} }
phosg::Image img(32, 32, true); phosg::ImageRGBA8888 img(32, 32);
for (size_t y = 0; y < 32; y++) { for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) { for (size_t x = 0; x < 32; x++) {
img.write_pixel(x, y, decode_argb1555_to_rgba8888(this->flag_data->at(y * 0x20 + x))); img.write(x, y, phosg::rgba8888_for_argb1555(this->flag_data->at(y * 0x20 + x)));
} }
} }
img.save(this->flag_filename(), phosg::Image::Format::WINDOWS_BITMAP); phosg::save_file(this->flag_filename(), img.serialize(phosg::ImageFormat::WINDOWS_BITMAP));
} }
void TeamIndex::Team::delete_files() const { void TeamIndex::Team::delete_files() const {