diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp index e2700b8740..c3839c7cae 100644 --- a/src/openrct2-ui/scripting/CustomImages.cpp +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -275,6 +275,21 @@ namespace OpenRCT2::Scripting return unpadded; } + static ImportMode getImportModeFromPalette(const PixelDataPaletteKind& palette) + { + switch (palette) + { + case PixelDataPaletteKind::Closest: + return ImportMode::Closest; + case PixelDataPaletteKind::Dither: + return ImportMode::Dithering; + case PixelDataPaletteKind::None: + case PixelDataPaletteKind::Keep: + default: + return ImportMode::Default; + } + } + static std::vector GetBufferFromPixelData(duk_context* ctx, PixelData& pixelData) { std::vector imageData; @@ -303,18 +318,14 @@ namespace OpenRCT2::Scripting case PixelDataKind::Png: { auto imageFormat = pixelData.Palette == PixelDataPaletteKind::Keep ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32; - auto palette = pixelData.Palette == PixelDataPaletteKind::Keep ? ImageImporter::Palette::KeepIndices - : ImageImporter::Palette::OpenRCT2; - auto importMode = ImageImporter::ImportMode::Default; - if (pixelData.Palette == PixelDataPaletteKind::Closest) - importMode = ImageImporter::ImportMode::Closest; - else if (pixelData.Palette == PixelDataPaletteKind::Dither) - importMode = ImageImporter::ImportMode::Dithering; + auto palette = pixelData.Palette == PixelDataPaletteKind::Keep ? Palette::KeepIndices : Palette::OpenRCT2; + auto importMode = getImportModeFromPalette(pixelData.Palette); auto pngData = DukGetDataFromBufferLikeObject(pixelData.Data); auto image = Imaging::ReadFromBuffer(pngData, imageFormat); + ImageImportMeta meta = { { 0, 0 }, palette, ImportFlags::RLE, importMode }; ImageImporter importer; - auto importResult = importer.Import(image, 0, 0, palette, ImageImporter::ImportFlags::RLE, importMode); + auto importResult = importer.Import(image, meta); pixelData.Type = PixelDataKind::Rle; pixelData.Width = importResult.Element.width; diff --git a/src/openrct2/CommandLineSprite.cpp b/src/openrct2/CommandLineSprite.cpp index 9d054ef77c..ffe578309d 100644 --- a/src/openrct2/CommandLineSprite.cpp +++ b/src/openrct2/CommandLineSprite.cpp @@ -225,21 +225,12 @@ static bool SpriteImageExport(const G1Element& spriteElement, u8string_view outP } } -static std::optional SpriteImageImport( - const char* path, int16_t x_offset, int16_t y_offset, ImageImporter::Palette palette, bool forceBmp, - ImageImporter::ImportMode mode) +static std::optional SpriteImageImport(u8string_view path, ImageImportMeta meta) { try { auto format = IMAGE_FORMAT::PNG_32; - auto flags = ImageImporter::ImportFlags::None; - - if (!forceBmp) - { - flags = ImageImporter::ImportFlags::RLE; - } - - if (palette == ImageImporter::Palette::KeepIndices) + if (meta.palette == Palette::KeepIndices) { format = IMAGE_FORMAT::PNG; } @@ -247,7 +238,7 @@ static std::optional SpriteImageImport( ImageImporter importer; auto image = Imaging::ReadFromFile(path, format); - return importer.Import(image, x_offset, y_offset, palette, flags, mode); + return importer.Import(image, meta); } catch (const std::exception& e) { @@ -483,21 +474,21 @@ int32_t CommandLineForSprite(const char** argv, int32_t argc) const utf8* spriteFilePath = argv[1]; const utf8* imagePath = argv[2]; - int16_t x_offset = 0; - int16_t y_offset = 0; + int16_t xOffset = 0; + int16_t yOffset = 0; if (argc == 5) { char* endptr; - x_offset = strtol(argv[3], &endptr, 0); + xOffset = strtol(argv[3], &endptr, 0); if (*endptr != 0) { fprintf(stderr, "X offset must be an integer\n"); return -1; } - y_offset = strtol(argv[4], &endptr, 0); + yOffset = strtol(argv[4], &endptr, 0); if (*endptr != 0) { fprintf(stderr, "Y offset must be an integer\n"); @@ -505,8 +496,8 @@ int32_t CommandLineForSprite(const char** argv, int32_t argc) } } - auto importResult = SpriteImageImport( - imagePath, x_offset, y_offset, ImageImporter::Palette::OpenRCT2, false, gSpriteMode); + ImageImportMeta meta = { { xOffset, yOffset }, Palette::OpenRCT2, ImportFlags::RLE, gSpriteMode }; + auto importResult = SpriteImageImport(imagePath, meta); if (!importResult.has_value()) return -1; @@ -579,18 +570,12 @@ int32_t CommandLineForSprite(const char** argv, int32_t argc) } std::string strPath = Json::GetString(path); - json_t x_offset = jsonSprite["x"]; - json_t y_offset = jsonSprite["y"]; - - auto palette = (Json::GetString(jsonSprite["palette"]) == "keep") ? ImageImporter::Palette::KeepIndices - : ImageImporter::Palette::OpenRCT2; - bool forceBmp = !jsonSprite["palette"].is_null() && Json::GetString(jsonSprite["format"]) == "raw"; + auto meta = createImageImportMetaFromJson(jsonSprite); + meta.importMode = gSpriteMode; auto imagePath = Path::GetAbsolute(Path::Combine(directoryPath, strPath)); - auto importResult = SpriteImageImport( - imagePath.c_str(), Json::GetNumber(x_offset), Json::GetNumber(y_offset), palette, forceBmp, - gSpriteMode); + auto importResult = SpriteImageImport(imagePath, meta); if (importResult == std::nullopt) { fprintf(stderr, "Could not import image file: %s\nCanceling\n", imagePath.c_str()); diff --git a/src/openrct2/CommandLineSprite.h b/src/openrct2/CommandLineSprite.h index ec31c9505a..726722ec10 100644 --- a/src/openrct2/CommandLineSprite.h +++ b/src/openrct2/CommandLineSprite.h @@ -13,4 +13,4 @@ #include "drawing/ImageImporter.h" int32_t CommandLineForSprite(const char** argv, int32_t argc); -extern OpenRCT2::Drawing::ImageImporter::ImportMode gSpriteMode; +extern OpenRCT2::Drawing::ImportMode gSpriteMode; diff --git a/src/openrct2/command_line/SpriteCommands.cpp b/src/openrct2/command_line/SpriteCommands.cpp index 6bb5ca1b0c..7e13e9791f 100644 --- a/src/openrct2/command_line/SpriteCommands.cpp +++ b/src/openrct2/command_line/SpriteCommands.cpp @@ -16,7 +16,7 @@ #define SZ_CLOSEST "closest" #define SZ_DITHERING "dithering" -using ImportMode = OpenRCT2::Drawing::ImageImporter::ImportMode; +using ImportMode = OpenRCT2::Drawing::ImportMode; ImportMode gSpriteMode = ImportMode::Default; diff --git a/src/openrct2/drawing/ImageImporter.cpp b/src/openrct2/drawing/ImageImporter.cpp index 2d5354f10a..e28a882c93 100644 --- a/src/openrct2/drawing/ImageImporter.cpp +++ b/src/openrct2/drawing/ImageImporter.cpp @@ -10,6 +10,7 @@ #include "ImageImporter.h" #include "../core/Imaging.h" +#include "../core/Json.hpp" #include #include @@ -20,36 +21,35 @@ using ImportResult = ImageImporter::ImportResult; constexpr int32_t PALETTE_TRANSPARENT = -1; -ImportResult ImageImporter::Import( - const Image& image, int32_t offsetX, int32_t offsetY, Palette palette, ImportFlags flags, ImportMode mode) const +ImportResult ImageImporter::Import(const Image& image, ImageImportMeta& meta) const { - return Import(image, 0, 0, image.Width, image.Height, offsetX, offsetY, palette, flags, mode); -} + if (meta.srcSize.width == 0) + meta.srcSize.width = image.Width; -ImportResult ImageImporter::Import( - const Image& image, int32_t srcX, int32_t srcY, int32_t width, int32_t height, int32_t offsetX, int32_t offsetY, - Palette palette, ImportFlags flags, ImportMode mode) const -{ - if (width > 256 || height > 256) + if (meta.srcSize.height == 0) + meta.srcSize.height = image.Height; + + if (meta.srcSize.width > 256 || meta.srcSize.height > 256) { throw std::invalid_argument("Only images 256x256 or less are supported."); } - if (palette == Palette::KeepIndices && image.Depth != 8) + if (meta.palette == Palette::KeepIndices && image.Depth != 8) { throw std::invalid_argument("Image is not paletted, it has bit depth of " + std::to_string(image.Depth)); } + const bool isRLE = meta.importFlags & ImportFlags::RLE; - auto pixels = GetPixels(image.Pixels.data(), image.Stride, srcX, srcY, width, height, palette, flags, mode); - auto buffer = flags & ImportFlags::RLE ? EncodeRLE(pixels.data(), width, height) : EncodeRaw(pixels.data(), width, height); + auto pixels = GetPixels(image, meta); + auto buffer = isRLE ? EncodeRLE(pixels.data(), meta.srcSize) : EncodeRaw(pixels.data(), meta.srcSize); G1Element outElement; - outElement.width = width; - outElement.height = height; - outElement.flags = (flags & ImportFlags::RLE ? G1_FLAG_RLE_COMPRESSION : G1_FLAG_HAS_TRANSPARENCY); - outElement.x_offset = offsetX; - outElement.y_offset = offsetY; - outElement.zoomed_offset = 0; + outElement.width = meta.srcSize.width; + outElement.height = meta.srcSize.height; + outElement.flags = isRLE ? G1_FLAG_RLE_COMPRESSION : G1_FLAG_HAS_TRANSPARENCY; + outElement.x_offset = meta.offset.x; + outElement.y_offset = meta.offset.y; + outElement.zoomed_offset = meta.zoomedOffset; ImportResult result; result.Element = outElement; @@ -58,44 +58,43 @@ ImportResult ImageImporter::Import( return result; } -std::vector ImageImporter::GetPixels( - const uint8_t* pixels, uint32_t pitch, uint32_t srcX, uint32_t srcY, uint32_t width, uint32_t height, Palette palette, - ImportFlags flags, ImportMode mode) +std::vector ImageImporter::GetPixels(const Image& image, const ImageImportMeta& meta) { + const uint8_t* pixels = image.Pixels.data(); std::vector buffer; - buffer.reserve(width * height); + buffer.reserve(meta.srcSize.width * meta.srcSize.height); // A larger range is needed for proper dithering auto palettedSrc = pixels; std::unique_ptr rgbaSrcBuffer; - if (palette != Palette::KeepIndices) + if (meta.palette != Palette::KeepIndices) { - rgbaSrcBuffer = std::make_unique(height * width * 4); + rgbaSrcBuffer = std::make_unique(meta.srcSize.height * meta.srcSize.width * 4); } auto rgbaSrc = rgbaSrcBuffer.get(); - if (palette != Palette::KeepIndices) + if (meta.palette != Palette::KeepIndices) { - auto src = pixels + (srcY * pitch) + (srcX * 4); + auto src = pixels + (meta.srcOffset.y * image.Stride) + (meta.srcOffset.x * 4); auto dst = rgbaSrc; - for (uint32_t y = 0; y < height; y++) + for (auto y = 0; y < meta.srcSize.height; y++) { - for (uint32_t x = 0; x < width * 4; x++) + for (auto x = 0; x < meta.srcSize.width * 4; x++) { *dst = static_cast(*src); src++; dst++; } - src += (pitch - (width * 4)); + src += (image.Stride - (meta.srcSize.width * 4)); } } - if (palette == Palette::KeepIndices) + if (meta.palette == Palette::KeepIndices) { - palettedSrc += srcX + srcY * pitch; - for (uint32_t y = 0; y < height; y++) + palettedSrc += meta.srcOffset.x + meta.srcOffset.y * image.Stride; + for (auto y = 0; y < meta.srcSize.height; y++) { - for (uint32_t x = 0; x < width; x++) + for (auto x = 0; x < meta.srcSize.width; x++) { int32_t paletteIndex = *palettedSrc; // The 1st index is always transparent @@ -106,16 +105,17 @@ std::vector ImageImporter::GetPixels( palettedSrc += 1; buffer.push_back(paletteIndex); } - palettedSrc += (pitch - width); + palettedSrc += (image.Stride - meta.srcSize.width); } } else { - for (uint32_t y = 0; y < height; y++) + for (auto y = 0; y < meta.srcSize.height; y++) { - for (uint32_t x = 0; x < width; x++) + for (auto x = 0; x < meta.srcSize.width; x++) { - auto paletteIndex = CalculatePaletteIndex(mode, rgbaSrc, x, y, width, height); + auto paletteIndex = CalculatePaletteIndex( + meta.importMode, rgbaSrc, x, y, meta.srcSize.width, meta.srcSize.height); rgbaSrc += 4; buffer.push_back(paletteIndex); } @@ -125,11 +125,11 @@ std::vector ImageImporter::GetPixels( return buffer; } -std::vector ImageImporter::EncodeRaw(const int32_t* pixels, uint32_t width, uint32_t height) +std::vector ImageImporter::EncodeRaw(const int32_t* pixels, ScreenSize size) { - auto bufferLength = width * height; + auto bufferLength = size.width * size.height; std::vector buffer(bufferLength); - for (size_t i = 0; i < bufferLength; i++) + for (auto i = 0; i < bufferLength; i++) { auto p = pixels[i]; buffer[i] = (p == PALETTE_TRANSPARENT ? 0 : static_cast(p)); @@ -137,7 +137,7 @@ std::vector ImageImporter::EncodeRaw(const int32_t* pixels, uint32_t wi return buffer; } -std::vector ImageImporter::EncodeRLE(const int32_t* pixels, uint32_t width, uint32_t height) +std::vector ImageImporter::EncodeRLE(const int32_t* pixels, ScreenSize size) { struct RLECode { @@ -146,12 +146,12 @@ std::vector ImageImporter::EncodeRLE(const int32_t* pixels, uint32_t wi }; auto src = pixels; - std::vector buffer((height * 2) + (width * height * 16)); + std::vector buffer((size.height * 2) + (size.width * size.height * 16)); - std::fill_n(buffer.data(), (height * 2) + (width * height * 16), 0x00); + std::fill_n(buffer.data(), (size.height * 2) + (size.width * size.height * 16), 0x00); auto yOffsets = reinterpret_cast(buffer.data()); - auto dst = buffer.data() + (height * 2); - for (uint32_t y = 0; y < height; y++) + auto dst = buffer.data() + (size.height * 2); + for (auto y = 0; y < size.height; y++) { yOffsets[y] = static_cast(dst - buffer.data()); @@ -162,7 +162,7 @@ std::vector ImageImporter::EncodeRLE(const int32_t* pixels, uint32_t wi auto startX = 0; auto npixels = 0; bool pushRun = false; - for (uint32_t x = 0; x < width; x++) + for (auto x = 0; x < size.width; x++) { int32_t paletteIndex = *src++; if (paletteIndex == PALETTE_TRANSPARENT) @@ -184,7 +184,7 @@ std::vector ImageImporter::EncodeRLE(const int32_t* pixels, uint32_t wi npixels++; *dst++ = static_cast(paletteIndex); } - if (npixels == 127 || x == width - 1) + if (npixels == 127 || x == size.width - 1) { pushRun = true; } @@ -197,7 +197,7 @@ std::vector ImageImporter::EncodeRLE(const int32_t* pixels, uint32_t wi currentCode->NumPixels = npixels; currentCode->OffsetX = startX; - if (x == width - 1) + if (x == size.width - 1) { currentCode->NumPixels |= 0x80; } @@ -381,3 +381,26 @@ int32_t ImageImporter::GetClosestPaletteIndex(const GamePalette& palette, const } return bestMatch; } + +namespace OpenRCT2::Drawing +{ + ImageImportMeta createImageImportMetaFromJson(json_t& input) + { + auto xOffset = Json::GetNumber(input["x"]); + auto yOffset = Json::GetNumber(input["y"]); + auto keepPalette = Json::GetString(input["palette"]) == "keep"; + auto palette = keepPalette ? Palette::KeepIndices : Palette::OpenRCT2; + + auto raw = Json::GetString(input["format"]) == "raw"; + auto flags = raw ? ImportFlags::None : ImportFlags::RLE; + + auto srcX = Json::GetNumber(input["srcX"]); + auto srcY = Json::GetNumber(input["srcY"]); + auto srcWidth = Json::GetNumber(input["srcWidth"]); + auto srcHeight = Json::GetNumber(input["srcHeight"]); + auto zoomedOffset = Json::GetNumber(input["zoom"]); + + return ImageImportMeta{ { xOffset, yOffset }, palette, flags, ImportMode::Default, { srcX, srcY }, + { srcWidth, srcHeight }, zoomedOffset }; + }; +} // namespace OpenRCT2::Drawing diff --git a/src/openrct2/drawing/ImageImporter.h b/src/openrct2/drawing/ImageImporter.h index 8566c4e2a3..e9ec90feb7 100644 --- a/src/openrct2/drawing/ImageImporter.h +++ b/src/openrct2/drawing/ImageImporter.h @@ -10,6 +10,7 @@ #pragma once #include "../core/Imaging.h" +#include "../core/JsonFwd.hpp" #include "Drawing.h" #include @@ -19,6 +20,36 @@ struct Image; namespace OpenRCT2::Drawing { + enum class ImportMode : uint8_t + { + Default, + Closest, + Dithering, + }; + + enum ImportFlags : uint8_t + { + None = 0, + RLE = 1 << 1, + }; + + enum class Palette : uint8_t + { + OpenRCT2, + KeepIndices, + }; + + struct ImageImportMeta + { + ScreenCoordsXY offset{}; + Palette palette = Palette::OpenRCT2; + ImportFlags importFlags = ImportFlags::RLE; + ImportMode importMode = ImportMode::Default; + ScreenCoordsXY srcOffset{}; + ScreenSize srcSize{}; + int32_t zoomedOffset{}; + }; + /** * Imports images to the internal RCT G1 format. */ @@ -31,32 +62,7 @@ namespace OpenRCT2::Drawing std::vector Buffer; }; - enum class ImportMode : uint8_t - { - Default, - Closest, - Dithering, - }; - - enum ImportFlags : uint8_t - { - None = 0, - RLE = 1 << 1, - }; - - enum class Palette : uint8_t - { - OpenRCT2, - KeepIndices, - }; - - ImportResult Import( - const Image& image, int32_t srcX, int32_t srcY, int32_t width, int32_t height, int32_t offsetX, int32_t offsetY, - Palette palette = Palette::OpenRCT2, ImportFlags flags = ImportFlags::None, - ImportMode mode = ImportMode::Default) const; - ImportResult Import( - const Image& image, int32_t offsetX = 0, int32_t offsetY = 0, Palette palette = Palette::OpenRCT2, - ImportFlags flags = ImportFlags::None, ImportMode mode = ImportMode::Default) const; + ImportResult Import(const Image& image, ImageImportMeta& meta) const; private: enum class PaletteIndexType : uint8_t @@ -68,11 +74,9 @@ namespace OpenRCT2::Drawing Special, }; - static std::vector GetPixels( - const uint8_t* pixels, uint32_t pitch, uint32_t srcX, uint32_t srcY, uint32_t width, uint32_t height, - Palette palette, ImportFlags flags, ImportMode mode); - static std::vector EncodeRaw(const int32_t* pixels, uint32_t width, uint32_t height); - static std::vector EncodeRLE(const int32_t* pixels, uint32_t width, uint32_t height); + static std::vector GetPixels(const Image& image, const ImageImportMeta& meta); + static std::vector EncodeRaw(const int32_t* pixels, ScreenSize size); + static std::vector EncodeRLE(const int32_t* pixels, ScreenSize size); static int32_t CalculatePaletteIndex( ImportMode mode, int16_t* rgbaSrc, int32_t x, int32_t y, int32_t width, int32_t height); @@ -83,6 +87,9 @@ namespace OpenRCT2::Drawing static PaletteIndexType GetPaletteIndexType(int32_t paletteIndex); static int32_t GetClosestPaletteIndex(const GamePalette& palette, const int16_t* colour); }; + + // Note: jsonSprite is deliberately left non-const: json_t behaviour changes when const. + ImageImportMeta createImageImportMetaFromJson(json_t& input); } // namespace OpenRCT2::Drawing constexpr GamePalette StandardPalette = { { diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index dcd8d788c7..3ac0b90a8c 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -161,9 +161,10 @@ std::vector> ImageTable::ParseImages( { auto imageData = context->GetData(s); auto image = Imaging::ReadFromBuffer(imageData); + auto meta = ImageImportMeta{}; ImageImporter importer; - auto importResult = importer.Import(image, 0, 0, ImageImporter::Palette::OpenRCT2, ImageImporter::ImportFlags::RLE); + auto importResult = importer.Import(image, meta); result.push_back(std::make_unique(importResult.Element)); } @@ -183,30 +184,11 @@ std::vector> ImageTable::ParseImages( Guard::Assert(el.is_object(), "ImageTable::ParseImages expects parameter el to be object"); auto path = Json::GetString(el["path"]); - auto x = Json::GetNumber(el["x"]); - auto y = Json::GetNumber(el["y"]); - auto srcX = Json::GetNumber(el["srcX"]); - auto srcY = Json::GetNumber(el["srcY"]); - auto srcWidth = Json::GetNumber(el["srcWidth"]); - auto srcHeight = Json::GetNumber(el["srcHeight"]); - auto raw = Json::GetString(el["format"]) == "raw"; - auto keepPalette = Json::GetString(el["palette"]) == "keep"; - auto zoomOffset = Json::GetNumber(el["zoom"]); + auto meta = createImageImportMetaFromJson(el); std::vector> result; try { - auto flags = ImageImporter::ImportFlags::None; - auto palette = ImageImporter::Palette::OpenRCT2; - if (!raw) - { - flags = static_cast(flags | ImageImporter::ImportFlags::RLE); - } - if (keepPalette) - { - palette = ImageImporter::Palette::KeepIndices; - } - auto itSource = std::find_if( imageSources.begin(), imageSources.end(), [&path](const std::pair& item) { return item.first == path; }); @@ -216,16 +198,9 @@ std::vector> ImageTable::ParseImages( } auto& image = itSource->second; - if (srcWidth == 0) - srcWidth = image.Width; - - if (srcHeight == 0) - srcHeight = image.Height; - ImageImporter importer; - auto importResult = importer.Import(image, srcX, srcY, srcWidth, srcHeight, x, y, palette, flags); + auto importResult = importer.Import(image, meta); auto g1element = importResult.Element; - g1element.zoomed_offset = zoomOffset; result.push_back(std::make_unique(g1element)); } catch (const std::exception& e) diff --git a/test/tests/ImageImporterTests.cpp b/test/tests/ImageImporterTests.cpp index cfe50c4fa9..ec5d232f87 100644 --- a/test/tests/ImageImporterTests.cpp +++ b/test/tests/ImageImporterTests.cpp @@ -41,7 +41,8 @@ TEST_F(ImageImporterTests, Import_Logo) ImageImporter importer; auto image = Imaging::ReadFromFile(logoPath, IMAGE_FORMAT::PNG_32); - auto result = importer.Import(image, 3, 5, ImageImporter::Palette::OpenRCT2, ImageImporter::ImportFlags::RLE); + auto meta = ImageImportMeta{ .offset = { 3, 5 } }; + auto result = importer.Import(image, meta); ASSERT_EQ(result.Buffer.data(), result.Element.offset); ASSERT_EQ(128, result.Element.width);