From 03d303b2bbd041723897bc05e37d20d22df6971f Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 23 Feb 2025 17:03:43 -0800 Subject: [PATCH] add encode/decode options for bitmap fonts --- CMakeLists.txt | 2 +- README.md | 9 ++--- src/{GVMEncoder.cc => ImageEncoder.cc} | 50 ++++++++++++++++++++++++-- src/{GVMEncoder.hh => ImageEncoder.hh} | 2 ++ src/Main.cc | 32 ++++++++++++++++- src/ProxyCommands.cc | 2 +- src/ServerState.cc | 2 +- src/TeamIndex.cc | 2 +- 8 files changed, 90 insertions(+), 11 deletions(-) rename src/{GVMEncoder.cc => ImageEncoder.cc} (70%) rename src/{GVMEncoder.hh => ImageEncoder.hh} (94%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d8b3836..8d75c60e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,8 +84,8 @@ set(SOURCES src/FileContentsCache.cc src/FunctionCompiler.cc src/GSLArchive.cc - src/GVMEncoder.cc src/HTTPServer.cc + src/ImageEncoder.cc src/IntegralExpression.cc src/IPFrameInfo.cc src/IPStackSimulator.cc diff --git a/README.md b/README.md index 47b34798..eb25faaf 100644 --- a/README.md +++ b/README.md @@ -740,10 +740,11 @@ The data formats that newserv can convert to/from are: | PSO GC snapshot file | None | `decode-gci-snapshot` | | Quest script (.bin) | `assemble-quest-script` | `disassemble-quest-script` | | Quest map (.dat) | None | `disassemble-quest-map` | -| AFS archive | None | `extract-afs` | -| BML archive | None | `extract-bml` | -| GSL archive | `generate-gsl` | `extract-gsl` | -| GVM texture | `encode-gvm` | None | +| AFS archive (.afs) | None | `extract-afs` | +| BML archive (.bml) | None | `extract-bml` | +| GSL archive (.gsl) | `generate-gsl` | `extract-gsl` | +| GVM texture (.gvm) | `encode-gvm` | None | +| Bitmap font (.fon) | `encode-bitmap-font` | `decode-bitmap-font` | | Text archive | `encode-text-archive` | `decode-text-archive` | | Unicode text set | `encode-unicode-text-set` | `decode-unicode-text-set` | | Word Select data set | None | `decode-word-select-set` | diff --git a/src/GVMEncoder.cc b/src/ImageEncoder.cc similarity index 70% rename from src/GVMEncoder.cc rename to src/ImageEncoder.cc index 47202b5b..c7126722 100644 --- a/src/GVMEncoder.cc +++ b/src/ImageEncoder.cc @@ -1,5 +1,6 @@ -#include "GVMEncoder.hh" +#include "ImageEncoder.hh" +#include #include #include #include @@ -34,7 +35,7 @@ struct GVRHeader { be_uint16_t height; } __packed_ws__(GVRHeader, 0x10); -string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index) { +string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const string& internal_name, uint32_t global_index) { int8_t dimensions_field = -2; { size_t h = img.get_height(); @@ -111,3 +112,48 @@ string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const std: return std::move(w.str()); } + +static const array fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF}; + +phosg::Image 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::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)]); + } + } + return ret; +} + +constexpr size_t uabs(size_t a, size_t b) { + return (a > b) ? (a - b) : (b - a); +} + +string encode_fon(const phosg::Image& 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); + + size_t result_delta = 0x400; + size_t result_index = 0; + for (size_t z = 0; z < 4; z++) { + size_t delta = uabs((fon_colors[z] >> 24) & 0xFF, (color >> 24) & 0xFF) + + uabs((fon_colors[z] >> 16) & 0xFF, (color >> 16) & 0xFF) + + uabs((fon_colors[z] >> 8) & 0xFF, (color >> 8) & 0xFF) + + uabs(fon_colors[z] & 0xFF, color & 0xFF); + if (delta < result_delta) { + result_delta = delta; + result_index = z; + } + } + w.write(result_index & 2); + w.write(result_index & 1); + } + } + return w.str(); +} diff --git a/src/GVMEncoder.hh b/src/ImageEncoder.hh similarity index 94% rename from src/GVMEncoder.hh rename to src/ImageEncoder.hh index 5a0c533f..4fa9e6c5 100644 --- a/src/GVMEncoder.hh +++ b/src/ImageEncoder.hh @@ -20,6 +20,8 @@ enum class GVRDataFormat : uint8_t { }; 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); constexpr uint16_t encode_rgb565(uint8_t r, uint8_t g, uint8_t b) { return ((r << 8) & 0xF800) | ((g << 3) & 0x07E0) | ((b >> 3) & 0x001F); diff --git a/src/Main.cc b/src/Main.cc index 851dbb8f..22d2acdd 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -29,9 +29,9 @@ #include "DNSServer.hh" #include "DownloadSession.hh" #include "GSLArchive.hh" -#include "GVMEncoder.hh" #include "HTTPServer.hh" #include "IPStackSimulator.hh" +#include "ImageEncoder.hh" #include "Loggers.hh" #include "NetworkAddresses.hh" #include "PSOGCObjectGraph.hh" @@ -1120,6 +1120,36 @@ Action a_encode_gvm( write_output_data(args, encoded.data(), encoded.size(), "gvm"); }); +Action a_decode_bitmap_font( + "decode-bitmap-font", "\ + decode-bitmap-font --width=WIDTH [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Decode a 2-bit bitmap font file (.fon) into a BMP image. The --width\n\ + option is required; if the output looks wrong, try increasing or\n\ + decreasing this number. For S18all04.fon, the width should be 20.\n", + +[](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); + write_output_data(args, bmp_data.data(), bmp_data.size(), "bmp"); + }); +Action a_encode_bitmap_font( + "encode-bitmap-font", "\ + encode-bitmap-font [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Encode an image in BMP or PPM/PNM format into a bitmap font file for use\n\ + with the console PSO versions. The image dimensions must match the\n\ + original fon\'s dimensions.\n", + +[](phosg::Arguments& args) { + const string& input_filename = args.get(1, false); + phosg::Image img; + if (!input_filename.empty() && (input_filename != "-")) { + img = phosg::Image(input_filename); + } else { + img = phosg::Image(stdin); + } + string encoded = encode_fon(img); + write_output_data(args, encoded.data(), encoded.size(), "fon"); + }); + Action a_salvage_gci( "salvage-gci", "\ salvage-gci INPUT-FILENAME [--round2] [CRYPT-OPTION] [--bytes=SIZE]\n\ diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index b756e5ac..47e63f63 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -30,7 +30,7 @@ #include "ChatCommands.hh" #include "Compression.hh" -#include "GVMEncoder.hh" +#include "ImageEncoder.hh" #include "Loggers.hh" #include "PSOProtocol.hh" #include "ReceiveCommands.hh" diff --git a/src/ServerState.cc b/src/ServerState.cc index b92e23a9..eca3513c 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -11,8 +11,8 @@ #include "Compression.hh" #include "EventUtils.hh" #include "FileContentsCache.hh" -#include "GVMEncoder.hh" #include "IPStackSimulator.hh" +#include "ImageEncoder.hh" #include "Loggers.hh" #include "NetworkAddresses.hh" #include "SendCommands.hh" diff --git a/src/TeamIndex.cc b/src/TeamIndex.cc index 6548cc6b..e6ce4dac 100644 --- a/src/TeamIndex.cc +++ b/src/TeamIndex.cc @@ -5,7 +5,7 @@ #include #include "BattleParamsIndex.hh" -#include "GVMEncoder.hh" +#include "ImageEncoder.hh" #include "ItemData.hh" #include "Loggers.hh" #include "StaticGameData.hh"