From 0a5065707c561b9d2a0b91a03c85c66412fbab49 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 1 Jul 2025 09:56:42 -0700 Subject: [PATCH] use new phosg::Image class --- .gitignore | 2 ++ src/ImageEncoder.cc | 21 ++++++++-------- src/ImageEncoder.hh | 54 ++++++++++++++++-------------------------- src/Main.cc | 43 +++++++++++++++++---------------- src/SaveFileFormats.cc | 16 +++---------- src/SaveFileFormats.hh | 2 +- src/ServerState.cc | 4 ++-- src/TeamIndex.cc | 10 ++++---- 8 files changed, 65 insertions(+), 87 deletions(-) diff --git a/.gitignore b/.gitignore index ec7097d0..597f9a43 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ CTestTestfile.cmake install_manifest.txt Makefile Testing +build # Files modified by the user and/or server that don't have defaults system/config.json @@ -35,6 +36,7 @@ system/patch-bb/.metadata-cache.json # repository files make_release.py +notes-private old-khyller old-newserv release diff --git a/src/ImageEncoder.cc b/src/ImageEncoder.cc index c7126722..37f93b8f 100644 --- a/src/ImageEncoder.cc +++ b/src/ImageEncoder.cc @@ -35,7 +35,7 @@ struct GVRHeader { be_uint16_t height; } __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; { 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 yy = 0; yy < 4; yy++) { for (size_t xx = 0; xx < 4; xx++) { - uint64_t a, r, g, b; - img.read_pixel(x + xx, y + yy, &r, &g, &b, &a); + uint32_t c = img.read(x + xx, y + yy); switch (data_format) { case GVRDataFormat::RGB565: - w.put_u16b(encode_rgb565(r, g, b)); + w.put_u16b(phosg::rgb565_for_rgba8888(c)); break; case GVRDataFormat::RGB5A3: - w.put_u16b(encode_rgb5a3(r, g, b, a)); + w.put_u16b(encode_rgb5a3(c)); break; case GVRDataFormat::ARGB8888: - w.put_u32b(encode_argb8888(r, g, b, a)); + w.put_u32b(phosg::argb8888_for_rgba8888(c)); break; default: 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 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 height = num_pixels / width; - phosg::Image ret(width, height); + phosg::ImageRGB888 ret(width, height); phosg::BitReader r(data); for (size_t y = 0; y < height; y++) { 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; @@ -133,11 +132,11 @@ constexpr size_t uabs(size_t a, size_t b) { return (a > b) ? (a - b) : (b - a); } -string encode_fon(const phosg::Image& img) { +string encode_fon(const phosg::ImageRGB888& img) { phosg::BitWriter w; for (size_t y = 0; y < img.get_height(); y++) { 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_index = 0; diff --git a/src/ImageEncoder.hh b/src/ImageEncoder.hh index 4fa9e6c5..1f6baaac 100644 --- a/src/ImageEncoder.hh +++ b/src/ImageEncoder.hh @@ -19,43 +19,29 @@ enum class GVRDataFormat : uint8_t { DXT1 = 0x0E, }; -std::string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index); -phosg::Image decode_fon(const std::string& data, size_t width); -std::string encode_fon(const phosg::Image& img); +std::string encode_gvm( + const phosg::ImageRGBA8888& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index); +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) { - return ((r << 8) & 0xF800) | ((g << 3) & 0x07E0) | ((b >> 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); +constexpr uint16_t encode_rgb5a3(uint32_t c) { + 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); } 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) { - return (a << 24) | (r << 16) | (g << 8) | b; -} - -constexpr uint16_t encode_argb8888_to_argb1555(uint32_t argb8888) { - // In: aaaaaaaarrrrrrrrggggggggbbbbbbbb - // Out: arrrrrgggggbbbbb - 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 - // 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); +template +bool has_any_transparent_pixels(const phosg::Image& img) { + if constexpr (phosg::Image::HAS_ALPHA) { + for (size_t y = 0; y < img.get_height(); y++) { + for (size_t x = 0; x < img.get_height(); x++) { + if (phosg::get_a(img.read(x, y)) != 0xFF) { + return true; + } + } + } + } + return false; } diff --git a/src/Main.cc b/src/Main.cc index 87f2b76e..e4b6513a 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1166,7 +1166,7 @@ Action a_decode_gci_snapshot( } 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"); }); @@ -1177,13 +1177,16 @@ Action a_encode_gvm( GVM file can be used as an Episode 3 lobby banner.\n", +[](phosg::Arguments& args) { const string& input_filename = args.get(1, false); - phosg::Image img; + string data; if (!input_filename.empty() && (input_filename != "-")) { - img = phosg::Image(input_filename); + data = phosg::load_file(input_filename); } 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"); }); @@ -1196,7 +1199,7 @@ Action a_decode_bitmap_font( +[](phosg::Arguments& args) { std::string data = read_input_data(args); size_t width = args.get("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"); }); Action a_encode_bitmap_font( @@ -1207,12 +1210,13 @@ Action a_encode_bitmap_font( original fon\'s dimensions.\n", +[](phosg::Arguments& args) { const string& input_filename = args.get(1, false); - phosg::Image img; + string data; if (!input_filename.empty() && (input_filename != "-")) { - img = phosg::Image(input_filename); + data = phosg::load_file(input_filename); } else { - img = phosg::Image(stdin); + data = phosg::read_all(stdin); } + auto img = phosg::ImageRGB888::from_file_data(data); string encoded = encode_fon(img); write_output_data(args, encoded.data(), encoded.size(), "fon"); }); @@ -2591,22 +2595,19 @@ Action a_generate_ep3_cards_html( phosg::parallel_range([&](uint32_t index, size_t) -> bool { auto& info = this->card_infos[index]; if (!info.large_filename.empty()) { - phosg::Image img(info.large_filename); - phosg::Image cropped(512, 399); - cropped.blit(img, 0, 0, 512, 399, 0, 0); - info.large_data_url = cropped.png_data_url(); + auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(info.large_filename)); + img.resize(512, 399); + info.large_data_url = img.serialize(phosg::ImageFormat::PNG_DATA_URL); } if (!info.medium_filename.empty()) { - phosg::Image img(info.medium_filename); - phosg::Image cropped(184, 144); - cropped.blit(img, 0, 0, 184, 144, 0, 0); - info.medium_data_url = cropped.png_data_url(); + auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(info.medium_filename)); + img.resize(184, 144); + info.medium_data_url = img.serialize(phosg::ImageFormat::PNG_DATA_URL); } if (!info.small_filename.empty()) { - phosg::Image img(info.small_filename); - phosg::Image cropped(58, 43); - cropped.blit(img, 0, 0, 58, 43, 0, 0); - info.small_data_url = cropped.png_data_url(); + auto img = phosg::ImageRGBA8888::from_file_data(phosg::load_file(info.small_filename)); + img.resize(58, 43); + info.small_data_url = img.serialize(phosg::ImageFormat::PNG_DATA_URL); } return false; }, diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 54473db2..5a70541e 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -218,16 +218,7 @@ bool PSOGCSnapshotFile::checksum_correct() const { return (crc == this->checksum); } -static uint32_t decode_rgb565(uint16_t c) { - // 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 { +phosg::ImageRGB888 PSOGCSnapshotFile::decode_image() const { size_t width = this->width ? this->width.load() : 256; size_t height = this->height ? this->height.load() : 192; if (width != 256) { @@ -238,14 +229,13 @@ phosg::Image PSOGCSnapshotFile::decode_image() const { } // 4x4 blocks of pixels - phosg::Image ret(width, height, false); + phosg::ImageRGB888 ret(width, height); size_t offset = 0; for (size_t y = 0; y < this->height; y += 4) { for (size_t x = 0; x < this->width; x += 4) { for (size_t yy = 0; yy < 4; yy++) { for (size_t xx = 0; xx < 4; xx++) { - uint32_t color = decode_rgb565(this->pixels[offset++]); - ret.write_pixel(x + xx, y + yy, color); + ret.write(x + xx, y + yy, phosg::rgba8888_for_rgb565(this->pixels[offset++])); } } } diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 34c6cbb0..02cc134d 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -1066,7 +1066,7 @@ struct PSOGCSnapshotFile { /* 1818C */ bool checksum_correct() const; - phosg::Image decode_image() const; + phosg::ImageRGB888 decode_image() const; } __packed_ws__(PSOGCSnapshotFile, 0x1818C); //////////////////////////////////////////////////////////////////////////////// diff --git a/src/ServerState.cc b/src/ServerState.cc index b95b69b0..b4e249aa 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -966,10 +966,10 @@ void ServerState::load_config_early() { } else if (lower_path.ends_with(".gvm")) { decompressed_gvm_data = phosg::load_file(path); } 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( img, - img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565, + has_any_transparent_pixels(img) ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565, std::format("bnr{}", banner_index), 0x80 | banner_index); banner_index++; diff --git a/src/TeamIndex.cc b/src/TeamIndex.cc index 432c963a..e88e2c22 100644 --- a/src/TeamIndex.cc +++ b/src/TeamIndex.cc @@ -98,14 +98,14 @@ void TeamIndex::Team::save_config() const { } 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) { throw runtime_error("incorrect flag image dimensions"); } this->flag_data.reset(new parray()); for (size_t y = 0; y < 32; y++) { 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) { return; } - phosg::Image img(32, 32, true); + phosg::ImageRGBA8888 img(32, 32); for (size_t y = 0; y < 32; y++) { 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 {