From 4fa45abec9a12083d3cb00772b9964e7e08bae18 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 7 May 2018 18:39:38 +0100 Subject: [PATCH 01/13] Remove old C signatures in Imaging.h --- src/openrct2/CmdlineSprite.cpp | 4 ++-- src/openrct2/Imaging.cpp | 16 ---------------- src/openrct2/Imaging.h | 4 ---- src/openrct2/interface/Screenshot.cpp | 8 ++++---- src/openrct2/world/MapGen.cpp | 2 +- 5 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/openrct2/CmdlineSprite.cpp b/src/openrct2/CmdlineSprite.cpp index 5255169eb6..66509d6d84 100644 --- a/src/openrct2/CmdlineSprite.cpp +++ b/src/openrct2/CmdlineSprite.cpp @@ -233,7 +233,7 @@ static bool sprite_file_export(sint32 spriteIndex, const char *outPath) gfx_bmp_sprite_to_buffer((uint8*)spriteFilePalette, spriteHeader->offset, pixels, spriteHeader, &dpi, spriteHeader->height, spriteHeader->width, IMAGE_TYPE_DEFAULT); } - if (image_io_png_write(&dpi, (rct_palette*)spriteFilePalette, outPath)) { + if (Imaging::PngWrite(&dpi, (rct_palette*)spriteFilePalette, outPath)) { return true; } else { fprintf(stderr, "Error writing PNG"); @@ -303,7 +303,7 @@ static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offse uint8 *pixels; uint32 width, height; sint32 bitDepth; - if (!image_io_png_read(&pixels, &width, &height, !keep_palette, path, &bitDepth)) + if (!Imaging::PngRead(&pixels, &width, &height, !keep_palette, path, &bitDepth)) { fprintf(stderr, "Error reading PNG\n"); return false; diff --git a/src/openrct2/Imaging.cpp b/src/openrct2/Imaging.cpp index 3c34841438..dde73791f3 100644 --- a/src/openrct2/Imaging.cpp +++ b/src/openrct2/Imaging.cpp @@ -300,19 +300,3 @@ namespace Imaging log_error(b); } } - -bool image_io_png_read(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth) -{ - return Imaging::PngRead(pixels, width, height, expand, path, bitDepth); -} - -bool image_io_png_write(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path) -{ - return Imaging::PngWrite(dpi, palette, path); -} - -bool image_io_png_write_32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path) -{ - return Imaging::PngWrite32bpp(width, height, pixels, path); -} - diff --git a/src/openrct2/Imaging.h b/src/openrct2/Imaging.h index c6c3df9f5f..b7140dae2d 100644 --- a/src/openrct2/Imaging.h +++ b/src/openrct2/Imaging.h @@ -27,7 +27,3 @@ namespace Imaging bool PngWrite(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path); bool PngWrite32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path); } - -bool image_io_png_read(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth); -bool image_io_png_write(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path); -bool image_io_png_write_32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path); diff --git a/src/openrct2/interface/Screenshot.cpp b/src/openrct2/interface/Screenshot.cpp index fc47453757..b619e2103f 100644 --- a/src/openrct2/interface/Screenshot.cpp +++ b/src/openrct2/interface/Screenshot.cpp @@ -153,7 +153,7 @@ sint32 screenshot_dump_png(rct_drawpixelinfo *dpi) rct_palette renderedPalette; screenshot_get_rendered_palette(&renderedPalette); - if (image_io_png_write(dpi, &renderedPalette, path)) { + if (Imaging::PngWrite(dpi, &renderedPalette, path)) { return index; } else { return -1; @@ -169,7 +169,7 @@ sint32 screenshot_dump_png_32bpp(sint32 width, sint32 height, const void *pixels return -1; } - if (image_io_png_write_32bpp(width, height, pixels, path)) { + if (Imaging::PngWrite32bpp(width, height, pixels, path)) { return index; } else { return -1; @@ -259,7 +259,7 @@ void screenshot_giant() rct_palette renderedPalette; screenshot_get_rendered_palette(&renderedPalette); - image_io_png_write(&dpi, &renderedPalette, path); + Imaging::PngWrite(&dpi, &renderedPalette, path); free(dpi.bits); @@ -576,7 +576,7 @@ sint32 cmdline_for_screenshot(const char * * argv, sint32 argc, ScreenshotOption rct_palette renderedPalette; screenshot_get_rendered_palette(&renderedPalette); - image_io_png_write(&dpi, &renderedPalette, outputPath); + Imaging::PngWrite(&dpi, &renderedPalette, outputPath); free(dpi.bits); drawing_engine_dispose(); diff --git a/src/openrct2/world/MapGen.cpp b/src/openrct2/world/MapGen.cpp index 77e28c54dd..7c045da670 100644 --- a/src/openrct2/world/MapGen.cpp +++ b/src/openrct2/world/MapGen.cpp @@ -656,7 +656,7 @@ bool mapgen_load_heightmap(const utf8 * path) if (String::Equals(extension, ".png", false)) { sint32 bitDepth; - if (!image_io_png_read(&pixels, &width, &height, true, path, &bitDepth)) + if (!Imaging::PngRead(&pixels, &width, &height, true, path, &bitDepth)) { log_warning("Error reading PNG"); context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_PNG); From 4552f6a4054d7cd4051ebf3bd05554e5495a26e2 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 7 May 2018 18:45:15 +0100 Subject: [PATCH 02/13] Move Imaging to core --- src/openrct2/CmdlineSprite.cpp | 2 +- src/openrct2/{ => core}/Imaging.cpp | 10 +++++----- src/openrct2/{ => core}/Imaging.h | 2 +- src/openrct2/interface/Screenshot.cpp | 2 +- src/openrct2/world/MapGen.cpp | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename src/openrct2/{ => core}/Imaging.cpp (98%) rename src/openrct2/{ => core}/Imaging.h (98%) diff --git a/src/openrct2/CmdlineSprite.cpp b/src/openrct2/CmdlineSprite.cpp index 66509d6d84..bed9975a7e 100644 --- a/src/openrct2/CmdlineSprite.cpp +++ b/src/openrct2/CmdlineSprite.cpp @@ -21,7 +21,7 @@ #include #include "CmdlineSprite.h" #include "drawing/Drawing.h" -#include "Imaging.h" +#include "core/Imaging.h" #include "localisation/Language.h" #include "OpenRCT2.h" #include "platform/platform.h" diff --git a/src/openrct2/Imaging.cpp b/src/openrct2/core/Imaging.cpp similarity index 98% rename from src/openrct2/Imaging.cpp rename to src/openrct2/core/Imaging.cpp index dde73791f3..8a95d5ca0e 100644 --- a/src/openrct2/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -18,12 +18,12 @@ #include #include -#include "core/FileStream.hpp" -#include "core/Guard.hpp" -#include "core/Memory.hpp" -#include "drawing/Drawing.h" - +#include "FileStream.hpp" +#include "Guard.hpp" #include "Imaging.h" +#include "Memory.hpp" +#include "../drawing/Drawing.h" + namespace Imaging { diff --git a/src/openrct2/Imaging.h b/src/openrct2/core/Imaging.h similarity index 98% rename from src/openrct2/Imaging.h rename to src/openrct2/core/Imaging.h index b7140dae2d..4afec47186 100644 --- a/src/openrct2/Imaging.h +++ b/src/openrct2/core/Imaging.h @@ -16,7 +16,7 @@ #pragma once -#include "common.h" +#include "../common.h" struct rct_drawpixelinfo; struct rct_palette; diff --git a/src/openrct2/interface/Screenshot.cpp b/src/openrct2/interface/Screenshot.cpp index b619e2103f..f31aaa0434 100644 --- a/src/openrct2/interface/Screenshot.cpp +++ b/src/openrct2/interface/Screenshot.cpp @@ -21,7 +21,7 @@ #include "../audio/audio.h" #include "../Context.h" #include "../core/Console.hpp" -#include "../Imaging.h" +#include "../core/Imaging.h" #include "../OpenRCT2.h" #include "Screenshot.h" diff --git a/src/openrct2/world/MapGen.cpp b/src/openrct2/world/MapGen.cpp index 7c045da670..a1e054fa14 100644 --- a/src/openrct2/world/MapGen.cpp +++ b/src/openrct2/world/MapGen.cpp @@ -25,7 +25,7 @@ #include "../core/String.hpp" #include "../core/Util.hpp" #include "../Game.h" -#include "../Imaging.h" +#include "../core/Imaging.h" #include "../localisation/StringIds.h" #include "../object/Object.h" #include "../platform/platform.h" From 766997f15c065a88f2d144f86406fedbe77579de Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 7 May 2018 21:32:24 +0100 Subject: [PATCH 03/13] Develop new imaging code --- src/openrct2/core/Imaging.cpp | 372 +++++++++++++++++++--------------- src/openrct2/core/Imaging.h | 30 +++ src/openrct2/core/String.cpp | 10 + src/openrct2/core/String.hpp | 1 + 4 files changed, 250 insertions(+), 163 deletions(-) diff --git a/src/openrct2/core/Imaging.cpp b/src/openrct2/core/Imaging.cpp index 8a95d5ca0e..9f76aa46c5 100644 --- a/src/openrct2/core/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -17,60 +17,105 @@ #pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable #include +#include +#include +#include #include #include "FileStream.hpp" #include "Guard.hpp" #include "Imaging.h" #include "Memory.hpp" +#include "String.hpp" #include "../drawing/Drawing.h" +template +class ivstream : public std::istream +{ +private: + class vector_streambuf : public std::basic_streambuf> + { + public: + explicit vector_streambuf(const std::vector& vec) + { + this->setg((char *)vec.data(), (char *)vec.data(), (char *)(vec.data() + vec.size())); + } + }; + + vector_streambuf _streambuf; + +public: + ivstream(const std::vector& vec) + : std::istream(&_streambuf), + _streambuf(vec) + { + } +}; namespace Imaging { - static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length); - static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length); - static void PngFlush(png_structp png_ptr); - static void PngWarning(png_structp png_ptr, const char * b); - static void PngError(png_structp png_ptr, const char * b); + constexpr auto EXCEPTION_IMAGE_FORMAT_UNSUPPORTED = "Unsupported image format."; + constexpr auto EXCEPTION_IMAGE_FORMAT_UNKNOWN = "Unknown image format."; - bool PngRead(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth) + static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length) + { + auto istream = static_cast(png_get_io_ptr(png_ptr)); + istream->read((char *)data, length); + } + + static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length) + { + auto ostream = static_cast(png_get_io_ptr(png_ptr)); + ostream->write((const char *)data, length); + } + + static void PngFlush(png_structp png_ptr) + { + auto ostream = static_cast(png_get_io_ptr(png_ptr)); + ostream->flush(); + } + + static void PngWarning(png_structp, const char * b) + { + log_warning(b); + } + + static void PngError(png_structp, const char * b) + { + log_error(b); + } + + static Image ReadPng(std::istream& istream, bool expandTo32) { png_structp png_ptr; png_infop info_ptr; - // Setup PNG structures - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (png_ptr == nullptr) - { - return false; - } - - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) - { - png_destroy_read_struct(&png_ptr, nullptr, nullptr); - return false; - } - - // Open PNG file try { - unsigned int sig_read = 0; - auto fs = FileStream(path, FILE_MODE_OPEN); + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (png_ptr == nullptr) + { + throw std::runtime_error("png_create_read_struct failed."); + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) + { + throw std::runtime_error("png_create_info_struct failed."); + } // Set error handling if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return false; + throw std::runtime_error("png error."); } // Setup PNG reading - png_set_read_fn(png_ptr, &fs, PngReadData); + int sig_read = 0; + png_set_read_fn(png_ptr, &istream, PngReadData); png_set_sig_bytes(png_ptr, sig_read); uint32 readFlags = PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING; - if (expand) + if (expandTo32) { // If we expand the resulting image always be full RGBA readFlags |= PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_EXPAND; @@ -79,21 +124,21 @@ namespace Imaging // Read header png_uint_32 pngWidth, pngHeight; - int colourType, interlaceType; - png_get_IHDR(png_ptr, info_ptr, &pngWidth, &pngHeight, bitDepth, &colourType, &interlaceType, nullptr, nullptr); + int bitDepth, colourType, interlaceType; + png_get_IHDR(png_ptr, info_ptr, &pngWidth, &pngHeight, &bitDepth, &colourType, &interlaceType, nullptr, nullptr); // Read pixels as 32bpp RGBA data - png_size_t rowBytes = png_get_rowbytes(png_ptr, info_ptr); - png_bytepp rowPointers = png_get_rows(png_ptr, info_ptr); - uint8 * pngPixels = Memory::Allocate(pngWidth * pngHeight * 4); - uint8 * dst = pngPixels; + auto rowBytes = png_get_rowbytes(png_ptr, info_ptr); + auto rowPointers = png_get_rows(png_ptr, info_ptr); + auto pngPixels = std::vector(pngWidth * pngHeight * 4); + auto dst = pngPixels.data(); if (colourType == PNG_COLOR_TYPE_RGB) { // 24-bit PNG (no alpha) Guard::Assert(rowBytes == pngWidth * 3, GUARD_LINE); for (png_uint_32 i = 0; i < pngHeight; i++) { - uint8 * src = rowPointers[i]; + auto src = rowPointers[i]; for (png_uint_32 x = 0; x < pngWidth; x++) { *dst++ = *src++; @@ -103,7 +148,7 @@ namespace Imaging } } } - else if (*bitDepth == 8 && !expand) + else if (bitDepth == 8 && !expandTo32) { // 8-bit paletted or grayscale Guard::Assert(rowBytes == pngWidth, GUARD_LINE); @@ -128,58 +173,63 @@ namespace Imaging png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); // Return the output data - *pixels = pngPixels; - if (width != nullptr) *width = pngWidth; - if (height != nullptr) *height = pngHeight; - - return true; + Image img; + img.Width = pngWidth; + img.Height = pngHeight; + img.Depth = expandTo32 ? 32 : 8; + img.Pixels = std::move(pngPixels); + img.Stride = pngWidth; + return img; } catch (const std::exception &) { - *pixels = nullptr; - if (width != nullptr) *width = 0; - if (height != nullptr) *height = 0; - return false; + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + throw; } } - bool PngWrite(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path) + static void WritePng(std::ostream& ostream, const Image& image) { - bool result = false; - - // Get image size - int stride = dpi->width + dpi->pitch; - - // Setup PNG - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (png_ptr == nullptr) - { - return false; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) - { - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return false; - } - - png_colorp png_palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); - for (int i = 0; i < 256; i++) - { - const rct_palette_entry *entry = &palette->entries[i]; - png_palette[i].blue = entry->blue; - png_palette[i].green = entry->green; - png_palette[i].red = entry->red; - } - - png_set_PLTE(png_ptr, info_ptr, png_palette, PNG_MAX_PALETTE_LENGTH); - + png_structp png_ptr = nullptr; + png_colorp png_palette = nullptr; try { - // Open file for writing - auto fs = FileStream(path, FILE_MODE_WRITE); - png_set_write_fn(png_ptr, &fs, PngWriteData, PngFlush); + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PngError, PngWarning); + if (png_ptr == nullptr) + { + throw std::runtime_error("png_create_write_struct failed."); + } + + auto info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) + { + throw std::runtime_error("png_create_info_struct failed."); + } + + if (image.Depth == 8) + { + if (image.Palette == nullptr) + { + throw std::runtime_error("Expected a palette for 8-bit image."); + } + + // Set the palette + png_palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); + if (png_palette == nullptr) + { + throw std::runtime_error("png_malloc failed."); + } + for (size_t i = 0; i < PNG_MAX_PALETTE_LENGTH; i++) + { + const auto entry = &image.Palette->entries[i]; + png_palette[i].blue = entry->blue; + png_palette[i].green = entry->green; + png_palette[i].red = entry->red; + } + png_set_PLTE(png_ptr, info_ptr, png_palette, PNG_MAX_PALETTE_LENGTH); + } + + png_set_write_fn(png_ptr, &ostream, PngWriteData, PngFlush); // Set error handler if (setjmp(png_jmpbuf(png_ptr))) @@ -188,115 +238,111 @@ namespace Imaging } // Write header + auto colourType = PNG_COLOR_TYPE_RGB_ALPHA; + if (image.Depth == 8) + { + png_byte transparentIndex = 0; + png_set_tRNS(png_ptr, info_ptr, &transparentIndex, 1, nullptr); + colourType = PNG_COLOR_TYPE_PALETTE; + } png_set_IHDR( - png_ptr, info_ptr, dpi->width, dpi->height, 8, - PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT - ); - png_byte transparentIndex = 0; - png_set_tRNS(png_ptr, info_ptr, &transparentIndex, 1, nullptr); + png_ptr, + info_ptr, + image.Width, + image.Height, + 8, + colourType, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); png_write_info(png_ptr, info_ptr); // Write pixels - uint8 * bits = dpi->bits; - for (int y = 0; y < dpi->height; y++) + auto pixels = image.Pixels.data(); + for (uint32 y = 0; y < image.Height; y++) { - png_write_row(png_ptr, (png_byte *)bits); - bits += stride; + png_write_row(png_ptr, (png_byte *)pixels); + pixels += image.Stride; } - // Finish png_write_end(png_ptr, nullptr); - result = true; + png_free(png_ptr, png_palette); + png_destroy_write_struct(&png_ptr, nullptr); } - catch (const std::exception &) + catch (const std::exception&) { + png_free(png_ptr, png_palette); + png_destroy_write_struct(&png_ptr, nullptr); + throw; } - - png_free(png_ptr, png_palette); - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return result; } - bool PngWrite32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path) + static IMAGE_FORMAT GetImageFormatFromPath(const std::string_view& path) { - bool result = false; - - // Setup PNG - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PngError, PngWarning); - if (png_ptr == nullptr) + if (String::EndsWith(path, ".png", true)) { - return false; + return IMAGE_FORMAT::PNG; } - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) + else if (String::EndsWith(path, ".bmp", true)) { - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return false; + return IMAGE_FORMAT::BITMAP; } - - try + else { - // Open file for writing - auto fs = FileStream(path, FILE_MODE_WRITE); - png_set_write_fn(png_ptr, &fs, PngWriteData, PngFlush); + return IMAGE_FORMAT::UNKNOWN; + } + } - // Set error handler - if (setjmp(png_jmpbuf(png_ptr))) + static Image ReadFromStream(std::istream& istream, IMAGE_FORMAT format) + { + switch (format) + { + case IMAGE_FORMAT::PNG: + return ReadPng(istream, false); + case IMAGE_FORMAT::PNG_32: + return ReadPng(istream, true); + case IMAGE_FORMAT::AUTOMATIC: + throw std::invalid_argument("format can not be automatic."); + default: + throw std::runtime_error(EXCEPTION_IMAGE_FORMAT_UNKNOWN); + } + } + + Image ReadFromFile(const std::string_view& path, IMAGE_FORMAT format) + { + switch (format) + { + case IMAGE_FORMAT::AUTOMATIC: + return ReadFromFile(path, GetImageFormatFromPath(path)); + default: { - throw std::runtime_error("PNG ERROR"); + std::ifstream fs(path.data(), std::ios::binary); + return ReadFromStream(fs, format); } - - // Write header - png_set_IHDR( - png_ptr, info_ptr, width, height, 8, - PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT - ); - png_write_info(png_ptr, info_ptr); - - // Write pixels - uint8 * bits = (uint8 *)pixels; - for (int y = 0; y < height; y++) - { - png_write_row(png_ptr, (png_byte *)bits); - bits += width * 4; - } - - // Finish - png_write_end(png_ptr, nullptr); - result = true; } - catch (const std::exception &) + } + + Image ReadFromBuffer(const std::vector& buffer, IMAGE_FORMAT format) + { + ivstream istream(buffer); + return ReadFromStream(istream, format); + } + + void WriteToFile(const std::string_view& path, const Image& image, IMAGE_FORMAT format) + { + switch (format) { + case IMAGE_FORMAT::AUTOMATIC: + WriteToFile(path, image, GetImageFormatFromPath(path)); + break; + case IMAGE_FORMAT::PNG: + { + std::ofstream fs(path.data(), std::ios::binary); + WritePng(fs, image); + break; + } + default: + throw std::runtime_error("Unknown image format."); } - - png_destroy_write_struct(&png_ptr, &info_ptr); - return result; - } - - static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length) - { - auto * fs = static_cast(png_get_io_ptr(png_ptr)); - fs->Read(data, length); - } - - static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length) - { - auto * fs = static_cast(png_get_io_ptr(png_ptr)); - fs->Write(data, length); - } - - static void PngFlush(png_structp png_ptr) - { - } - - static void PngWarning([[maybe_unused]] png_structp png_ptr, const char * b) - { - log_warning(b); - } - - static void PngError([[maybe_unused]] png_structp png_ptr, const char * b) - { - log_error(b); } } diff --git a/src/openrct2/core/Imaging.h b/src/openrct2/core/Imaging.h index 4afec47186..104ad25ca5 100644 --- a/src/openrct2/core/Imaging.h +++ b/src/openrct2/core/Imaging.h @@ -16,13 +16,43 @@ #pragma once +#include +#include +#include #include "../common.h" struct rct_drawpixelinfo; struct rct_palette; +enum class IMAGE_FORMAT +{ + UNKNOWN, + AUTOMATIC, // Automatically detect from file extension + BITMAP, + PNG, + PNG_32, // Force load to 32bpp buffer +}; + +struct Image +{ + // Meta + uint32 Width{}; + uint32 Height{}; + uint32 Depth{}; + + // Data + std::vector Pixels; + std::unique_ptr Palette; + uint32 Stride{}; +}; + + namespace Imaging { + Image ReadFromFile(const std::string_view& path, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); + Image ReadFromBuffer(const std::vector& buffer, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); + void WriteToFile(const std::string_view& path, const Image& image, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); + bool PngRead(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth); bool PngWrite(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path); bool PngWrite32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path); diff --git a/src/openrct2/core/String.cpp b/src/openrct2/core/String.cpp index 2e9ef839c0..659c0b111a 100644 --- a/src/openrct2/core/String.cpp +++ b/src/openrct2/core/String.cpp @@ -160,6 +160,16 @@ namespace String return StartsWith(str.c_str(), match.c_str(), ignoreCase); } + bool EndsWith(const std::string_view& str, const std::string_view& match, bool ignoreCase) + { + if (str.size() >= match.size()) + { + auto view = str.substr(str.size() - match.size()); + return Equals(view.data(), match.data(), ignoreCase); + } + return false; + } + size_t IndexOf(const utf8 * str, utf8 match, size_t startIndex) { const utf8 * ch = str + startIndex; diff --git a/src/openrct2/core/String.hpp b/src/openrct2/core/String.hpp index 80d66578b7..0629585f5b 100644 --- a/src/openrct2/core/String.hpp +++ b/src/openrct2/core/String.hpp @@ -52,6 +52,7 @@ namespace String bool Equals(const utf8 * a, const utf8 * b, bool ignoreCase = false); bool StartsWith(const utf8 * str, const utf8 * match, bool ignoreCase = false); bool StartsWith(const std::string &str, const std::string &match, bool ignoreCase = false); + bool EndsWith(const std::string_view& str, const std::string_view& match, bool ignoreCase = false); size_t IndexOf(const utf8 * str, utf8 match, size_t startIndex = 0); ptrdiff_t LastIndexOf(const utf8 * str, utf8 match); From ac3233b81915e65d9c5e8fa876c4582d3a12f213 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 May 2018 21:41:41 +0100 Subject: [PATCH 04/13] Change consumers to use new imaging API --- src/openrct2/CmdlineSprite.cpp | 45 +++++++---- src/openrct2/core/Imaging.cpp | 2 +- src/openrct2/core/Imaging.h | 5 +- src/openrct2/interface/Screenshot.cpp | 52 +++++++++++-- src/openrct2/world/MapGen.cpp | 106 ++++++++++++-------------- 5 files changed, 128 insertions(+), 82 deletions(-) diff --git a/src/openrct2/CmdlineSprite.cpp b/src/openrct2/CmdlineSprite.cpp index bed9975a7e..ae1416712a 100644 --- a/src/openrct2/CmdlineSprite.cpp +++ b/src/openrct2/CmdlineSprite.cpp @@ -233,10 +233,23 @@ static bool sprite_file_export(sint32 spriteIndex, const char *outPath) gfx_bmp_sprite_to_buffer((uint8*)spriteFilePalette, spriteHeader->offset, pixels, spriteHeader, &dpi, spriteHeader->height, spriteHeader->width, IMAGE_TYPE_DEFAULT); } - if (Imaging::PngWrite(&dpi, (rct_palette*)spriteFilePalette, outPath)) { + auto const pixels8 = dpi.bits; + auto const pixelsLen = (dpi.width + dpi.pitch) * dpi.height; + try + { + Image image; + image.Width = dpi.width; + image.Height = dpi.height; + image.Depth = 8; + image.Stride = dpi.width + dpi.pitch; + image.Palette = std::make_unique(*((rct_palette *)&spriteFilePalette)); + image.Pixels = std::vector(pixels8, pixels8 + pixelsLen); + Imaging::WriteToFile(outPath, image, IMAGE_FORMAT::PNG); return true; - } else { - fprintf(stderr, "Error writing PNG"); + } + catch (const std::exception& e) + { + fprintf(stderr, "Unable to write png: %s", e.what()); return false; } } @@ -300,29 +313,34 @@ static sint32 get_palette_index(sint16 *colour) static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offset, bool keep_palette, rct_g1_element *outElement, uint8 **outBuffer, int *outBufferLength, sint32 mode) { - uint8 *pixels; - uint32 width, height; - sint32 bitDepth; - if (!Imaging::PngRead(&pixels, &width, &height, !keep_palette, path, &bitDepth)) + Image image; + auto format = keep_palette ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32; + try { - fprintf(stderr, "Error reading PNG\n"); + image = std::move(Imaging::ReadFromFile(path, format)); + } + catch (const std::exception &e) + { + fprintf(stderr, "Error reading PNG: %s\n", e.what()); return false; } - if (width > 256 || height > 256) + if (image.Width > 256 || image.Height > 256) { fprintf(stderr, "Only images 256x256 or less are supported.\n"); - free(pixels); return false; } - if (keep_palette && (bitDepth != 8)) + if (keep_palette && (image.Depth != 8)) { - fprintf(stderr, "Image is not palletted, it has bit depth of %d\n", bitDepth); - free(pixels); + fprintf(stderr, "Image is not palletted, it has bit depth of %d\n", image.Depth); return false; } + const auto width = image.Width; + const auto height = image.Height; + const auto pixels = image.Pixels.data(); + memcpy(spriteFilePalette, CmdlineSprite::_standardPalette, 256 * 4); uint8 *buffer = (uint8 *)malloc((height * 2) + (width * height * 16)); @@ -497,7 +515,6 @@ static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offse } } } - free(pixels); free(rgbaSrc_orig); sint32 bufferLength = (sint32)(dst - buffer); diff --git a/src/openrct2/core/Imaging.cpp b/src/openrct2/core/Imaging.cpp index 9f76aa46c5..099a652427 100644 --- a/src/openrct2/core/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -277,7 +277,7 @@ namespace Imaging } } - static IMAGE_FORMAT GetImageFormatFromPath(const std::string_view& path) + IMAGE_FORMAT GetImageFormatFromPath(const std::string_view& path) { if (String::EndsWith(path, ".png", true)) { diff --git a/src/openrct2/core/Imaging.h b/src/openrct2/core/Imaging.h index 104ad25ca5..10b9c74051 100644 --- a/src/openrct2/core/Imaging.h +++ b/src/openrct2/core/Imaging.h @@ -49,11 +49,8 @@ struct Image namespace Imaging { + IMAGE_FORMAT GetImageFormatFromPath(const std::string_view& path); Image ReadFromFile(const std::string_view& path, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); Image ReadFromBuffer(const std::vector& buffer, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); void WriteToFile(const std::string_view& path, const Image& image, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); - - bool PngRead(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth); - bool PngWrite(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path); - bool PngWrite32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path); } diff --git a/src/openrct2/interface/Screenshot.cpp b/src/openrct2/interface/Screenshot.cpp index f31aaa0434..3f0fde6d41 100644 --- a/src/openrct2/interface/Screenshot.cpp +++ b/src/openrct2/interface/Screenshot.cpp @@ -41,6 +41,29 @@ using namespace OpenRCT2; uint8 gScreenshotCountdown = 0; +static bool WriteDpiToFile(const std::string_view& path, const rct_drawpixelinfo * dpi, const rct_palette& palette) +{ + auto const pixels8 = dpi->bits; + auto const pixelsLen = (dpi->width + dpi->pitch) * dpi->height; + try + { + Image image; + image.Width = dpi->width; + image.Height = dpi->height; + image.Depth = 8; + image.Stride = dpi->width + dpi->pitch; + image.Palette = std::make_unique(palette); + image.Pixels = std::vector(pixels8, pixels8 + pixelsLen); + Imaging::WriteToFile(path, image, IMAGE_FORMAT::PNG); + return true; + } + catch (const std::exception& e) + { + log_error("Unable to write png: %s", e.what()); + return false; + } +} + /** * * rct2: 0x006E3AEC @@ -153,9 +176,12 @@ sint32 screenshot_dump_png(rct_drawpixelinfo *dpi) rct_palette renderedPalette; screenshot_get_rendered_palette(&renderedPalette); - if (Imaging::PngWrite(dpi, &renderedPalette, path)) { + if (WriteDpiToFile(path, dpi, renderedPalette)) + { return index; - } else { + } + else + { return -1; } } @@ -169,9 +195,23 @@ sint32 screenshot_dump_png_32bpp(sint32 width, sint32 height, const void *pixels return -1; } - if (Imaging::PngWrite32bpp(width, height, pixels, path)) { + const auto pixels8 = (const uint8 *)pixels; + const auto pixelsLen = width * 4 * height; + + try + { + Image image; + image.Width = width; + image.Height = height; + image.Depth = 32; + image.Stride = width * 4; + image.Pixels = std::vector(pixels8, pixels8 + pixelsLen); + Imaging::WriteToFile(path, image, IMAGE_FORMAT::PNG_32); return index; - } else { + } + catch (const std::exception &e) + { + log_error("Unable to save screenshot: %s", e.what()); return -1; } } @@ -259,7 +299,7 @@ void screenshot_giant() rct_palette renderedPalette; screenshot_get_rendered_palette(&renderedPalette); - Imaging::PngWrite(&dpi, &renderedPalette, path); + WriteDpiToFile(path, &dpi, renderedPalette); free(dpi.bits); @@ -576,7 +616,7 @@ sint32 cmdline_for_screenshot(const char * * argv, sint32 argc, ScreenshotOption rct_palette renderedPalette; screenshot_get_rendered_palette(&renderedPalette); - Imaging::PngWrite(&dpi, &renderedPalette, outputPath); + WriteDpiToFile(outputPath, &dpi, renderedPalette); free(dpi.bits); drawing_engine_dispose(); diff --git a/src/openrct2/world/MapGen.cpp b/src/openrct2/world/MapGen.cpp index a1e054fa14..05e1e24b4e 100644 --- a/src/openrct2/world/MapGen.cpp +++ b/src/openrct2/world/MapGen.cpp @@ -647,75 +647,67 @@ static void mapgen_simplex(mapgen_settings * settings) bool mapgen_load_heightmap(const utf8 * path) { - const char * extension = path_get_extension(path); - uint8 * pixels; - size_t pitch; - uint32 numChannels; - uint32 width, height; - - if (String::Equals(extension, ".png", false)) + auto format = Imaging::GetImageFormatFromPath(path); + if (format == IMAGE_FORMAT::PNG) { - sint32 bitDepth; - if (!Imaging::PngRead(&pixels, &width, &height, true, path, &bitDepth)) + // Promote to 32-bit + format = IMAGE_FORMAT::PNG_32; + } + + try + { + auto image = Imaging::ReadFromFile(path, format); + if (image.Width != image.Height) { - log_warning("Error reading PNG"); - context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_PNG); + context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_WIDTH_AND_HEIGHT_DO_NOT_MATCH); return false; } - numChannels = 4; - pitch = width * numChannels; - } - else if (strcicmp(extension, ".bmp") == 0) - { - if (!context_read_bmp((void **) &pixels, &width, &height, path)) + auto size = image.Width; + if (image.Width > MAXIMUM_MAP_SIZE_PRACTICAL) { - // ReadBMP contains context_show_error calls - return false; + context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_HEIHGT_MAP_TOO_BIG); + size = std::min(image.Height, MAXIMUM_MAP_SIZE_PRACTICAL); } - numChannels = 4; - pitch = width * numChannels; - } - else - { - openrct2_assert(false, "A file with an invalid file extension was selected."); - return false; - } + // Allocate memory for the height map values, one byte pixel + delete[] _heightMapData.mono_bitmap; + _heightMapData.mono_bitmap = new uint8[size * size]; + _heightMapData.width = size; + _heightMapData.height = size; - if (width != height) - { - context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_WIDTH_AND_HEIGHT_DO_NOT_MATCH); - free(pixels); - return false; - } - - if (width > MAXIMUM_MAP_SIZE_PRACTICAL) - { - context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_HEIHGT_MAP_TOO_BIG); - width = height = Math::Min(height, (uint32)MAXIMUM_MAP_SIZE_PRACTICAL); - } - - // Allocate memory for the height map values, one byte pixel - delete[] _heightMapData.mono_bitmap; - _heightMapData.mono_bitmap = new uint8[width * height]; - _heightMapData.width = width; - _heightMapData.height = height; - - // Copy average RGB value to mono bitmap - for (uint32 x = 0; x < _heightMapData.width; x++) - { - for (uint32 y = 0; y < _heightMapData.height; y++) + // Copy average RGB value to mono bitmap + constexpr auto numChannels = 4; + const auto pitch = image.Stride; + const auto pixels = image.Pixels.data(); + for (uint32 x = 0; x < _heightMapData.width; x++) { - const uint8 red = pixels[x * numChannels + y * pitch]; - const uint8 green = pixels[x * numChannels + y * pitch + 1]; - const uint8 blue = pixels[x * numChannels + y * pitch + 2]; - _heightMapData.mono_bitmap[x + y * _heightMapData.width] = (red + green + blue) / 3; + for (uint32 y = 0; y < _heightMapData.height; y++) + { + const auto red = pixels[x * numChannels + y * pitch]; + const auto green = pixels[x * numChannels + y * pitch + 1]; + const auto blue = pixels[x * numChannels + y * pitch + 2]; + _heightMapData.mono_bitmap[x + y * _heightMapData.width] = (red + green + blue) / 3; + } } + return true; + } + catch (const std::exception& e) + { + switch (format) + { + case IMAGE_FORMAT::BITMAP: + context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_BITMAP); + break; + case IMAGE_FORMAT::PNG_32: + context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_PNG); + break; + default: + log_error("Unable to load height map image: %s", e.what()); + break; + } + return false; } - - free(pixels); - return true; } /** From 229c50dff90a6c9ad93e9e5f90161ff24591fb47 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 May 2018 23:39:03 +0100 Subject: [PATCH 05/13] Add bitmap reading support --- src/openrct2-ui/Ui.cpp | 2 + src/openrct2-ui/drawing/BitmapReader.cpp | 110 +++++++++++++++++++++++ src/openrct2-ui/drawing/BitmapReader.h | 17 ++++ src/openrct2/core/Imaging.cpp | 26 +++++- src/openrct2/core/Imaging.h | 6 ++ 5 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/openrct2-ui/drawing/BitmapReader.cpp create mode 100644 src/openrct2-ui/drawing/BitmapReader.h diff --git a/src/openrct2-ui/Ui.cpp b/src/openrct2-ui/Ui.cpp index 7a6b834e2e..b87e152c71 100644 --- a/src/openrct2-ui/Ui.cpp +++ b/src/openrct2-ui/Ui.cpp @@ -20,6 +20,7 @@ #include #include #include "audio/AudioContext.h" +#include "drawing/BitmapReader.h" #include "Ui.h" #include "UiContext.h" @@ -46,6 +47,7 @@ int main(int argc, const char * * argv) { int runGame = cmdline_run(argv, argc); core_init(); + RegisterBitmapReader(); if (runGame == 1) { if (gOpenRCT2Headless) diff --git a/src/openrct2-ui/drawing/BitmapReader.cpp b/src/openrct2-ui/drawing/BitmapReader.cpp new file mode 100644 index 0000000000..80d1fcc14a --- /dev/null +++ b/src/openrct2-ui/drawing/BitmapReader.cpp @@ -0,0 +1,110 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include +#include +#include "BitmapReader.h" + +static std::vector ReadToVector(std::istream &stream) +{ + std::vector result; + if (!stream.eof() && !stream.fail()) + { + stream.seekg(0, std::ios_base::end); + auto size = stream.tellg(); + result.resize(size); + stream.seekg(0, std::ios_base::beg); + stream.read((char *)result.data(), size); + } + return result; +} + +static Image ReadBitmap(std::istream &istream, IMAGE_FORMAT format) +{ + auto buffer = ReadToVector(istream); + auto sdlStream = SDL_RWFromConstMem(buffer.data(), buffer.size()); + auto bitmap = SDL_LoadBMP_RW(sdlStream, 1); + if (bitmap != nullptr) + { + auto numChannels = bitmap->format->BytesPerPixel; + if (numChannels < 3 || bitmap->format->BitsPerPixel < 24) + { + SDL_FreeSurface(bitmap); + throw std::runtime_error("Only 24-bit bitmaps are supported."); + } + + // Copy pixels over, then discard the surface + if (SDL_LockSurface(bitmap) == 0) + { + Image image; + image.Width = bitmap->w; + image.Height = bitmap->h; + image.Depth = 32; + image.Pixels.resize(bitmap->w * bitmap->h * 4); + image.Stride = bitmap->w * 4; + + // Clear image with 0xFF + std::fill(image.Pixels.begin(), image.Pixels.end(), 0xFF); + + // Copy pixels over + auto src = (const uint8 *)bitmap->pixels; + auto dst = image.Pixels.data(); + if (numChannels == 4) + { + for (sint32 y = 0; y < bitmap->h; y++) + { + std::memcpy(dst, src, bitmap->w); + src += bitmap->pitch; + dst += bitmap->w; + } + } + else + { + for (sint32 y = 0; y < bitmap->h; y++) + { + for (sint32 x = 0; x < bitmap->w; x++) + { + std::memcpy(dst, src, 3); + src += 3; + dst += 4; + } + src += bitmap->pitch - (bitmap->w * 3); + } + } + SDL_UnlockSurface(bitmap); + SDL_FreeSurface(bitmap); + + return image; + } + else + { + SDL_FreeSurface(bitmap); + throw std::runtime_error("Unable to lock surface."); + } + } + else + { + throw std::runtime_error(SDL_GetError()); + } +} + +void RegisterBitmapReader() +{ + Imaging::SetReader(IMAGE_FORMAT::BITMAP, ReadBitmap); +} diff --git a/src/openrct2-ui/drawing/BitmapReader.h b/src/openrct2-ui/drawing/BitmapReader.h new file mode 100644 index 0000000000..c7d34c7183 --- /dev/null +++ b/src/openrct2-ui/drawing/BitmapReader.h @@ -0,0 +1,17 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +void RegisterBitmapReader(); diff --git a/src/openrct2/core/Imaging.cpp b/src/openrct2/core/Imaging.cpp index 099a652427..3ccfce78f6 100644 --- a/src/openrct2/core/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -56,6 +56,8 @@ namespace Imaging constexpr auto EXCEPTION_IMAGE_FORMAT_UNSUPPORTED = "Unsupported image format."; constexpr auto EXCEPTION_IMAGE_FORMAT_UNKNOWN = "Unknown image format."; + static std::unordered_map _readerImplementations; + static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length) { auto istream = static_cast(png_get_io_ptr(png_ptr)); @@ -178,7 +180,7 @@ namespace Imaging img.Height = pngHeight; img.Depth = expandTo32 ? 32 : 8; img.Pixels = std::move(pngPixels); - img.Stride = pngWidth; + img.Stride = pngWidth * (expandTo32 ? 4 : 1); return img; } catch (const std::exception &) @@ -293,6 +295,21 @@ namespace Imaging } } + static ImageReaderFunc GetReader(IMAGE_FORMAT format) + { + auto result = _readerImplementations.find(format); + if (result != _readerImplementations.end()) + { + return result->second; + } + return {}; + } + + void SetReader(IMAGE_FORMAT format, ImageReaderFunc impl) + { + _readerImplementations[format] = impl; + } + static Image ReadFromStream(std::istream& istream, IMAGE_FORMAT format) { switch (format) @@ -304,7 +321,14 @@ namespace Imaging case IMAGE_FORMAT::AUTOMATIC: throw std::invalid_argument("format can not be automatic."); default: + { + auto impl = GetReader(format); + if (impl) + { + return impl(istream, format); + } throw std::runtime_error(EXCEPTION_IMAGE_FORMAT_UNKNOWN); + } } } diff --git a/src/openrct2/core/Imaging.h b/src/openrct2/core/Imaging.h index 10b9c74051..0eda061d6e 100644 --- a/src/openrct2/core/Imaging.h +++ b/src/openrct2/core/Imaging.h @@ -16,10 +16,13 @@ #pragma once +#include +#include #include #include #include #include "../common.h" +#include "../drawing/Drawing.h" struct rct_drawpixelinfo; struct rct_palette; @@ -46,6 +49,7 @@ struct Image uint32 Stride{}; }; +using ImageReaderFunc = std::function; namespace Imaging { @@ -53,4 +57,6 @@ namespace Imaging Image ReadFromFile(const std::string_view& path, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); Image ReadFromBuffer(const std::vector& buffer, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); void WriteToFile(const std::string_view& path, const Image& image, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC); + + void SetReader(IMAGE_FORMAT format, ImageReaderFunc impl); } From 7eb1703f36bd59bd6bd34057a5085fe7d69a3767 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 May 2018 23:42:13 +0100 Subject: [PATCH 06/13] Remove old bitmap code via UiContext --- src/openrct2-ui/UiContext.cpp | 63 ------------------------ src/openrct2-ui/drawing/BitmapReader.cpp | 2 + src/openrct2/Context.cpp | 5 -- src/openrct2/ui/DummyUiContext.cpp | 3 -- src/openrct2/ui/UiContext.h | 5 -- 5 files changed, 2 insertions(+), 76 deletions(-) diff --git a/src/openrct2-ui/UiContext.cpp b/src/openrct2-ui/UiContext.cpp index 7696ca05cb..da87bb3562 100644 --- a/src/openrct2-ui/UiContext.cpp +++ b/src/openrct2-ui/UiContext.cpp @@ -577,69 +577,6 @@ public: return _windowManager; } - bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) override - { - auto bitmap = SDL_LoadBMP(path.c_str()); - if (bitmap != nullptr) - { - sint32 numChannels = bitmap->format->BytesPerPixel; - if (numChannels < 3 || bitmap->format->BitsPerPixel < 24) - { - context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_24_BIT_BITMAP); - SDL_FreeSurface(bitmap); - return false; - } - - // Copy pixels over, then discard the surface - *outPixels = nullptr; - *outWidth = bitmap->w; - *outHeight = bitmap->h; - if (SDL_LockSurface(bitmap) == 0) - { - *outPixels = malloc(bitmap->w * bitmap->h * 4); - memset(*outPixels, 0xFF, bitmap->w * bitmap->h); - - auto src = (const uint8 *)bitmap->pixels; - auto dst = (uint8 *)*outPixels; - if (numChannels == 4) - { - for (sint32 y = 0; y < bitmap->h; y++) - { - memcpy(dst, src, bitmap->w); - src += bitmap->pitch; - dst += bitmap->w; - } - } - else - { - for (sint32 y = 0; y < bitmap->h; y++) - { - for (sint32 x = 0; x < bitmap->w; x++) - { - memcpy(dst, src, 3); - src += 3; - dst += 4; - } - src += bitmap->pitch - (bitmap->w * 3); - } - } - SDL_UnlockSurface(bitmap); - } - else - { - return false; - } - SDL_FreeSurface(bitmap); - return true; - } - else - { - log_warning("Failed to load bitmap: %s", SDL_GetError()); - context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_BITMAP); - return false; - } - } - bool SetClipboardText(const utf8* target) override { return (SDL_SetClipboardText(target) == 0); diff --git a/src/openrct2-ui/drawing/BitmapReader.cpp b/src/openrct2-ui/drawing/BitmapReader.cpp index 80d1fcc14a..754531f182 100644 --- a/src/openrct2-ui/drawing/BitmapReader.cpp +++ b/src/openrct2-ui/drawing/BitmapReader.cpp @@ -35,6 +35,8 @@ static std::vector ReadToVector(std::istream &stream) return result; } +// TODO Bitmaps aren't very complicated to read so we should probably just write our +// own implementation in libopenrct2 and spare the AOT implementation registration. static Image ReadBitmap(std::istream &istream, IMAGE_FORMAT format) { auto buffer = ReadToVector(istream); diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 605c37f586..098b870f84 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -1127,11 +1127,6 @@ void context_input_handle_keyboard(bool isTitle) windowManager->HandleKeyboard(isTitle); } -bool context_read_bmp(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const utf8 * path) -{ - return GetContext()->GetUiContext()->ReadBMP(outPixels, outWidth, outHeight, std::string(path)); -} - void context_quit() { GetContext()->Quit(); diff --git a/src/openrct2/ui/DummyUiContext.cpp b/src/openrct2/ui/DummyUiContext.cpp index 036b81b60e..a640447119 100644 --- a/src/openrct2/ui/DummyUiContext.cpp +++ b/src/openrct2/ui/DummyUiContext.cpp @@ -91,9 +91,6 @@ namespace OpenRCT2::Ui return _windowManager; } - // Misc - bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) override { return false; } - // Clipboard bool SetClipboardText(const utf8* target) override { return false; } diff --git a/src/openrct2/ui/UiContext.h b/src/openrct2/ui/UiContext.h index b80c725ea2..86f63482ec 100644 --- a/src/openrct2/ui/UiContext.h +++ b/src/openrct2/ui/UiContext.h @@ -140,11 +140,6 @@ namespace OpenRCT2 // In-game UI virtual IWindowManager * GetWindowManager() abstract; - // Misc. - // HACK: This should either be implemented ourselves in libopenrct2 - // or the mapgen height map code is moved to libopenrct2ui. - virtual bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) abstract; - // Clipboard virtual bool SetClipboardText(const utf8* target) abstract; }; From f0348dafb786ceecbf05c054ebb8e1c715251f77 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 9 May 2018 19:27:36 +0100 Subject: [PATCH 07/13] Fix MSVC warning --- src/openrct2-ui/drawing/BitmapReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2-ui/drawing/BitmapReader.cpp b/src/openrct2-ui/drawing/BitmapReader.cpp index 754531f182..ca78151cae 100644 --- a/src/openrct2-ui/drawing/BitmapReader.cpp +++ b/src/openrct2-ui/drawing/BitmapReader.cpp @@ -40,7 +40,7 @@ static std::vector ReadToVector(std::istream &stream) static Image ReadBitmap(std::istream &istream, IMAGE_FORMAT format) { auto buffer = ReadToVector(istream); - auto sdlStream = SDL_RWFromConstMem(buffer.data(), buffer.size()); + auto sdlStream = SDL_RWFromConstMem(buffer.data(), (int)buffer.size()); auto bitmap = SDL_LoadBMP_RW(sdlStream, 1); if (bitmap != nullptr) { From bffbf5857d64649bddfc4d161c6607cabc9bbe00 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 9 May 2018 19:33:08 +0100 Subject: [PATCH 08/13] Refactor image importer out into new class --- src/openrct2/CmdlineSprite.cpp | 288 +----------- src/openrct2/core/Imaging.h | 8 + src/openrct2/drawing/ImageImporter.cpp | 596 +++++++++++++++++++++++++ src/openrct2/drawing/ImageImporter.h | 60 +++ 4 files changed, 679 insertions(+), 273 deletions(-) create mode 100644 src/openrct2/drawing/ImageImporter.cpp create mode 100644 src/openrct2/drawing/ImageImporter.h diff --git a/src/openrct2/CmdlineSprite.cpp b/src/openrct2/CmdlineSprite.cpp index ae1416712a..a7e4bf5732 100644 --- a/src/openrct2/CmdlineSprite.cpp +++ b/src/openrct2/CmdlineSprite.cpp @@ -20,16 +20,15 @@ #include #include #include "CmdlineSprite.h" -#include "drawing/Drawing.h" #include "core/Imaging.h" +#include "drawing/Drawing.h" +#include "drawing/ImageImporter.h" #include "localisation/Language.h" #include "OpenRCT2.h" #include "platform/platform.h" #include "util/Util.h" -#define MODE_DEFAULT 0 -#define MODE_CLOSEST 1 -#define MODE_DITHERING 2 +using namespace OpenRCT2::Drawing; #pragma pack(push, 1) @@ -254,283 +253,26 @@ static bool sprite_file_export(sint32 spriteIndex, const char *outPath) } } -static bool is_transparent_pixel(const sint16 *colour){ - return colour[3] < 128; -} - -// Returns true if pixel index is an index not used for remapping -static bool is_changable_pixel(sint32 palette_index) { - if (palette_index == -1) - return true; - if (palette_index == 0) - return false; - if (palette_index >= 203 && palette_index < 214) - return false; - if (palette_index == 226) - return false; - if (palette_index >= 227 && palette_index < 229) - return false; - if (palette_index >= 243) - return false; - return true; -} - -static sint32 get_closest_palette_index(const sint16 *colour){ - uint32 smallest_error = (uint32)-1; - sint32 best_match = -1; - - for (sint32 x = 0; x < 256; x++){ - if (is_changable_pixel(x)){ - uint32 error = - ((sint16)(spriteFilePalette[x].r) - colour[0]) * ((sint16)(spriteFilePalette[x].r) - colour[0]) + - ((sint16)(spriteFilePalette[x].g) - colour[1]) * ((sint16)(spriteFilePalette[x].g) - colour[1]) + - ((sint16)(spriteFilePalette[x].b) - colour[2]) * ((sint16)(spriteFilePalette[x].b) - colour[2]); - - if (smallest_error == (uint32)-1 || smallest_error > error){ - best_match = x; - smallest_error = error; - } - } - } - return best_match; -} - -static sint32 get_palette_index(sint16 *colour) -{ - if (is_transparent_pixel(colour)) - return -1; - - for (sint32 i = 0; i < 256; i++) { - if ((sint16)(spriteFilePalette[i].r) != colour[0]) continue; - if ((sint16)(spriteFilePalette[i].g) != colour[1]) continue; - if ((sint16)(spriteFilePalette[i].b) != colour[2]) continue; - return i; - } - - return -1; -} - - static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offset, bool keep_palette, rct_g1_element *outElement, uint8 **outBuffer, int *outBufferLength, sint32 mode) { - Image image; - auto format = keep_palette ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32; try { - image = std::move(Imaging::ReadFromFile(path, format)); + auto format = keep_palette ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32; + auto image = Imaging::ReadFromFile(path, format); + + ImageImporter importer; + auto result = importer.Import(image, x_offset, y_offset, keep_palette, (ImageImporter::IMPORT_MODE)mode); + + *outElement = result.Element; + *outBuffer = (uint8 *)result.Buffer; + *outBufferLength = (int)result.BufferLength; + return true; } - catch (const std::exception &e) + catch (const std::exception& e) { - fprintf(stderr, "Error reading PNG: %s\n", e.what()); + fprintf(stderr, "%s\n", e.what()); return false; } - - if (image.Width > 256 || image.Height > 256) - { - fprintf(stderr, "Only images 256x256 or less are supported.\n"); - return false; - } - - if (keep_palette && (image.Depth != 8)) - { - fprintf(stderr, "Image is not palletted, it has bit depth of %d\n", image.Depth); - return false; - } - - const auto width = image.Width; - const auto height = image.Height; - const auto pixels = image.Pixels.data(); - - memcpy(spriteFilePalette, CmdlineSprite::_standardPalette, 256 * 4); - - uint8 *buffer = (uint8 *)malloc((height * 2) + (width * height * 16)); - memset(buffer, 0, (height * 2) + (width * height * 16)); - uint16 *yOffsets = (uint16*)buffer; - - // A larger range is needed for proper dithering - uint8 *palettedSrc = pixels; - sint16 *rgbaSrc = keep_palette? nullptr : (sint16 *)malloc(height * width * 4 * 2); - sint16 *rgbaSrc_orig = rgbaSrc; - if (!keep_palette) - { - for (uint32 x = 0; x < height * width * 4; x++) - { - rgbaSrc[x] = (sint16) pixels[x]; - } - } - - uint8 *dst = buffer + (height * 2); - - for (uint32 y = 0; y < height; y++) { - rle_code *previousCode, *currentCode; - - yOffsets[y] = (uint16)(dst - buffer); - - previousCode = nullptr; - currentCode = (rle_code*)dst; - dst += 2; - sint32 startX = 0; - sint32 npixels = 0; - bool pushRun = false; - for (uint32 x = 0; x < width; x++) { - sint32 paletteIndex; - - if (keep_palette) - { - paletteIndex = *palettedSrc; - // The 1st index is always transparent - if (paletteIndex == 0) - { - paletteIndex = -1; - } - } - else - { - paletteIndex = get_palette_index(rgbaSrc); - - if (mode == MODE_CLOSEST || mode == MODE_DITHERING) - { - if (paletteIndex == -1 && !is_transparent_pixel(rgbaSrc)) - { - paletteIndex = get_closest_palette_index(rgbaSrc); - } - } - - if (mode == MODE_DITHERING) - { - if (!is_transparent_pixel(rgbaSrc) && is_changable_pixel(get_palette_index(rgbaSrc))) - { - sint16 dr = rgbaSrc[0] - (sint16)(spriteFilePalette[paletteIndex].r); - sint16 dg = rgbaSrc[1] - (sint16)(spriteFilePalette[paletteIndex].g); - sint16 db = rgbaSrc[2] - (sint16)(spriteFilePalette[paletteIndex].b); - - if (x + 1 < width) - { - if (!is_transparent_pixel(rgbaSrc + 4) && is_changable_pixel(get_palette_index(rgbaSrc + 4))) - { - // Right - rgbaSrc[4] += dr * 7 / 16; - rgbaSrc[5] += dg * 7 / 16; - rgbaSrc[6] += db * 7 / 16; - } - } - - if (y + 1 < height) - { - if (x > 0) - { - if (!is_transparent_pixel(rgbaSrc + 4 * (width - 1)) && is_changable_pixel(get_palette_index(rgbaSrc + 4 * (width - 1)))) - { - // Bottom left - rgbaSrc[4 * (width - 1)] += dr * 3 / 16; - rgbaSrc[4 * (width - 1) + 1] += dg * 3 / 16; - rgbaSrc[4 * (width - 1) + 2] += db * 3 / 16; - } - } - - // Bottom - if (!is_transparent_pixel(rgbaSrc + 4 * width) && is_changable_pixel(get_palette_index(rgbaSrc + 4 * width))) - { - rgbaSrc[4 * width] += dr * 5 / 16; - rgbaSrc[4 * width + 1] += dg * 5 / 16; - rgbaSrc[4 * width + 2] += db * 5 / 16; - } - - if (x + 1 < width) - { - if (!is_transparent_pixel(rgbaSrc + 4 * (width + 1)) && is_changable_pixel(get_palette_index(rgbaSrc + 4 * (width + 1)))) - { - // Bottom right - rgbaSrc[4 * (width + 1)] += dr * 1 / 16; - rgbaSrc[4 * (width + 1) + 1] += dg * 1 / 16; - rgbaSrc[4 * (width + 1) + 2] += db * 1 / 16; - } - } - } - } - } - } - - rgbaSrc += 4; - palettedSrc += 1; - - if (paletteIndex == -1) - { - if (npixels != 0) - { - x--; - rgbaSrc -= 4; - palettedSrc -= 1; - pushRun = true; - } - } - else - { - if (npixels == 0) - { - startX = x; - } - - npixels++; - *dst++ = (uint8)paletteIndex; - } - if (npixels == 127 || x == width - 1) - { - pushRun = true; - } - - if (pushRun) - { - if (npixels > 0) - { - previousCode = currentCode; - currentCode->num_pixels = npixels; - currentCode->offset_x = startX; - - if (x == width - 1) - { - currentCode->num_pixels |= 0x80; - } - - currentCode = (rle_code*)dst; - dst += 2; - } - else - { - if (previousCode == nullptr) - { - currentCode->num_pixels = 0x80; - currentCode->offset_x = 0; - } - else - { - previousCode->num_pixels |= 0x80; - dst -= 2; - } - } - - startX = 0; - npixels = 0; - pushRun = false; - } - } - } - free(rgbaSrc_orig); - - sint32 bufferLength = (sint32)(dst - buffer); - buffer = (uint8 *)realloc(buffer, bufferLength); - - outElement->offset = buffer; - outElement->width = width; - outElement->height = height; - outElement->flags = G1_FLAG_RLE_COMPRESSION; - outElement->x_offset = x_offset; - outElement->y_offset = y_offset; - outElement->zoomed_offset = 0; - - *outBuffer = buffer; - *outBufferLength = bufferLength; - return true; } sint32 cmdline_for_sprite(const char **argv, sint32 argc) diff --git a/src/openrct2/core/Imaging.h b/src/openrct2/core/Imaging.h index 0eda061d6e..4716e8d8ec 100644 --- a/src/openrct2/core/Imaging.h +++ b/src/openrct2/core/Imaging.h @@ -27,6 +27,14 @@ struct rct_drawpixelinfo; struct rct_palette; +struct PaletteBGRA +{ + uint8 Blue{}; + uint8 Green{}; + uint8 Red{}; + uint8 Alpha{}; +}; + enum class IMAGE_FORMAT { UNKNOWN, diff --git a/src/openrct2/drawing/ImageImporter.cpp b/src/openrct2/drawing/ImageImporter.cpp new file mode 100644 index 0000000000..cfbba1a7db --- /dev/null +++ b/src/openrct2/drawing/ImageImporter.cpp @@ -0,0 +1,596 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#include +#include +#include "../core/Imaging.h" +#include "ImageImporter.h" + +using namespace OpenRCT2::Drawing; +using ImportResult = ImageImporter::ImportResult; + +struct RLECode +{ + uint8 NumPixels; + uint8 OffsetX; +}; + +ImportResult ImageImporter::Import( + const Image& image, + sint32 offsetX, + sint32 offsetY, + bool keepPalette, + IMPORT_MODE mode) const +{ + if (image.Width > 256 || image.Height > 256) + { + throw std::runtime_error("Only images 256x256 or less are supported."); + } + + if (keepPalette && image.Depth != 8) + { + throw std::runtime_error("Image is not palletted, it has bit depth of " + image.Depth); + } + + const auto width = image.Width; + const auto height = image.Height; + const auto pixels = image.Pixels.data(); + const auto palette = StandardPalette; + + auto buffer = (uint8 *)std::malloc((height * 2) + (width * height * 16)); + std::memset(buffer, 0, (height * 2) + (width * height * 16)); + auto yOffsets = (uint16 *)buffer; + + // A larger range is needed for proper dithering + auto palettedSrc = pixels; + std::unique_ptr rgbaSrcBuffer; + if (!keepPalette) + { + rgbaSrcBuffer = std::make_unique(height * width * 4); + } + + auto rgbaSrc = rgbaSrcBuffer.get(); + if (!keepPalette) + { + for (uint32 x = 0; x < height * width * 4; x++) + { + rgbaSrc[x] = (sint16)pixels[x]; + } + } + + auto dst = buffer + (height * 2); + for (uint32 y = 0; y < height; y++) + { + yOffsets[y] = (uint16)(dst - buffer); + + auto previousCode = (RLECode *)nullptr; + auto currentCode = (RLECode *)dst; + dst += 2; + + auto startX = 0; + auto npixels = 0; + bool pushRun = false; + for (uint32 x = 0; x < width; x++) + { + sint32 paletteIndex; + if (keepPalette) + { + paletteIndex = *palettedSrc; + // The 1st index is always transparent + if (paletteIndex == 0) + { + paletteIndex = -1; + } + } + else + { + paletteIndex = GetPaletteIndex(palette, rgbaSrc); + + if (mode == IMPORT_MODE::CLOSEST || mode == IMPORT_MODE::DITHERING) + { + if (paletteIndex == -1 && !IsTransparentPixel(rgbaSrc)) + { + paletteIndex = GetClosestPaletteIndex(palette, rgbaSrc); + } + } + + if (mode == IMPORT_MODE::DITHERING) + { + if (!IsTransparentPixel(rgbaSrc) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc))) + { + sint16 dr = rgbaSrc[0] - (sint16)(palette[paletteIndex].Red); + sint16 dg = rgbaSrc[1] - (sint16)(palette[paletteIndex].Green); + sint16 db = rgbaSrc[2] - (sint16)(palette[paletteIndex].Blue); + + if (x + 1 < width) + { + if (!IsTransparentPixel(rgbaSrc + 4) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4))) + { + // Right + rgbaSrc[4] += dr * 7 / 16; + rgbaSrc[5] += dg * 7 / 16; + rgbaSrc[6] += db * 7 / 16; + } + } + + if (y + 1 < height) + { + if (x > 0) + { + if (!IsTransparentPixel(rgbaSrc + 4 * (width - 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width - 1)))) + { + // Bottom left + rgbaSrc[4 * (width - 1)] += dr * 3 / 16; + rgbaSrc[4 * (width - 1) + 1] += dg * 3 / 16; + rgbaSrc[4 * (width - 1) + 2] += db * 3 / 16; + } + } + + // Bottom + if (!IsTransparentPixel(rgbaSrc + 4 * width) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * width))) + { + rgbaSrc[4 * width] += dr * 5 / 16; + rgbaSrc[4 * width + 1] += dg * 5 / 16; + rgbaSrc[4 * width + 2] += db * 5 / 16; + } + + if (x + 1 < width) + { + if (!IsTransparentPixel(rgbaSrc + 4 * (width + 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width + 1)))) + { + // Bottom right + rgbaSrc[4 * (width + 1)] += dr * 1 / 16; + rgbaSrc[4 * (width + 1) + 1] += dg * 1 / 16; + rgbaSrc[4 * (width + 1) + 2] += db * 1 / 16; + } + } + } + } + } + } + + rgbaSrc += 4; + palettedSrc += 1; + + if (paletteIndex == -1) + { + if (npixels != 0) + { + x--; + rgbaSrc -= 4; + palettedSrc -= 1; + pushRun = true; + } + } + else + { + if (npixels == 0) + { + startX = x; + } + + npixels++; + *dst++ = (uint8)paletteIndex; + } + if (npixels == 127 || x == width - 1) + { + pushRun = true; + } + + if (pushRun) + { + if (npixels > 0) + { + previousCode = currentCode; + currentCode->NumPixels = npixels; + currentCode->OffsetX = startX; + + if (x == width - 1) + { + currentCode->NumPixels |= 0x80; + } + + currentCode = (RLECode *)dst; + dst += 2; + } + else + { + if (previousCode == nullptr) + { + currentCode->NumPixels = 0x80; + currentCode->OffsetX = 0; + } + else + { + previousCode->NumPixels |= 0x80; + dst -= 2; + } + } + + startX = 0; + npixels = 0; + pushRun = false; + } + } + } + + auto bufferLength = (size_t)(dst - buffer); + buffer = (uint8 *)realloc(buffer, bufferLength); + + rct_g1_element outElement; + outElement.offset = buffer; + outElement.width = width; + outElement.height = height; + outElement.flags = G1_FLAG_RLE_COMPRESSION; + outElement.x_offset = offsetX; + outElement.y_offset = offsetY; + outElement.zoomed_offset = 0; + + ImportResult result; + result.Element = outElement; + result.Buffer = buffer; + result.BufferLength = bufferLength; + return result; +} + +sint32 ImageImporter::GetPaletteIndex(const PaletteBGRA * palette, sint16 * colour) +{ + if (!IsTransparentPixel(colour)) + { + for (sint32 i = 0; i < 256; i++) + { + if ((sint16)(palette[i].Red) == colour[0] && + (sint16)(palette[i].Green) == colour[1] && + (sint16)(palette[i].Blue) == colour[2]) + { + return i; + } + } + } + return -1; +} + +bool ImageImporter::IsTransparentPixel(const sint16 * colour) +{ + return colour[3] < 128; +} + +/** + * @returns true if pixel index is an index not used for remapping. + */ +bool ImageImporter::IsChangablePixel(sint32 paletteIndex) +{ + if (paletteIndex == -1) + return true; + if (paletteIndex == 0) + return false; + if (paletteIndex >= 203 && paletteIndex < 214) + return false; + if (paletteIndex == 226) + return false; + if (paletteIndex >= 227 && paletteIndex < 229) + return false; + if (paletteIndex >= 243) + return false; + return true; +} + +sint32 ImageImporter::GetClosestPaletteIndex(const PaletteBGRA * palette, const sint16 * colour) +{ + uint32 smallest_error = (uint32)-1; + sint32 best_match = -1; + + for (sint32 x = 0; x < 256; x++) { + if (IsChangablePixel(x)) { + uint32 error = + ((sint16)(palette[x].Red) - colour[0]) * ((sint16)(palette[x].Red) - colour[0]) + + ((sint16)(palette[x].Green) - colour[1]) * ((sint16)(palette[x].Green) - colour[1]) + + ((sint16)(palette[x].Blue) - colour[2]) * ((sint16)(palette[x].Blue) - colour[2]); + + if (smallest_error == (uint32)-1 || smallest_error > error) { + best_match = x; + smallest_error = error; + } + } + } + return best_match; +} + +const PaletteBGRA ImageImporter::StandardPalette[256] = +{ + // 0 (unused) + { 0, 0, 0, 255 }, + + // 1 - 9 (misc. e.g. font and water) + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 255 }, + + // + { 35, 35, 23, 255 }, + { 51, 51, 35, 255 }, + { 67, 67, 47, 255 }, + { 83, 83, 63, 255 }, + { 99, 99, 75, 255 }, + { 115, 115, 91, 255 }, + { 131, 131, 111, 255 }, + { 151, 151, 131, 255 }, + { 175, 175, 159, 255 }, + { 195, 195, 183, 255 }, + { 219, 219, 211, 255 }, + { 243, 243, 239, 255 }, + { 0, 47, 51, 255 }, + { 0, 59, 63, 255 }, + { 11, 75, 79, 255 }, + { 19, 91, 91, 255 }, + { 31, 107, 107, 255 }, + { 47, 123, 119, 255 }, + { 59, 139, 135, 255 }, + { 79, 155, 151, 255 }, + { 95, 175, 167, 255 }, + { 115, 191, 187, 255 }, + { 139, 207, 203, 255 }, + { 163, 227, 223, 255 }, + { 7, 43, 67, 255 }, + { 11, 59, 87, 255 }, + { 23, 75, 111, 255 }, + { 31, 87, 127, 255 }, + { 39, 99, 143, 255 }, + { 51, 115, 159, 255 }, + { 67, 131, 179, 255 }, + { 87, 151, 191, 255 }, + { 111, 175, 203, 255 }, + { 135, 199, 219, 255 }, + { 163, 219, 231, 255 }, + { 195, 239, 247, 255 }, + { 0, 27, 71, 255 }, + { 0, 43, 95, 255 }, + { 0, 63, 119, 255 }, + { 7, 83, 143, 255 }, + { 7, 111, 167, 255 }, + { 15, 139, 191, 255 }, + { 19, 167, 215, 255 }, + { 27, 203, 243, 255 }, + { 47, 231, 255, 255 }, + { 95, 243, 255, 255 }, + { 143, 251, 255, 255 }, + { 195, 255, 255, 255 }, + { 0, 0, 35, 255 }, + { 0, 0, 79, 255 }, + { 7, 7, 95, 255 }, + { 15, 15, 111, 255 }, + { 27, 27, 127, 255 }, + { 39, 39, 143, 255 }, + { 59, 59, 163, 255 }, + { 79, 79, 179, 255 }, + { 103, 103, 199, 255 }, + { 127, 127, 215, 255 }, + { 159, 159, 235, 255 }, + { 191, 191, 255, 255 }, + { 19, 51, 27, 255 }, + { 23, 63, 35, 255 }, + { 31, 79, 47, 255 }, + { 39, 95, 59, 255 }, + { 43, 111, 71, 255 }, + { 51, 127, 87, 255 }, + { 59, 143, 99, 255 }, + { 67, 155, 115, 255 }, + { 75, 171, 131, 255 }, + { 83, 187, 147, 255 }, + { 95, 203, 163, 255 }, + { 103, 219, 183, 255 }, + { 27, 55, 31, 255 }, + { 35, 71, 47, 255 }, + { 43, 83, 59, 255 }, + { 55, 99, 75, 255 }, + { 67, 111, 91, 255 }, + { 79, 135, 111, 255 }, + { 95, 159, 135, 255 }, + { 111, 183, 159, 255 }, + { 127, 207, 183, 255 }, + { 147, 219, 195, 255 }, + { 167, 231, 207, 255 }, + { 191, 247, 223, 255 }, + { 0, 63, 15, 255 }, + { 0, 83, 19, 255 }, + { 0, 103, 23, 255 }, + { 0, 123, 31, 255 }, + { 7, 143, 39, 255 }, + { 23, 159, 55, 255 }, + { 39, 175, 71, 255 }, + { 63, 191, 91, 255 }, + { 87, 207, 111, 255 }, + { 115, 223, 139, 255 }, + { 143, 239, 163, 255 }, + { 179, 255, 195, 255 }, + { 19, 43, 79, 255 }, + { 27, 55, 99, 255 }, + { 43, 71, 119, 255 }, + { 59, 87, 139, 255 }, + { 67, 99, 167, 255 }, + { 83, 115, 187, 255 }, + { 99, 131, 207, 255 }, + { 115, 151, 215, 255 }, + { 131, 171, 227, 255 }, + { 151, 191, 239, 255 }, + { 171, 207, 247, 255 }, + { 195, 227, 255, 255 }, + { 55, 19, 15, 255 }, + { 87, 43, 39, 255 }, + { 103, 55, 51, 255 }, + { 119, 67, 63, 255 }, + { 139, 83, 83, 255 }, + { 155, 99, 99, 255 }, + { 175, 119, 119, 255 }, + { 191, 139, 139, 255 }, + { 207, 159, 159, 255 }, + { 223, 183, 183, 255 }, + { 239, 211, 211, 255 }, + { 255, 239, 239, 255 }, + { 111, 27, 0, 255 }, + { 151, 39, 0, 255 }, + { 167, 51, 7, 255 }, + { 187, 67, 15, 255 }, + { 203, 83, 27, 255 }, + { 223, 103, 43, 255 }, + { 227, 135, 67, 255 }, + { 231, 163, 91, 255 }, + { 239, 187, 119, 255 }, + { 243, 211, 143, 255 }, + { 251, 231, 175, 255 }, + { 255, 247, 215, 255 }, + { 15, 43, 11, 255 }, + { 23, 55, 15, 255 }, + { 31, 71, 23, 255 }, + { 43, 83, 35, 255 }, + { 59, 99, 47, 255 }, + { 75, 115, 59, 255 }, + { 95, 135, 79, 255 }, + { 119, 155, 99, 255 }, + { 139, 175, 123, 255 }, + { 167, 199, 147, 255 }, + { 195, 219, 175, 255 }, + { 223, 243, 207, 255 }, + { 95, 0, 63, 255 }, + { 115, 7, 75, 255 }, + { 127, 15, 83, 255 }, + { 143, 31, 95, 255 }, + { 155, 43, 107, 255 }, + { 171, 63, 123, 255 }, + { 187, 83, 135, 255 }, + { 199, 103, 155, 255 }, + { 215, 127, 171, 255 }, + { 231, 155, 191, 255 }, + { 243, 195, 215, 255 }, + { 255, 235, 243, 255 }, + { 0, 0, 63, 255 }, + { 0, 0, 87, 255 }, + { 0, 0, 115, 255 }, + { 0, 0, 143, 255 }, + { 0, 0, 171, 255 }, + { 0, 0, 199, 255 }, + { 0, 7, 227, 255 }, + { 0, 7, 255, 255 }, + { 67, 79, 255, 255 }, + { 115, 123, 255, 255 }, + { 163, 171, 255, 255 }, + { 215, 219, 255, 255 }, + { 0, 39, 79, 255 }, + { 0, 51, 111, 255 }, + { 0, 63, 147, 255 }, + { 0, 71, 183, 255 }, + { 0, 79, 219, 255 }, + { 0, 83, 255, 255 }, + { 23, 111, 255, 255 }, + { 51, 139, 255, 255 }, + { 79, 163, 255, 255 }, + { 107, 183, 255, 255 }, + { 135, 203, 255, 255 }, + { 163, 219, 255, 255 }, + { 47, 51, 0, 255 }, + { 55, 63, 0, 255 }, + { 67, 75, 0, 255 }, + { 79, 87, 0, 255 }, + { 99, 107, 7, 255 }, + { 119, 127, 23, 255 }, + { 143, 147, 43, 255 }, + { 163, 167, 71, 255 }, + { 187, 187, 99, 255 }, + { 207, 207, 131, 255 }, + { 231, 231, 171, 255 }, + { 255, 255, 207, 255 }, + + // 203 - 214 (Secondary remap) + { 27, 0, 63, 255 }, + { 51, 0, 103, 255 }, + { 63, 11, 123, 255 }, + { 79, 23, 143, 255 }, + { 95, 31, 163, 255 }, + { 111, 39, 183, 255 }, + { 143, 59, 219, 255 }, + { 171, 91, 239, 255 }, + { 187, 119, 243, 255 }, + { 203, 151, 247, 255 }, + { 223, 183, 251, 255 }, + { 239, 215, 255, 255 }, + + // 214 - 225 (Brown) + { 0, 19, 39, 255 }, + { 7, 31, 55, 255 }, + { 15, 47, 71, 255 }, + { 31, 63, 91, 255 }, + { 51, 83, 107, 255 }, + { 75, 103, 123, 255 }, + { 107, 127, 143, 255 }, + { 127, 147, 163, 255 }, + { 147, 171, 187, 255 }, + { 171, 195, 207, 255 }, + { 195, 219, 231, 255 }, + { 223, 243, 255, 255 }, + + // 226 (unknown) + { 75, 75, 55, 255 }, + + // 227 - 229 (tertiary remap) + { 0, 183, 255, 255 }, + { 0, 219, 255, 255 }, + { 0, 255, 255, 255 }, + + // 230 - 239 (water) + { 99, 107, 7, 255 }, + { 99, 107, 7, 255 }, + { 135, 143, 39, 255 }, + { 123, 131, 27, 255 }, + { 99, 107, 7, 255 }, + { 151, 155, 55, 255 }, + { 151, 155, 55, 255 }, + { 227, 227, 155, 255 }, + { 203, 203, 115, 255 }, + { 151, 155, 55, 255 }, + + // 240 - 242 (chain lift) + { 91, 91, 67, 255 }, + { 107, 107, 83, 255 }, + { 123, 123, 99, 255 }, + + // Old 243 - 245, changed to nice shade remap below + // { 47, 47, 47, 255 }, + // { 47, 47, 47, 255 }, + // { 47, 71, 87, 255 }, + + // 243 to 254 (primary remap) + { 47, 51, 111, 255 }, + { 47, 55, 131, 255 }, + { 51, 63, 151, 255 }, + { 51, 67, 171, 255 }, + { 47, 75, 191, 255 }, + { 43, 79, 211, 255 }, + { 35, 87, 231, 255 }, + { 31, 95, 255, 255 }, + { 39, 127, 255, 255 }, + { 51, 155, 255, 255 }, + { 63, 183, 255, 255 }, + { 75, 207, 255, 255 }, + + // 255 (unused?) + { 0, 0, 0, 255 } +}; diff --git a/src/openrct2/drawing/ImageImporter.h b/src/openrct2/drawing/ImageImporter.h new file mode 100644 index 0000000000..7d7886fb08 --- /dev/null +++ b/src/openrct2/drawing/ImageImporter.h @@ -0,0 +1,60 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#include +#include "../core/Imaging.h" +#include "Drawing.h" + +struct Image; + +namespace OpenRCT2::Drawing +{ + /** + * Imports images to the internal RCT G1 format. + */ + class ImageImporter + { + public: + struct ImportResult + { + rct_g1_element Element{}; + void * Buffer{}; + size_t BufferLength{}; + }; + + enum class IMPORT_MODE + { + DEFAULT, + CLOSEST, + DITHERING, + }; + + ImportResult Import( + const Image& image, + sint32 offsetX = 0, + sint32 offsetY = 0, + bool keepPalette = false, + IMPORT_MODE mode = IMPORT_MODE::DEFAULT) const; + + private: + static const PaletteBGRA StandardPalette[256]; + + static sint32 GetPaletteIndex(const PaletteBGRA * palette, sint16 * colour); + static bool IsTransparentPixel(const sint16 * colour); + static bool IsChangablePixel(sint32 paletteIndex); + static sint32 GetClosestPaletteIndex(const PaletteBGRA * palette, const sint16 * colour); + }; +} From 428dd05dcfa623bec8d36f660052f54345c8ef3b Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 9 May 2018 20:42:58 +0100 Subject: [PATCH 09/13] Refactor ImageImporter --- src/openrct2/CmdlineSprite.cpp | 12 +- src/openrct2/drawing/ImageImporter.cpp | 185 +++++++++++++------------ src/openrct2/drawing/ImageImporter.h | 10 +- 3 files changed, 117 insertions(+), 90 deletions(-) diff --git a/src/openrct2/CmdlineSprite.cpp b/src/openrct2/CmdlineSprite.cpp index a7e4bf5732..140690956a 100644 --- a/src/openrct2/CmdlineSprite.cpp +++ b/src/openrct2/CmdlineSprite.cpp @@ -257,11 +257,17 @@ static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offse { try { - auto format = keep_palette ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32; - auto image = Imaging::ReadFromFile(path, format); + auto format = IMAGE_FORMAT::PNG_32; + auto flags = ImageImporter::IMPORT_FLAGS::RLE; + if (keep_palette) + { + format = IMAGE_FORMAT::PNG; + flags = (ImageImporter::IMPORT_FLAGS)(flags | ImageImporter::IMPORT_FLAGS::KEEP_PALETTE); + } ImageImporter importer; - auto result = importer.Import(image, x_offset, y_offset, keep_palette, (ImageImporter::IMPORT_MODE)mode); + auto image = Imaging::ReadFromFile(path, format); + auto result = importer.Import(image, x_offset, y_offset, flags, (ImageImporter::IMPORT_MODE)mode); *outElement = result.Element; *outBuffer = (uint8 *)result.Buffer; diff --git a/src/openrct2/drawing/ImageImporter.cpp b/src/openrct2/drawing/ImageImporter.cpp index cfbba1a7db..668907eb86 100644 --- a/src/openrct2/drawing/ImageImporter.cpp +++ b/src/openrct2/drawing/ImageImporter.cpp @@ -24,31 +24,37 @@ using ImportResult = ImageImporter::ImportResult; struct RLECode { - uint8 NumPixels; - uint8 OffsetX; + uint8 NumPixels{}; + uint8 OffsetX{}; }; +constexpr sint32 PALETTE_TRANSPARENT = -1; + ImportResult ImageImporter::Import( const Image& image, sint32 offsetX, sint32 offsetY, - bool keepPalette, + IMPORT_FLAGS flags, IMPORT_MODE mode) const { if (image.Width > 256 || image.Height > 256) { - throw std::runtime_error("Only images 256x256 or less are supported."); + throw std::invalid_argument("Only images 256x256 or less are supported."); } - if (keepPalette && image.Depth != 8) + if ((flags & IMPORT_FLAGS::KEEP_PALETTE) && image.Depth != 8) { - throw std::runtime_error("Image is not palletted, it has bit depth of " + image.Depth); + throw std::invalid_argument("Image is not palletted, it has bit depth of " + image.Depth); + } + + if (!(flags & IMPORT_FLAGS::RLE)) + { + throw std::invalid_argument("Only RLE image import is currently supported."); } const auto width = image.Width; const auto height = image.Height; const auto pixels = image.Pixels.data(); - const auto palette = StandardPalette; auto buffer = (uint8 *)std::malloc((height * 2) + (width * height * 16)); std::memset(buffer, 0, (height * 2) + (width * height * 16)); @@ -57,13 +63,13 @@ ImportResult ImageImporter::Import( // A larger range is needed for proper dithering auto palettedSrc = pixels; std::unique_ptr rgbaSrcBuffer; - if (!keepPalette) + if (!(flags & IMPORT_FLAGS::KEEP_PALETTE)) { rgbaSrcBuffer = std::make_unique(height * width * 4); } auto rgbaSrc = rgbaSrcBuffer.get(); - if (!keepPalette) + if (!(flags & IMPORT_FLAGS::KEEP_PALETTE)) { for (uint32 x = 0; x < height * width * 4; x++) { @@ -86,86 +92,24 @@ ImportResult ImageImporter::Import( for (uint32 x = 0; x < width; x++) { sint32 paletteIndex; - if (keepPalette) + if (flags & IMPORT_FLAGS::KEEP_PALETTE) { paletteIndex = *palettedSrc; // The 1st index is always transparent if (paletteIndex == 0) { - paletteIndex = -1; + paletteIndex = PALETTE_TRANSPARENT; } } else { - paletteIndex = GetPaletteIndex(palette, rgbaSrc); - - if (mode == IMPORT_MODE::CLOSEST || mode == IMPORT_MODE::DITHERING) - { - if (paletteIndex == -1 && !IsTransparentPixel(rgbaSrc)) - { - paletteIndex = GetClosestPaletteIndex(palette, rgbaSrc); - } - } - - if (mode == IMPORT_MODE::DITHERING) - { - if (!IsTransparentPixel(rgbaSrc) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc))) - { - sint16 dr = rgbaSrc[0] - (sint16)(palette[paletteIndex].Red); - sint16 dg = rgbaSrc[1] - (sint16)(palette[paletteIndex].Green); - sint16 db = rgbaSrc[2] - (sint16)(palette[paletteIndex].Blue); - - if (x + 1 < width) - { - if (!IsTransparentPixel(rgbaSrc + 4) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4))) - { - // Right - rgbaSrc[4] += dr * 7 / 16; - rgbaSrc[5] += dg * 7 / 16; - rgbaSrc[6] += db * 7 / 16; - } - } - - if (y + 1 < height) - { - if (x > 0) - { - if (!IsTransparentPixel(rgbaSrc + 4 * (width - 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width - 1)))) - { - // Bottom left - rgbaSrc[4 * (width - 1)] += dr * 3 / 16; - rgbaSrc[4 * (width - 1) + 1] += dg * 3 / 16; - rgbaSrc[4 * (width - 1) + 2] += db * 3 / 16; - } - } - - // Bottom - if (!IsTransparentPixel(rgbaSrc + 4 * width) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * width))) - { - rgbaSrc[4 * width] += dr * 5 / 16; - rgbaSrc[4 * width + 1] += dg * 5 / 16; - rgbaSrc[4 * width + 2] += db * 5 / 16; - } - - if (x + 1 < width) - { - if (!IsTransparentPixel(rgbaSrc + 4 * (width + 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width + 1)))) - { - // Bottom right - rgbaSrc[4 * (width + 1)] += dr * 1 / 16; - rgbaSrc[4 * (width + 1) + 1] += dg * 1 / 16; - rgbaSrc[4 * (width + 1) + 2] += db * 1 / 16; - } - } - } - } - } + paletteIndex = CalculatePaletteIndex(mode, rgbaSrc, x, y, width, height); } rgbaSrc += 4; palettedSrc += 1; - if (paletteIndex == -1) + if (paletteIndex == PALETTE_TRANSPARENT) { if (npixels != 0) { @@ -246,6 +190,73 @@ ImportResult ImageImporter::Import( return result; } +sint32 ImageImporter::CalculatePaletteIndex(IMPORT_MODE mode, sint16 * rgbaSrc, sint32 x, sint32 y, sint32 width, sint32 height) +{ + auto palette = StandardPalette; + auto paletteIndex = GetPaletteIndex(palette, rgbaSrc); + if (mode == IMPORT_MODE::CLOSEST || mode == IMPORT_MODE::DITHERING) + { + if (paletteIndex == PALETTE_TRANSPARENT && !IsTransparentPixel(rgbaSrc)) + { + paletteIndex = GetClosestPaletteIndex(palette, rgbaSrc); + } + } + if (mode == IMPORT_MODE::DITHERING) + { + if (!IsTransparentPixel(rgbaSrc) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc))) + { + auto dr = rgbaSrc[0] - (sint16)(palette[paletteIndex].Red); + auto dg = rgbaSrc[1] - (sint16)(palette[paletteIndex].Green); + auto db = rgbaSrc[2] - (sint16)(palette[paletteIndex].Blue); + + if (x + 1 < width) + { + if (!IsTransparentPixel(rgbaSrc + 4) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4))) + { + // Right + rgbaSrc[4] += dr * 7 / 16; + rgbaSrc[5] += dg * 7 / 16; + rgbaSrc[6] += db * 7 / 16; + } + } + + if (y + 1 < height) + { + if (x > 0) + { + if (!IsTransparentPixel(rgbaSrc + 4 * (width - 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width - 1)))) + { + // Bottom left + rgbaSrc[4 * (width - 1)] += dr * 3 / 16; + rgbaSrc[4 * (width - 1) + 1] += dg * 3 / 16; + rgbaSrc[4 * (width - 1) + 2] += db * 3 / 16; + } + } + + // Bottom + if (!IsTransparentPixel(rgbaSrc + 4 * width) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * width))) + { + rgbaSrc[4 * width] += dr * 5 / 16; + rgbaSrc[4 * width + 1] += dg * 5 / 16; + rgbaSrc[4 * width + 2] += db * 5 / 16; + } + + if (x + 1 < width) + { + if (!IsTransparentPixel(rgbaSrc + 4 * (width + 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width + 1)))) + { + // Bottom right + rgbaSrc[4 * (width + 1)] += dr * 1 / 16; + rgbaSrc[4 * (width + 1) + 1] += dg * 1 / 16; + rgbaSrc[4 * (width + 1) + 2] += db * 1 / 16; + } + } + } + } + } + return paletteIndex; +} + sint32 ImageImporter::GetPaletteIndex(const PaletteBGRA * palette, sint16 * colour) { if (!IsTransparentPixel(colour)) @@ -260,7 +271,7 @@ sint32 ImageImporter::GetPaletteIndex(const PaletteBGRA * palette, sint16 * colo } } } - return -1; + return PALETTE_TRANSPARENT; } bool ImageImporter::IsTransparentPixel(const sint16 * colour) @@ -273,7 +284,7 @@ bool ImageImporter::IsTransparentPixel(const sint16 * colour) */ bool ImageImporter::IsChangablePixel(sint32 paletteIndex) { - if (paletteIndex == -1) + if (paletteIndex == PALETTE_TRANSPARENT) return true; if (paletteIndex == 0) return false; @@ -290,23 +301,25 @@ bool ImageImporter::IsChangablePixel(sint32 paletteIndex) sint32 ImageImporter::GetClosestPaletteIndex(const PaletteBGRA * palette, const sint16 * colour) { - uint32 smallest_error = (uint32)-1; - sint32 best_match = -1; - - for (sint32 x = 0; x < 256; x++) { - if (IsChangablePixel(x)) { + auto smallestError = (uint32)-1; + auto bestMatch = PALETTE_TRANSPARENT; + for (sint32 x = 0; x < 256; x++) + { + if (IsChangablePixel(x)) + { uint32 error = ((sint16)(palette[x].Red) - colour[0]) * ((sint16)(palette[x].Red) - colour[0]) + ((sint16)(palette[x].Green) - colour[1]) * ((sint16)(palette[x].Green) - colour[1]) + ((sint16)(palette[x].Blue) - colour[2]) * ((sint16)(palette[x].Blue) - colour[2]); - if (smallest_error == (uint32)-1 || smallest_error > error) { - best_match = x; - smallest_error = error; + if (smallestError == (uint32)-1 || smallestError > error) + { + bestMatch = x; + smallestError = error; } } } - return best_match; + return bestMatch; } const PaletteBGRA ImageImporter::StandardPalette[256] = diff --git a/src/openrct2/drawing/ImageImporter.h b/src/openrct2/drawing/ImageImporter.h index 7d7886fb08..6d4ba9d50b 100644 --- a/src/openrct2/drawing/ImageImporter.h +++ b/src/openrct2/drawing/ImageImporter.h @@ -42,16 +42,24 @@ namespace OpenRCT2::Drawing DITHERING, }; + enum IMPORT_FLAGS + { + NONE = 0, + KEEP_PALETTE = 1 << 0, + RLE = 1 << 1, + }; + ImportResult Import( const Image& image, sint32 offsetX = 0, sint32 offsetY = 0, - bool keepPalette = false, + IMPORT_FLAGS flags = IMPORT_FLAGS::NONE, IMPORT_MODE mode = IMPORT_MODE::DEFAULT) const; private: static const PaletteBGRA StandardPalette[256]; + static sint32 CalculatePaletteIndex(IMPORT_MODE mode, sint16 * rgbaSrc, sint32 x, sint32 y, sint32 width, sint32 height); static sint32 GetPaletteIndex(const PaletteBGRA * palette, sint16 * colour); static bool IsTransparentPixel(const sint16 * colour); static bool IsChangablePixel(sint32 paletteIndex); From f212894eb04c82e3dbc0f12e516e12fb0ebe516d Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 10 May 2018 18:13:41 +0100 Subject: [PATCH 10/13] Fix clang builds --- src/openrct2/core/Imaging.cpp | 3 +-- src/openrct2/drawing/ImageImporter.cpp | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openrct2/core/Imaging.cpp b/src/openrct2/core/Imaging.cpp index 3ccfce78f6..37e5e25173 100644 --- a/src/openrct2/core/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -53,7 +53,6 @@ public: namespace Imaging { - constexpr auto EXCEPTION_IMAGE_FORMAT_UNSUPPORTED = "Unsupported image format."; constexpr auto EXCEPTION_IMAGE_FORMAT_UNKNOWN = "Unknown image format."; static std::unordered_map _readerImplementations; @@ -366,7 +365,7 @@ namespace Imaging break; } default: - throw std::runtime_error("Unknown image format."); + throw std::runtime_error(EXCEPTION_IMAGE_FORMAT_UNKNOWN); } } } diff --git a/src/openrct2/drawing/ImageImporter.cpp b/src/openrct2/drawing/ImageImporter.cpp index 668907eb86..07f5747a0c 100644 --- a/src/openrct2/drawing/ImageImporter.cpp +++ b/src/openrct2/drawing/ImageImporter.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "../core/Imaging.h" #include "ImageImporter.h" @@ -44,7 +45,7 @@ ImportResult ImageImporter::Import( if ((flags & IMPORT_FLAGS::KEEP_PALETTE) && image.Depth != 8) { - throw std::invalid_argument("Image is not palletted, it has bit depth of " + image.Depth); + throw std::invalid_argument("Image is not palletted, it has bit depth of " + std::to_string(image.Depth)); } if (!(flags & IMPORT_FLAGS::RLE)) From 44764874a3b7220bc490e638a6acaa57d4b4a87f Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 12 May 2018 00:04:16 +0100 Subject: [PATCH 11/13] Add test for ImageImporter --- test/tests/CMakeLists.txt | 6 ++++ test/tests/ImageImporterTests.cpp | 48 ++++++++++++++++++++++++++++ test/tests/testdata/images/logo.png | Bin 0 -> 4582 bytes test/tests/tests.vcxproj | 1 + 4 files changed, 55 insertions(+) create mode 100644 test/tests/ImageImporterTests.cpp create mode 100644 test/tests/testdata/images/logo.png diff --git a/test/tests/CMakeLists.txt b/test/tests/CMakeLists.txt index 50ef66c92b..2c23d182bf 100644 --- a/test/tests/CMakeLists.txt +++ b/test/tests/CMakeLists.txt @@ -147,6 +147,12 @@ add_executable(test_localisation ${STRING_TEST_SOURCES}) target_link_libraries(test_localisation ${GTEST_LIBRARIES} test-common ${LDL} z) add_test(NAME localisation COMMAND test_localisation) +# ImageImporter tests +add_executable(test_imageimporter "${CMAKE_CURRENT_LIST_DIR}/ImageImporterTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TestData.cpp") +target_link_libraries(test_imageimporter ${GTEST_LIBRARIES} libopenrct2) +add_test(NAME ImageImporter COMMAND test_imageimporter) + # Ride ratings test set(RIDE_RATINGS_TEST_SOURCES "${CMAKE_CURRENT_LIST_DIR}/RideRatings.cpp" "${CMAKE_CURRENT_LIST_DIR}/TestData.cpp") diff --git a/test/tests/ImageImporterTests.cpp b/test/tests/ImageImporterTests.cpp new file mode 100644 index 0000000000..9e5f2bd71a --- /dev/null +++ b/test/tests/ImageImporterTests.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include "TestData.h" + +using namespace OpenRCT2::Drawing; + +class ImageImporterTests : public testing::Test +{ +public: + static std::string GetImagePath(const std::string_view& name) + { + return Path::Combine(TestData::GetBasePath(), "images", name.data()); + } + + static uint32 GetHash(void * buffer, size_t bufferLength) + { + uint32 hash = 27; + for (size_t i = 0; i < bufferLength; i++) + { + hash = (13 * hash) + ((uint8 *)buffer)[i]; + } + return hash; + } +}; + +TEST_F(ImageImporterTests, Import_Logo) +{ + auto logoPath = GetImagePath("logo.png"); + + ImageImporter importer; + auto image = Imaging::ReadFromFile(logoPath, IMAGE_FORMAT::PNG_32); + auto result = importer.Import(image, 3, 5, ImageImporter::IMPORT_FLAGS::RLE); + + ASSERT_EQ(result.Buffer, result.Element.offset); + ASSERT_EQ(128, result.Element.width); + ASSERT_EQ(128, result.Element.height); + ASSERT_EQ(3, result.Element.x_offset); + ASSERT_EQ(5, result.Element.y_offset); + ASSERT_EQ(0, result.Element.zoomed_offset); + + // Check to ensure RLE data doesn't change unexpectedly. + // Update expected hash if change is expected. + ASSERT_NE(nullptr, result.Buffer); + auto hash = GetHash(result.Buffer, result.BufferLength); + ASSERT_EQ(0xCEF27C7D, hash); +} diff --git a/test/tests/testdata/images/logo.png b/test/tests/testdata/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f99b17660d0c4cde90a5b3cd5e5d349d1512628b GIT binary patch literal 4582 zcmeHK{X5h9|9`#TduN-;W~`w50C5de7v65>-DFnhs#23 zBW(b{LO0hfUh`G-pTJ?}^SI)iYQ7+0YhU`YW?Fz?3prdT=+Fd4v{PPer7quYnkT+A8X)--2ldk%-`?ag$K*W~Mf`2uvOwgxDK%IfQ@!ik}OO6JpQf=nMT!BfROc zF4hqdrm+H=b3gzmB*Z%+B7n~qgmFRy0%PwM>#(i}>ns7Y*xNfTBqS>~wuG0;A(m1;z0w5~@URkLL^+q+ z1V9fKpjgns1*kVbdHPLZVeLX8%5_Es{M0mtERC2d4a&^oN-KHgNzSrNzx?yUf|8)h zj6{IBosvL`zq6%j&knaOY@fKjZ?4P+1;j3U9Q)+w%S|oNjjOxFk+=5j>&zxUCckpJYGdp5 zVZB}CH`2YbV7RFG@w)z>12|DhlctZAd{xe@+jGYnb)BV6l2hxLX0uc|`^@dF(!--5 zyW*xaw5=aHG`BimGXkw1Je82KvjDkcGdTIyw}(ErF3AHMOaQnm>6(2Tu4>Eg(fr>& z_6-><*D`5L&*`=U_ckdkg4J7RFm5s0z}U#(?m-X?Go8Io4uQLx#BM6NkV8;wP(FCq zfuV!K!}U4zD82EpzpSjm7oL9F1pu0b9QDNPpBMF5Kf-_o3wP!gW2QFdsg`^$OHb61 zl@$=n<92h$v-&UKMHyU`^Xs=wj1_AAGtJx57l>-wOz}hA=AT3E4GiRa~lwLb`?~48>nRnMsAERf_U)mFi9Xg(? zULDx6HC;|M*CmWE>i=w{)FQ8m1$c1nQ&pr^2BFa zdUgy6Z+Me>aykNbwNJSQV%(nzAHW&Y~t7L)pPeQUZ5I+lAtLxR96vMo6uk($4R_yL|cmrmuzYHfn{Yc`1L>z$5b z(rz>j)YsP!WW;+&wpEB-mW>6Y*N|H9uX@+(%r!P|w{X6wRKJfcaC^8HElgQr3s2;R zQ3Rk*-JiFrX7c=0hHFI{d?&G~FZ^&wbo9Q@lQYND{Aj<5O$=kO3YDNUuRekqI%@>o zTph_Wr#%e+l5oGOY(`UnY6b%PbFJ7~f%8~;N0u*2P(LMZv-9%1E=7nljot8p(99*iFab|10-rwe>GvkLiZw3k^8EGbh!ZvPK8(~`! z0X(9q1nRZo?8Bd}EI=067PLq>d#GY&Yk}~wk|xOOSkw)LtL~!e#lgV1jv%l2Cd3-(DHJeK%}`72>%gni=&=$O4Gk7vz?@XNJK9m$l^47|*s^Y&3{ZS~%9M z7e@(qz?0-`tHfFU6r0qQWB?v0#KB zzxX7G)5itFKdSWYQ*dCyzC~h?Scw_N4Mpt4M}cg%9ovBQ)l{El1${aJlQv_sK{wtB z#Q$_uiNvq^Y&Bdi#EGuI{apd*l?duDK7HCd#Axv@?+M}QK`UPTR^*sG zmr9I8&KXWaee33AM`2uv%zssH%lLksi!EQ&y={l8|2XMV{qag*gCpsps2B&Ef?GkY zLD59=1zWgg%|4s8az@KH=Ov_Ep2e5lxSQKe z{UBT{_(8lWT{Mu06}|1^YP8I5{zYKndt1(Xw+wiY;Z!rSnZp8ok&^cH?qn3Wz$$X+ zS-^rBsjY2$LrH%RnJT_JLRE+^c+b~dn|4=lM2yj)(f4#F2Fw1GOnQZ3CPL|&B7qv5 zy59ez1|!GIO>`bEy#x;XaQs|>EFRHT^p33A+?>lh`Kp*dS@Oj&_%gUumkKIKV(I6^ z_)$F^n37D%oJmWto<^$%i_P4FeIfry-7Qg)z}n+1h-Ct6%`XO3x$qJjbLl5*`CqRg z>7qn6u_I6W2lm$}JWgJnU{!zh5q?qMH|VY4FL~q8p_aL>;nH8`_-E#)Bw@ml`sbxL6L%-E&E&O54S?v~UXo57-_l1Y@Kl>&&8*6387H2t zEDXGcB=GAJ#@^`fU2-ye{mXTNvX2MzQuamzvD`=>_K>fzU3i+1dr9fe;-;oqTh!Ry zeiIqEY?^}&i`BGC5mW#>&NYo4vRmU2!sO%V6cfC*ei z)T_d)H@0SpaLrP%1IcQSa=Gh!c)Rs1b2vjZSORKsjmw^9{zgehPpnj&4o-t6b+1$& zxvxyLee+=_KVxus%pPo~YS{e)-wBn@+M{kZw(tUfhQn~*^QHd2yvje|&l9oW7coh! zBT{o-53f{I{$$-gurl!g2E3`L6`I6GM#Bb4AIjU7cF$#c2k0x3gM!zxJ!=wjz$yhC z1wTiO+_n5Wn49;eI+_<&UgCpv-rQcWpX3&7X)JfANe78vsT|i?FybQ~=LB`Xnu%)fVnO@$XUO5Ma9h2s zxccNr^O+`X2%%doo(Rs@o(5vP9bOa=H|~1NEO65;(@Bl$d_C9_e4I7tGP|n^Lk6T5 zA6Zlg!&>1PORi3xlI=v~c(qN$p2tC}^MuQJ*kynRw|`vn&;8wh{Hu1`3C2s1!x1|u z2Hxn>Rq3+uZMLQZta7&H&Z>Di{i?a<3Uc6e3SEyWYp~4O1IEdv02k>Bk0(#lB#u(% zxiQPF7zcMm-uO-gT(K#+ClCji2gj2X_-J&x&>WET8ov@Bfn2OX19e^xAt51m_4Xcv zdJS;L0G3^tvQ&*lxi)T@-`BWBD4W;)M;QQvR*vp2DW@QRM0E_Lnz2Oti(IIT`6ZIQC;jSMR? zBLNd=76VF9BGGjCsI8#1iHBj?#3LxaOfq6xj1_T6J49X5-pn4ZgyH^AFlB1Xh9uz; z{IvSp=Pn8hcZepkgtSO{3p|#4u(pd-QCj9cByp`&(=)oWsNz{!?=6RxI zl_kbgEdTjr8o2WJjO7n5j*2MV@ef?0rVsri(asfCq>|xFD|SjLIc(=!v-n@zW4bCChvz+WD~NMF6Gy=eBQu~aV@SGT(YEAO+Jl-5KItF!+2 zP08I(0Px0V*?GdMZ4Z2^k4`K}#y$T&{d%TgUK1z!ZY+wuUOX-_9*iCN6o`&)JS=O| z7y^&xrVZAykJ`T^kSas+FTa)I=a~}if=TF(!B(xP`UB39@cVYDo(P|w2;0TY{c%}M zhe_<`M(fO*A8P&uosFG$ELfV$U%5}UshbQ|g6RULaA~NP;-}tE;da}mDRT|uPj#t= zr%%6_E}xZZDU!2`P8WP + From 2e91fc2c920110e7eca31a7f665c81d409123339 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 12 May 2018 18:39:55 +0200 Subject: [PATCH 12/13] Update Xcode project for new image classes. --- OpenRCT2.xcodeproj/project.pbxproj | 36 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 499268585b..03d5e79f5a 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -46,6 +46,15 @@ 933F2CB820935653001B33FD /* LocalisationService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933F2CB620935653001B33FD /* LocalisationService.cpp */; }; 933F2CB920935653001B33FD /* LocalisationService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933F2CB620935653001B33FD /* LocalisationService.cpp */; }; 933F2CBB20935668001B33FD /* LocalisationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 933F2CBA20935668001B33FD /* LocalisationService.h */; }; + 93CBA4C020A74FF200867D56 /* BitmapReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4BF20A74FF200867D56 /* BitmapReader.cpp */; }; + 93CBA4C320A7502E00867D56 /* Imaging.h in Headers */ = {isa = PBXBuildFile; fileRef = 93CBA4C120A7502D00867D56 /* Imaging.h */; }; + 93CBA4C420A7502E00867D56 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C220A7502E00867D56 /* Imaging.cpp */; }; + 93CBA4C520A7502E00867D56 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C220A7502E00867D56 /* Imaging.cpp */; }; + 93CBA4C620A7502E00867D56 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C220A7502E00867D56 /* Imaging.cpp */; }; + 93CBA4C920A7504500867D56 /* ImageImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C720A7504400867D56 /* ImageImporter.cpp */; }; + 93CBA4CA20A7504500867D56 /* ImageImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C720A7504400867D56 /* ImageImporter.cpp */; }; + 93CBA4CB20A7504500867D56 /* ImageImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C720A7504400867D56 /* ImageImporter.cpp */; }; + 93CBA4CC20A7504500867D56 /* ImageImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 93CBA4C820A7504500867D56 /* ImageImporter.h */; }; C61ADB1F1FB6A0A70024F2EF /* TopToolbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB1E1FB6A0A60024F2EF /* TopToolbar.cpp */; }; C61ADB211FB7DC060024F2EF /* Scenery.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB201FB7DC060024F2EF /* Scenery.cpp */; }; C61ADB231FBBCB8B0024F2EF /* GameBottomToolbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB221FBBCB8A0024F2EF /* GameBottomToolbar.cpp */; }; @@ -404,7 +413,6 @@ F76C86031EC4E88300FA49E2 /* Sprite.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83AF1EC4E7CC00FA49E2 /* Sprite.cpp */; }; F76C86051EC4E88300FA49E2 /* Editor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83B11EC4E7CC00FA49E2 /* Editor.cpp */; }; F76C86071EC4E88300FA49E2 /* FileClassifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83B31EC4E7CC00FA49E2 /* FileClassifier.cpp */; }; - F76C860B1EC4E88300FA49E2 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83B71EC4E7CC00FA49E2 /* Imaging.cpp */; }; F76C86451EC4E88300FA49E2 /* Http.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83F61EC4E7CC00FA49E2 /* Http.cpp */; }; F76C86471EC4E88300FA49E2 /* Network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83F81EC4E7CC00FA49E2 /* Network.cpp */; }; F76C86491EC4E88300FA49E2 /* NetworkAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83FA1EC4E7CC00FA49E2 /* NetworkAction.cpp */; }; @@ -869,6 +877,12 @@ 9346F9D7208A191900C77D91 /* GuestPathfinding.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GuestPathfinding.cpp; sourceTree = ""; }; 933F2CB620935653001B33FD /* LocalisationService.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LocalisationService.cpp; sourceTree = ""; }; 933F2CBA20935668001B33FD /* LocalisationService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalisationService.h; sourceTree = ""; }; + 93CBA4BE20A74FF200867D56 /* BitmapReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitmapReader.h; sourceTree = ""; }; + 93CBA4BF20A74FF200867D56 /* BitmapReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BitmapReader.cpp; sourceTree = ""; }; + 93CBA4C120A7502D00867D56 /* Imaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Imaging.h; sourceTree = ""; }; + 93CBA4C220A7502E00867D56 /* Imaging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Imaging.cpp; sourceTree = ""; }; + 93CBA4C720A7504400867D56 /* ImageImporter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageImporter.cpp; sourceTree = ""; }; + 93CBA4C820A7504500867D56 /* ImageImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageImporter.h; sourceTree = ""; }; C61ADB1E1FB6A0A60024F2EF /* TopToolbar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TopToolbar.cpp; sourceTree = ""; }; C61ADB201FB7DC060024F2EF /* Scenery.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Scenery.cpp; sourceTree = ""; }; C61ADB221FBBCB8A0024F2EF /* GameBottomToolbar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameBottomToolbar.cpp; sourceTree = ""; }; @@ -1223,8 +1237,6 @@ F76C83B21EC4E7CC00FA49E2 /* Editor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Editor.h; sourceTree = ""; }; F76C83B31EC4E7CC00FA49E2 /* FileClassifier.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FileClassifier.cpp; sourceTree = ""; }; F76C83B41EC4E7CC00FA49E2 /* FileClassifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileClassifier.h; sourceTree = ""; }; - F76C83B71EC4E7CC00FA49E2 /* Imaging.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Imaging.cpp; sourceTree = ""; }; - F76C83B81EC4E7CC00FA49E2 /* Imaging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Imaging.h; sourceTree = ""; }; F76C83BA1EC4E7CC00FA49E2 /* input.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = input.h; sourceTree = ""; }; F76C83F61EC4E7CC00FA49E2 /* Http.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Http.cpp; sourceTree = ""; }; F76C83F71EC4E7CC00FA49E2 /* http.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = http.h; sourceTree = ""; }; @@ -1819,8 +1831,6 @@ 4CE4623F1FD0710E0001CD98 /* Game.cpp */, 4CE462401FD0710E0001CD98 /* Game.h */, 4C80798D2062AFE400C5850B /* HandleParkLoad.h */, - F76C83B71EC4E7CC00FA49E2 /* Imaging.cpp */, - F76C83B81EC4E7CC00FA49E2 /* Imaging.h */, 4CC4B8E81FE00C5D00660D62 /* Input.cpp */, 4CC4B8E91FE00C5D00660D62 /* Input.h */, 4CC4B8EA1FE00C5D00660D62 /* Intro.cpp */, @@ -1890,6 +1900,8 @@ children = ( F76C83791EC4E7CC00FA49E2 /* Collections.hpp */, F76C837A1EC4E7CC00FA49E2 /* Console.cpp */, + 93CBA4C220A7502E00867D56 /* Imaging.cpp */, + 93CBA4C120A7502D00867D56 /* Imaging.h */, F76C837B1EC4E7CC00FA49E2 /* Console.hpp */, C6352B811F477022006CCEE3 /* DataSerialiser.h */, C6352B821F477022006CCEE3 /* DataSerialiserTraits.h */, @@ -1936,6 +1948,8 @@ 4C7B53D620002CA400A52E21 /* Font.cpp */, 4C7B53CB1FFF995100A52E21 /* Font.h */, F76C83A31EC4E7CC00FA49E2 /* IDrawingContext.h */, + 93CBA4C720A7504400867D56 /* ImageImporter.cpp */, + 93CBA4C820A7504500867D56 /* ImageImporter.h */, F76C83A41EC4E7CC00FA49E2 /* IDrawingEngine.h */, F76C83A51EC4E7CC00FA49E2 /* Image.cpp */, 4C7B53D720002CA400A52E21 /* LightFX.cpp */, @@ -2567,6 +2581,8 @@ F76C858D1EC4E82600FA49E2 /* drawing */ = { isa = PBXGroup; children = ( + 93CBA4BF20A74FF200867D56 /* BitmapReader.cpp */, + 93CBA4BE20A74FF200867D56 /* BitmapReader.h */, F76C858E1EC4E82600FA49E2 /* engines */, ); path = drawing; @@ -2724,9 +2740,11 @@ C6352B971F477032006CCEE3 /* SetParkEntranceFeeAction.hpp in Headers */, 933F2CBB20935668001B33FD /* LocalisationService.h in Headers */, C6352B861F477022006CCEE3 /* Endianness.h in Headers */, + 93CBA4CC20A7504500867D56 /* ImageImporter.h in Headers */, C6352B941F477032006CCEE3 /* PlaceParkEntranceAction.hpp in Headers */, C6352B911F477032006CCEE3 /* GameAction.h in Headers */, C62D838B1FD36D6F008C04F1 /* EditorObjectSelectionSession.h in Headers */, + 93CBA4C320A7502E00867D56 /* Imaging.h in Headers */, 9308DA05209908090079EE96 /* Surface.h in Headers */, C6352B841F477022006CCEE3 /* DataSerialiser.h in Headers */, C67B28162002D67A00109C93 /* Window.h in Headers */, @@ -3059,12 +3077,15 @@ C654DF3D1F69C0430040F43D /* TrackDesignPlace.cpp in Sources */, C666EE721F37ACB10061AA04 /* Multiplayer.cpp in Sources */, C654DF371F69C0430040F43D /* Sign.cpp in Sources */, + 93CBA4C920A7504500867D56 /* ImageImporter.cpp in Sources */, + 93CBA4C020A74FF200867D56 /* BitmapReader.cpp in Sources */, C68878CC20289B710084B384 /* SoftwareDrawingEngine.cpp in Sources */, C67CCD681FBBD138004FAE4C /* EditorMain.cpp in Sources */, C6E415511FAFD6DC00D4A52A /* RideConstruction.cpp in Sources */, C685E51B1F8907850090598F /* Guest.cpp in Sources */, C64644F91F3FA4120026AC2D /* EditorInventionsList.cpp in Sources */, C68878C720289B710084B384 /* OpenGLShaderProgram.cpp in Sources */, + 93CBA4C420A7502E00867D56 /* Imaging.cpp in Sources */, C6D2BEE61F9BAACE008B557C /* TrackList.cpp in Sources */, C666EE701F37ACB10061AA04 /* LandRights.cpp in Sources */, C666EE781F37ACB10061AA04 /* ServerList.cpp in Sources */, @@ -3168,6 +3189,7 @@ C68878CE20289B9B0084B384 /* ObjectList.cpp in Sources */, C688787620289A780084B384 /* RideGroupManager.cpp in Sources */, C688788120289ADE0084B384 /* Line.cpp in Sources */, + 93CBA4CA20A7504500867D56 /* ImageImporter.cpp in Sources */, C688792520289B9B0084B384 /* RotoDrop.cpp in Sources */, F76C85BA1EC4E88300FA49E2 /* CommandLine.cpp in Sources */, C68878EE20289B9B0084B384 /* BolligerMabillardTrack.cpp in Sources */, @@ -3247,7 +3269,6 @@ C68878E320289B9B0084B384 /* Android.cpp in Sources */, F76C86051EC4E88300FA49E2 /* Editor.cpp in Sources */, F76C86071EC4E88300FA49E2 /* FileClassifier.cpp in Sources */, - F76C860B1EC4E88300FA49E2 /* Imaging.cpp in Sources */, C688786920289A660084B384 /* CableLift.cpp in Sources */, C688790020289B9B0084B384 /* ReverseFreefallCoaster.cpp in Sources */, C6607F481FE2B97E00D3FC0D /* Input.cpp in Sources */, @@ -3354,6 +3375,7 @@ C688791220289B9B0084B384 /* GhostTrain.cpp in Sources */, C688787F20289ADE0084B384 /* Font.cpp in Sources */, C68878CF20289B9B0084B384 /* Litter.cpp in Sources */, + 93CBA4C520A7502E00867D56 /* Imaging.cpp in Sources */, F76C86AF1EC4E88400FA49E2 /* S4Importer.cpp in Sources */, F76C86B01EC4E88400FA49E2 /* Tables.cpp in Sources */, C688788520289ADE0084B384 /* Text.cpp in Sources */, @@ -3441,9 +3463,11 @@ buildActionMask = 2147483647; files = ( 9308DA00209908090079EE96 /* TileElement.cpp in Sources */, + 93CBA4CB20A7504500867D56 /* ImageImporter.cpp in Sources */, 9346F9DD208A191900C77D91 /* GuestPathfinding.cpp in Sources */, 9346F9DA208A191900C77D91 /* Guest.cpp in Sources */, F7D7749E1EC6713200BE6EBC /* Cli.cpp in Sources */, + 93CBA4C620A7502E00867D56 /* Imaging.cpp in Sources */, 9308DA03209908090079EE96 /* Surface.cpp in Sources */, 933F2CB920935653001B33FD /* LocalisationService.cpp in Sources */, ); From 15c13137dfd22de7e5147186dfa040cc63106992 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 12 May 2018 23:10:39 +0200 Subject: [PATCH 13/13] Add missing include. --- src/openrct2/core/Imaging.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/openrct2/core/Imaging.cpp b/src/openrct2/core/Imaging.cpp index 37e5e25173..e933ae0b27 100644 --- a/src/openrct2/core/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "FileStream.hpp" #include "Guard.hpp"