From 8f30ed910f1e0831c96e4edf6cd02132a46d8121 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 23 Feb 2022 02:02:07 +0000 Subject: [PATCH 01/10] Start working on custom image APIs --- distribution/openrct2.d.ts | 102 +++++++++++++ src/openrct2-ui/scripting/ScImageManager.hpp | 147 +++++++++++++++++++ src/openrct2/scripting/ScriptEngine.cpp | 5 + 3 files changed, 254 insertions(+) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 583e90b330..45b3e09212 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2794,6 +2794,108 @@ declare global { * Useful for displaying how fragmented the allocated image list is. */ getAvailableAllocationRanges(): ImageIndexRange[]; + + /** + * Allocates one or more contigous image IDs. + * @param count The number of image IDs to allocate. + * @returns the range of allocated image IDs or null if the range could not be allocated. + */ + allocate(count: number): ImageIndexRange | null; + + /** + * Frees one or more contigous image IDs. + * An error will occur if attempting the given range contains an ID not owned by the plugin. + * @param range The range of images to free. + */ + free(range: ImageIndexRange): void; + + /** + * Sets the pixel data for a given image ID. + * + * Will error if given an ID of an image not owned by this plugin. + * @param id The id of the image to set the pixels of. + * @param data The pixel data. + */ + setPixelData(id: number, data: PixelData): void; + + /** + * Gets a {@link GraphicsContext} for the given image so that you can draw directly to it. + * Allocates or reallocates the image if not previously allocated or if the size is changed. + * The pixels of the image will persist between calls, so you can draw over the top of what + * is currently there. The default pixel colour will be 0 (transparent). + * + * Drawing a large number of pixels each frame can be expensive, so caching as many as you + * can in images is a good way to improve performance. + * + * Will error if given an ID of an image not owned by this plugin. + * @param id The id of the image to get a {@link GraphicsContext} for. + * @param size The size the image that should be allocated. + */ + getGraphicsContext(id: number, size: ScreenSize): GraphicsContext; + } + + type PixelData = RawPixelData | RlePixelData | PngPixelData; + + /** + * Raw pixel data that is not encoded. A contiguous sequence of bytes + * representing the 8bpp pixel values with a optional padding between + * each horizontal row. + */ + interface RawPixelData { + type: 'raw'; + width: number; + height: number; + + /** + * The length of each horizontal row in bytes. + */ + stride?: number; + + /** + * Data can either by a: + * - A base64 string. + * - An array of bytes + * - A {@link Uint8Array} of bytes + */ + data: string | number | Uint8Array; + } + + /** + * Pixel data that is encoded as RCT run-length encoded data. + */ + interface RlePixelData { + type: 'rle'; + width: number; + height: number; + + /** + * Data can either by a: + * - A base64 string. + * - An array of bytes + * - A {@link Uint8Array} of bytes + */ + data: string | number | Uint8Array; + } + + /** + * Pixel data that is encoded as a .png file. + */ + interface PngPixelData { + type: 'png'; + + /** + * If keep is specified for palette, the raw 8bpp .png bytes will be loaded. The palette + * in the .png will not be read. This will improve load performance. + */ + palette?: 'keep'; + + /** + * Data can either by a: + * - A base64 string. + * - An array of bytes + * - A {@link Uint8Array} of bytes + */ + data: string | number | Uint8Array; } interface ImageIndexRange { diff --git a/src/openrct2-ui/scripting/ScImageManager.hpp b/src/openrct2-ui/scripting/ScImageManager.hpp index cb456e4315..983887034b 100644 --- a/src/openrct2-ui/scripting/ScImageManager.hpp +++ b/src/openrct2-ui/scripting/ScImageManager.hpp @@ -32,6 +32,9 @@ namespace OpenRCT2::Scripting { dukglue_register_method(ctx, &ScImageManager::getPredefinedRange, "getPredefinedRange"); dukglue_register_method(ctx, &ScImageManager::getAvailableAllocationRanges, "getAvailableAllocationRanges"); + dukglue_register_method(ctx, &ScImageManager::allocate, "allocate"); + dukglue_register_method(ctx, &ScImageManager::free, "free"); + dukglue_register_method(ctx, &ScImageManager::setPixelData, "setPixelData"); } private: @@ -74,6 +77,150 @@ namespace OpenRCT2::Scripting return DukValue::take_from_stack(_ctx); } + DukValue allocate(int32_t count) + { + std::vector images; + images.resize(count); + + auto base = gfx_object_allocate_images(images.data(), count); + if (base == ImageIndexUndefined) + { + return ToDuk(_ctx, nullptr); + } + return CreateImageIndexRange(base, count); + } + + void free(const DukValue& range) + { + auto start = range["start"].as_int(); + auto count = range["count"].as_int(); + gfx_object_free_images(start, count); + } + + static void setPixelDataFromBuffer( + uint8_t* dst, const uint8_t* src, size_t srcLen, int32_t width, int32_t height, int32_t stride) + { + auto srcEnd = src + srcLen; + auto dstLen = static_cast(width) * height; + if (stride == width) + { + std::memcpy(dst, src, std::min(srcLen, dstLen)); + if (dstLen > srcLen) + { + std::memset(dst + srcLen, 0, dstLen - srcLen); + } + } + else + { + std::memset(dst, 0, dstLen); + auto srcLine = src; + for (int32_t y = 0; y < height; y++) + { + auto dstI = y * width; + auto lineWidth = std::min(srcEnd - srcLine, width); + std::memcpy(dst + dstI, srcLine, lineWidth); + if (lineWidth < width) + { + break; + } + srcLine += stride; + } + } + } + + void setPixelData(int32_t id, const DukValue& pixelData) + { + auto dukType = pixelData["type"]; + auto& type = dukType.as_string(); + if (type == "raw") + { + auto width = pixelData["width"].as_int(); + auto height = pixelData["height"].as_int(); + auto stride = AsOrDefault(pixelData["stride"], width); + auto data = pixelData["data"]; + auto padding = stride - width; + + auto imageData = new (std::nothrow) uint8_t[width * height]; + if (imageData == nullptr) + { + duk_pop(_ctx); + duk_error(_ctx, DUK_ERR_ERROR, "Unable to allocate memory for pixel data."); + } + else + { + // Setup the g1 element + rct_g1_element el{}; + auto* lastel = gfx_get_g1_element(id); + if (lastel != nullptr) + { + el = *lastel; + delete el.offset; + } + + el.offset = imageData; + el.width = width; + el.height = height; + gfx_set_g1_element(id, &el); + + // Set the pixel data + if (data.is_array()) + { + // From array of numbers + data.push(); + duk_uarridx_t i = 0; + for (int32_t y = 0; y < height; y++) + { + for (int32_t x = 0; x < width; x++) + { + auto dstI = y * width + x; + if (duk_get_prop_index(_ctx, -1, i)) + { + imageData[dstI] = duk_get_int(_ctx, -1) & 0xFF; + duk_pop(_ctx); + } + else + { + imageData[dstI] = 0; + } + i++; + } + i += padding; + } + duk_pop(_ctx); + } + else if (data.type() == DukValue::Type::STRING) + { + // From base64 string + data.push(); + duk_base64_decode(_ctx, -1); + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(_ctx, -1, &bufferLen)); + if (buffer != nullptr) + { + setPixelDataFromBuffer(imageData, buffer, bufferLen, width, height, stride); + } + duk_pop(_ctx); + } + else if (data.type() == DukValue::Type::OBJECT) + { + // From Uint8Array + data.push(); + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(_ctx, -1, &bufferLen)); + if (buffer != nullptr) + { + setPixelDataFromBuffer(imageData, buffer, bufferLen, width, height, stride); + } + duk_pop(_ctx); + } + } + } + else + { + duk_error(_ctx, DUK_ERR_ERROR, "Unsupported pixel data type."); + } + } + DukValue CreateImageIndexRange(size_t start, size_t count) const { DukObject obj(_ctx); diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 7d91c10924..fdc716dbd1 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -590,6 +590,11 @@ void ScriptEngine::StopUnloadRegisterAllPlugins() void ScriptEngine::LoadTransientPlugins() { + if (!_initialised) + { + Initialise(); + RefreshPlugins(); + } _transientPluginsEnabled = true; } From e74396b4646616376c30b67156840e041c288d09 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Feb 2022 00:42:54 +0000 Subject: [PATCH 02/10] Refactor and implement RLE custom images --- distribution/openrct2.d.ts | 10 + src/openrct2-ui/libopenrct2ui.vcxproj | 2 + src/openrct2-ui/scripting/CustomImages.cpp | 331 ++++++++++++++++++ src/openrct2-ui/scripting/CustomImages.h | 25 ++ .../scripting/ScGraphicsContext.hpp | 29 +- src/openrct2-ui/scripting/ScImageManager.hpp | 129 +------ 6 files changed, 383 insertions(+), 143 deletions(-) create mode 100644 src/openrct2-ui/scripting/CustomImages.cpp create mode 100644 src/openrct2-ui/scripting/CustomImages.h diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 45b3e09212..49e0699a97 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2809,6 +2809,16 @@ declare global { */ free(range: ImageIndexRange): void; + /** + * Gets the metadata for a given image. + */ + getImageInfo(id: number): ImageInfo | undefined; + + /** + * Gets the pixel data for a given image ID. + */ + getPixelData(id: number): PixelData | undefined; + /** * Sets the pixel data for a given image ID. * diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj index 635125faaf..0aab9e0b2d 100644 --- a/src/openrct2-ui/libopenrct2ui.vcxproj +++ b/src/openrct2-ui/libopenrct2ui.vcxproj @@ -51,6 +51,7 @@ + @@ -105,6 +106,7 @@ + diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp new file mode 100644 index 0000000000..a283c7e14a --- /dev/null +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -0,0 +1,331 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#ifdef ENABLE_SCRIPTING + +# include "CustomImages.h" + +namespace OpenRCT2::Scripting +{ + enum class PixelDataKind + { + Unknown, + Raw, + Rle, + Palette, + Png + }; + + struct PixelData + { + PixelDataKind Type; + int32_t Width; + int32_t Height; + int32_t Stride; + DukValue Data; + }; + + DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id) + { + auto* g1 = gfx_get_g1_element(id); + if (g1 == nullptr) + { + return ToDuk(ctx, undefined); + } + + DukObject obj(ctx); + obj.Set("id", id); + obj.Set("offset", ToDuk(ctx, { g1->x_offset, g1->y_offset })); + obj.Set("width", g1->width); + obj.Set("height", g1->height); + + obj.Set("isBMP", (g1->flags & G1_FLAG_BMP) != 0); + obj.Set("isRLE", (g1->flags & G1_FLAG_RLE_COMPRESSION) != 0); + obj.Set("isPalette", (g1->flags & G1_FLAG_PALETTE) != 0); + obj.Set("noZoom", (g1->flags & G1_FLAG_NO_ZOOM_DRAW) != 0); + + if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE) + { + obj.Set("nextZoomId", id - g1->zoomed_offset); + } + else + { + obj.Set("nextZoomId", undefined); + } + return obj.Take(); + } + + static const char* GetPixelDataTypeForG1(const rct_g1_element& g1) + { + if (g1.flags & G1_FLAG_RLE_COMPRESSION) + return "rle"; + else if (g1.flags & G1_FLAG_PALETTE) + return "palette"; + return "raw"; + } + + DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id) + { + auto* g1 = gfx_get_g1_element(id); + if (g1 == nullptr) + { + return ToDuk(ctx, undefined); + } + auto dataSize = g1_calculate_data_size(g1); + auto* type = GetPixelDataTypeForG1(*g1); + + // Copy the G1 data to a JS buffer wrapped in a Uint8Array + duk_push_fixed_buffer(ctx, dataSize); + duk_size_t bufferSize{}; + auto* buffer = duk_get_buffer_data(ctx, -1, &bufferSize); + if (buffer != nullptr && bufferSize == dataSize) + { + std::memcpy(buffer, g1->offset, dataSize); + } + duk_push_buffer_object(ctx, -1, 0, dataSize, DUK_BUFOBJ_UINT8ARRAY); + duk_remove(ctx, -2); + auto data = DukValue::take_from_stack(ctx, -1); + + DukObject obj(ctx); + obj.Set("type", type); + obj.Set("width", g1->width); + obj.Set("height", g1->height); + obj.Set("data", data); + return obj.Take(); + } + + static void SetPixelDataFromBuffer( + uint8_t* dst, const uint8_t* src, size_t srcLen, int32_t width, int32_t height, int32_t stride) + { + auto srcEnd = src + srcLen; + auto dstLen = static_cast(width) * height; + if (stride == width) + { + std::memcpy(dst, src, std::min(srcLen, dstLen)); + if (dstLen > srcLen) + { + std::memset(dst + srcLen, 0, dstLen - srcLen); + } + } + else + { + std::memset(dst, 0, dstLen); + auto srcLine = src; + for (int32_t y = 0; y < height; y++) + { + auto dstI = y * width; + auto lineWidth = std::min(srcEnd - srcLine, width); + std::memcpy(dst + dstI, srcLine, lineWidth); + if (lineWidth < width) + { + break; + } + srcLine += stride; + } + } + } + + static std::vector DukGetDataFromBufferLikeObject(const DukValue& data) + { + std::vector result; + auto ctx = data.context(); + if (data.is_array()) + { + // From array of numbers + data.push(); + auto len = duk_get_length(ctx, -1); + result.resize(len); + for (duk_uarridx_t i = 0; i < len; i++) + { + if (duk_get_prop_index(ctx, -1, i)) + { + result[i] = duk_get_int(ctx, -1) & 0xFF; + duk_pop(ctx); + } + } + duk_pop(ctx); + } + else if (data.type() == DukValue::Type::STRING) + { + // From base64 string + data.push(); + duk_base64_decode(ctx, -1); + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); + if (buffer != nullptr) + { + result = std::vector(buffer, buffer + bufferLen); + } + duk_pop(ctx); + } + else if (data.type() == DukValue::Type::OBJECT) + { + // From Uint8Array + data.push(); + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); + if (buffer != nullptr) + { + result = std::vector(buffer, buffer + bufferLen); + } + duk_pop(ctx); + } + return result; + } + + static uint8_t* GetBufferFromPixelData(duk_context* ctx, const PixelData& pixelData) + { + auto padding = pixelData.Stride - pixelData.Width; + auto imageData = new (std::nothrow) uint8_t[pixelData.Width * pixelData.Height]; + if (imageData == nullptr) + { + throw std::runtime_error("Unable to allocate memory for pixel data."); + } + + // Ensure image data is auto freed if exception occurs + std::unique_ptr uniqueImageData(imageData); + switch (pixelData.Type) + { + case PixelDataKind::Raw: + { + // Set the pixel data + auto& data = pixelData.Data; + if (data.is_array()) + { + // From array of numbers + data.push(); + duk_uarridx_t i = 0; + for (int32_t y = 0; y < pixelData.Height; y++) + { + for (int32_t x = 0; x < pixelData.Width; x++) + { + auto dstI = y * pixelData.Width + x; + if (duk_get_prop_index(ctx, -1, i)) + { + imageData[dstI] = duk_get_int(ctx, -1) & 0xFF; + duk_pop(ctx); + } + else + { + imageData[dstI] = 0; + } + i++; + } + i += padding; + } + duk_pop(ctx); + } + else if (data.type() == DukValue::Type::STRING) + { + // From base64 string + data.push(); + duk_base64_decode(ctx, -1); + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); + if (buffer != nullptr) + { + SetPixelDataFromBuffer( + imageData, buffer, bufferLen, pixelData.Width, pixelData.Height, pixelData.Stride); + } + duk_pop(ctx); + } + else if (data.type() == DukValue::Type::OBJECT) + { + // From Uint8Array + data.push(); + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); + if (buffer != nullptr) + { + SetPixelDataFromBuffer( + imageData, buffer, bufferLen, pixelData.Width, pixelData.Height, pixelData.Stride); + } + duk_pop(ctx); + } + break; + } + case PixelDataKind::Rle: + { + auto data = DukGetDataFromBufferLikeObject(pixelData.Data); + std::memcpy(imageData, data.data(), data.size()); + break; + } + case PixelDataKind::Png: + default: + throw std::runtime_error("Unsupported pixel data type."); + } + return uniqueImageData.release(); + } + + template<> PixelDataKind FromDuk(const DukValue& d) + { + if (d.type() == DukValue::Type::STRING) + { + auto& s = d.as_string(); + if (s == "raw") + return PixelDataKind::Raw; + if (s == "rle") + return PixelDataKind::Rle; + if (s == "palette") + return PixelDataKind::Palette; + if (s == "png") + return PixelDataKind::Png; + } + return PixelDataKind::Unknown; + } + + static PixelData GetPixelDataFromDuk(const DukValue& dukPixelData) + { + PixelData pixelData; + pixelData.Type = FromDuk(dukPixelData["type"]); + pixelData.Width = dukPixelData["width"].as_int(); + pixelData.Height = dukPixelData["height"].as_int(); + pixelData.Stride = AsOrDefault(dukPixelData["stride"], pixelData.Width); + pixelData.Data = dukPixelData["data"]; + return pixelData; + } + + static void ReplacePixelDataForImage(ImageIndex id, const PixelData& pixelData, void* data) + { + // Setup the g1 element + rct_g1_element el{}; + auto* lastel = gfx_get_g1_element(id); + if (lastel != nullptr) + { + el = *lastel; + delete[] el.offset; + } + el.offset = reinterpret_cast(data); + el.width = pixelData.Width; + el.height = pixelData.Height; + el.flags = 0; + if (pixelData.Type == PixelDataKind::Rle) + { + el.flags |= G1_FLAG_RLE_COMPRESSION; + } + gfx_set_g1_element(id, &el); + } + + void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData) + { + auto pixelData = GetPixelDataFromDuk(dukPixelData); + try + { + auto newData = GetBufferFromPixelData(ctx, pixelData); + ReplacePixelDataForImage(id, pixelData, newData); + } + catch (const std::runtime_error& e) + { + duk_error(ctx, DUK_ERR_ERROR, e.what()); + } + } + +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2-ui/scripting/CustomImages.h b/src/openrct2-ui/scripting/CustomImages.h new file mode 100644 index 0000000000..ff9e5af237 --- /dev/null +++ b/src/openrct2-ui/scripting/CustomImages.h @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#ifdef ENABLE_SCRIPTING + +# include +# include + +namespace OpenRCT2::Scripting +{ + DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id); + DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id); + void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData); + +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2-ui/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp index 63a7682666..7c7fcb8a1d 100644 --- a/src/openrct2-ui/scripting/ScGraphicsContext.hpp +++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp @@ -11,6 +11,8 @@ #ifdef ENABLE_SCRIPTING +# include "CustomImages.h" + # include # include @@ -149,32 +151,7 @@ namespace OpenRCT2::Scripting DukValue getImage(uint32_t id) { - auto* g1 = gfx_get_g1_element(id); - if (g1 == nullptr) - { - return ToDuk(_ctx, undefined); - } - - DukObject obj(_ctx); - obj.Set("id", id); - obj.Set("offset", ToDuk(_ctx, { g1->x_offset, g1->y_offset })); - obj.Set("width", g1->width); - obj.Set("height", g1->height); - - obj.Set("isBMP", (g1->flags & G1_FLAG_BMP) != 0); - obj.Set("isRLE", (g1->flags & G1_FLAG_RLE_COMPRESSION) != 0); - obj.Set("isPalette", (g1->flags & G1_FLAG_PALETTE) != 0); - obj.Set("noZoom", (g1->flags & G1_FLAG_NO_ZOOM_DRAW) != 0); - - if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE) - { - obj.Set("nextZoomId", id - g1->zoomed_offset); - } - else - { - obj.Set("nextZoomId", undefined); - } - return obj.Take(); + return DukGetImageInfo(_ctx, id); } DukValue measureText(const std::string& text) diff --git a/src/openrct2-ui/scripting/ScImageManager.hpp b/src/openrct2-ui/scripting/ScImageManager.hpp index 983887034b..923f173fb6 100644 --- a/src/openrct2-ui/scripting/ScImageManager.hpp +++ b/src/openrct2-ui/scripting/ScImageManager.hpp @@ -11,6 +11,8 @@ #ifdef ENABLE_SCRIPTING +# include "CustomImages.h" + # include # include # include @@ -34,6 +36,8 @@ namespace OpenRCT2::Scripting dukglue_register_method(ctx, &ScImageManager::getAvailableAllocationRanges, "getAvailableAllocationRanges"); dukglue_register_method(ctx, &ScImageManager::allocate, "allocate"); dukglue_register_method(ctx, &ScImageManager::free, "free"); + dukglue_register_method(ctx, &ScImageManager::getImageInfo, "getImageInfo"); + dukglue_register_method(ctx, &ScImageManager::getPixelData, "getPixelData"); dukglue_register_method(ctx, &ScImageManager::setPixelData, "setPixelData"); } @@ -97,128 +101,19 @@ namespace OpenRCT2::Scripting gfx_object_free_images(start, count); } - static void setPixelDataFromBuffer( - uint8_t* dst, const uint8_t* src, size_t srcLen, int32_t width, int32_t height, int32_t stride) + DukValue getImageInfo(int32_t id) { - auto srcEnd = src + srcLen; - auto dstLen = static_cast(width) * height; - if (stride == width) - { - std::memcpy(dst, src, std::min(srcLen, dstLen)); - if (dstLen > srcLen) - { - std::memset(dst + srcLen, 0, dstLen - srcLen); - } - } - else - { - std::memset(dst, 0, dstLen); - auto srcLine = src; - for (int32_t y = 0; y < height; y++) - { - auto dstI = y * width; - auto lineWidth = std::min(srcEnd - srcLine, width); - std::memcpy(dst + dstI, srcLine, lineWidth); - if (lineWidth < width) - { - break; - } - srcLine += stride; - } - } + return DukGetImageInfo(_ctx, id); + } + + DukValue getPixelData(int32_t id) + { + return DukGetImagePixelData(_ctx, id); } void setPixelData(int32_t id, const DukValue& pixelData) { - auto dukType = pixelData["type"]; - auto& type = dukType.as_string(); - if (type == "raw") - { - auto width = pixelData["width"].as_int(); - auto height = pixelData["height"].as_int(); - auto stride = AsOrDefault(pixelData["stride"], width); - auto data = pixelData["data"]; - auto padding = stride - width; - - auto imageData = new (std::nothrow) uint8_t[width * height]; - if (imageData == nullptr) - { - duk_pop(_ctx); - duk_error(_ctx, DUK_ERR_ERROR, "Unable to allocate memory for pixel data."); - } - else - { - // Setup the g1 element - rct_g1_element el{}; - auto* lastel = gfx_get_g1_element(id); - if (lastel != nullptr) - { - el = *lastel; - delete el.offset; - } - - el.offset = imageData; - el.width = width; - el.height = height; - gfx_set_g1_element(id, &el); - - // Set the pixel data - if (data.is_array()) - { - // From array of numbers - data.push(); - duk_uarridx_t i = 0; - for (int32_t y = 0; y < height; y++) - { - for (int32_t x = 0; x < width; x++) - { - auto dstI = y * width + x; - if (duk_get_prop_index(_ctx, -1, i)) - { - imageData[dstI] = duk_get_int(_ctx, -1) & 0xFF; - duk_pop(_ctx); - } - else - { - imageData[dstI] = 0; - } - i++; - } - i += padding; - } - duk_pop(_ctx); - } - else if (data.type() == DukValue::Type::STRING) - { - // From base64 string - data.push(); - duk_base64_decode(_ctx, -1); - duk_size_t bufferLen{}; - const auto* buffer = reinterpret_cast(duk_get_buffer_data(_ctx, -1, &bufferLen)); - if (buffer != nullptr) - { - setPixelDataFromBuffer(imageData, buffer, bufferLen, width, height, stride); - } - duk_pop(_ctx); - } - else if (data.type() == DukValue::Type::OBJECT) - { - // From Uint8Array - data.push(); - duk_size_t bufferLen{}; - const auto* buffer = reinterpret_cast(duk_get_buffer_data(_ctx, -1, &bufferLen)); - if (buffer != nullptr) - { - setPixelDataFromBuffer(imageData, buffer, bufferLen, width, height, stride); - } - duk_pop(_ctx); - } - } - } - else - { - duk_error(_ctx, DUK_ERR_ERROR, "Unsupported pixel data type."); - } + DukSetPixelData(_ctx, id, pixelData); } DukValue CreateImageIndexRange(size_t start, size_t count) const From a977bbaebeebbe73ef334e05cc448bd388eb00ee Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Feb 2022 20:49:35 +0000 Subject: [PATCH 03/10] Implement loading .png and refactor --- distribution/openrct2.d.ts | 6 +- src/openrct2-ui/scripting/CustomImages.cpp | 198 ++++++++++----------- 2 files changed, 98 insertions(+), 106 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 49e0699a97..312beba13b 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2894,10 +2894,14 @@ declare global { type: 'png'; /** + * How the colours of the .png file are converted to the OpenRCT2 palette. * If keep is specified for palette, the raw 8bpp .png bytes will be loaded. The palette * in the .png will not be read. This will improve load performance. + * Closest will find the closest matching colour from the OpenRCT2 palette. + * Dither will add noise to reduce colour banding for images rich in colour. + * If undefined, only colours that are in OpenRCT2 palette will be imported. */ - palette?: 'keep'; + palette?: 'keep' | 'closest' | 'dither'; /** * Data can either by a: diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp index a283c7e14a..902f828128 100644 --- a/src/openrct2-ui/scripting/CustomImages.cpp +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -11,6 +11,9 @@ # include "CustomImages.h" +# include +using namespace OpenRCT2::Drawing; + namespace OpenRCT2::Scripting { enum class PixelDataKind @@ -22,12 +25,21 @@ namespace OpenRCT2::Scripting Png }; + enum class PixelDataPaletteKind + { + None, + Keep, + Closest, + Dither + }; + struct PixelData { PixelDataKind Type; int32_t Width; int32_t Height; int32_t Stride; + PixelDataPaletteKind Palette; DukValue Data; }; @@ -100,35 +112,17 @@ namespace OpenRCT2::Scripting return obj.Take(); } - static void SetPixelDataFromBuffer( - uint8_t* dst, const uint8_t* src, size_t srcLen, int32_t width, int32_t height, int32_t stride) + static std::vector GetBufferFromDukStack(duk_context* ctx) { - auto srcEnd = src + srcLen; - auto dstLen = static_cast(width) * height; - if (stride == width) + std::vector result; + duk_size_t bufferLen{}; + const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); + if (buffer != nullptr) { - std::memcpy(dst, src, std::min(srcLen, dstLen)); - if (dstLen > srcLen) - { - std::memset(dst + srcLen, 0, dstLen - srcLen); - } - } - else - { - std::memset(dst, 0, dstLen); - auto srcLine = src; - for (int32_t y = 0; y < height; y++) - { - auto dstI = y * width; - auto lineWidth = std::min(srcEnd - srcLine, width); - std::memcpy(dst + dstI, srcLine, lineWidth); - if (lineWidth < width) - { - break; - } - srcLine += stride; - } + result.resize(bufferLen); + std::memcpy(result.data(), buffer, bufferLen); } + return result; } static std::vector DukGetDataFromBufferLikeObject(const DukValue& data) @@ -156,111 +150,84 @@ namespace OpenRCT2::Scripting // From base64 string data.push(); duk_base64_decode(ctx, -1); - duk_size_t bufferLen{}; - const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); - if (buffer != nullptr) - { - result = std::vector(buffer, buffer + bufferLen); - } + result = GetBufferFromDukStack(ctx); duk_pop(ctx); } else if (data.type() == DukValue::Type::OBJECT) { // From Uint8Array data.push(); - duk_size_t bufferLen{}; - const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); - if (buffer != nullptr) - { - result = std::vector(buffer, buffer + bufferLen); - } + result = GetBufferFromDukStack(ctx); duk_pop(ctx); } return result; } - static uint8_t* GetBufferFromPixelData(duk_context* ctx, const PixelData& pixelData) + static std::vector RemovePadding(const std::vector& srcData, const PixelData& pixelData) { - auto padding = pixelData.Stride - pixelData.Width; - auto imageData = new (std::nothrow) uint8_t[pixelData.Width * pixelData.Height]; - if (imageData == nullptr) + std::vector unpadded(pixelData.Width * pixelData.Height, 0); + auto* src = srcData.data(); + auto* dst = unpadded.data(); + for (int32_t y = 0; y < pixelData.Height; y++) { - throw std::runtime_error("Unable to allocate memory for pixel data."); + std::memcpy(dst, src, pixelData.Width); + src += pixelData.Stride; + dst += pixelData.Width; } + return unpadded; + } - // Ensure image data is auto freed if exception occurs - std::unique_ptr uniqueImageData(imageData); + static std::vector GetBufferFromPixelData(duk_context* ctx, PixelData& pixelData) + { + std::vector imageData; switch (pixelData.Type) { case PixelDataKind::Raw: { - // Set the pixel data - auto& data = pixelData.Data; - if (data.is_array()) + auto data = DukGetDataFromBufferLikeObject(pixelData.Data); + if (pixelData.Stride != pixelData.Width) { - // From array of numbers - data.push(); - duk_uarridx_t i = 0; - for (int32_t y = 0; y < pixelData.Height; y++) - { - for (int32_t x = 0; x < pixelData.Width; x++) - { - auto dstI = y * pixelData.Width + x; - if (duk_get_prop_index(ctx, -1, i)) - { - imageData[dstI] = duk_get_int(ctx, -1) & 0xFF; - duk_pop(ctx); - } - else - { - imageData[dstI] = 0; - } - i++; - } - i += padding; - } - duk_pop(ctx); - } - else if (data.type() == DukValue::Type::STRING) - { - // From base64 string - data.push(); - duk_base64_decode(ctx, -1); - duk_size_t bufferLen{}; - const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); - if (buffer != nullptr) - { - SetPixelDataFromBuffer( - imageData, buffer, bufferLen, pixelData.Width, pixelData.Height, pixelData.Stride); - } - duk_pop(ctx); - } - else if (data.type() == DukValue::Type::OBJECT) - { - // From Uint8Array - data.push(); - duk_size_t bufferLen{}; - const auto* buffer = reinterpret_cast(duk_get_buffer_data(ctx, -1, &bufferLen)); - if (buffer != nullptr) - { - SetPixelDataFromBuffer( - imageData, buffer, bufferLen, pixelData.Width, pixelData.Height, pixelData.Stride); - } - duk_pop(ctx); + // Make sure data is expected size for RemovePadding + data.resize(pixelData.Stride * pixelData.Height); + data = RemovePadding(data, pixelData); } + + // Make sure data is expected size + data.resize(pixelData.Width * pixelData.Height); break; } case PixelDataKind::Rle: { - auto data = DukGetDataFromBufferLikeObject(pixelData.Data); - std::memcpy(imageData, data.data(), data.size()); + imageData = DukGetDataFromBufferLikeObject(pixelData.Data); break; } case PixelDataKind::Png: + { + auto imageFormat = pixelData.Palette == PixelDataPaletteKind::Keep ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32; + auto palette = pixelData.Palette == PixelDataPaletteKind::Keep ? ImageImporter::Palette::KeepIndices + : ImageImporter::Palette::OpenRCT2; + auto importMode = ImageImporter::ImportMode::Default; + if (pixelData.Palette == PixelDataPaletteKind::Closest) + importMode = ImageImporter::ImportMode::Closest; + else if (pixelData.Palette == PixelDataPaletteKind::Dither) + importMode = ImageImporter::ImportMode::Dithering; + auto pngData = DukGetDataFromBufferLikeObject(pixelData.Data); + auto image = Imaging::ReadFromBuffer(pngData, imageFormat); + + ImageImporter importer; + auto importResult = importer.Import(image, 0, 0, palette, ImageImporter::ImportFlags::RLE, importMode); + + pixelData.Type = PixelDataKind::Rle; + pixelData.Width = importResult.Element.width; + pixelData.Height = importResult.Element.height; + + imageData = std::move(importResult.Buffer); + break; + } default: throw std::runtime_error("Unsupported pixel data type."); } - return uniqueImageData.release(); + return imageData; } template<> PixelDataKind FromDuk(const DukValue& d) @@ -280,18 +247,34 @@ namespace OpenRCT2::Scripting return PixelDataKind::Unknown; } + template<> PixelDataPaletteKind FromDuk(const DukValue& d) + { + if (d.type() == DukValue::Type::STRING) + { + auto& s = d.as_string(); + if (s == "keep") + return PixelDataPaletteKind::Keep; + if (s == "closest") + return PixelDataPaletteKind::Closest; + if (s == "dither") + return PixelDataPaletteKind::Dither; + } + return PixelDataPaletteKind::None; + } + static PixelData GetPixelDataFromDuk(const DukValue& dukPixelData) { PixelData pixelData; pixelData.Type = FromDuk(dukPixelData["type"]); - pixelData.Width = dukPixelData["width"].as_int(); - pixelData.Height = dukPixelData["height"].as_int(); + pixelData.Palette = FromDuk(dukPixelData["palette"]); + pixelData.Width = AsOrDefault(dukPixelData["width"], 0); + pixelData.Height = AsOrDefault(dukPixelData["height"], 0); pixelData.Stride = AsOrDefault(dukPixelData["stride"], pixelData.Width); pixelData.Data = dukPixelData["data"]; return pixelData; } - static void ReplacePixelDataForImage(ImageIndex id, const PixelData& pixelData, void* data) + static void ReplacePixelDataForImage(ImageIndex id, const PixelData& pixelData, std::vector&& data) { // Setup the g1 element rct_g1_element el{}; @@ -301,7 +284,12 @@ namespace OpenRCT2::Scripting el = *lastel; delete[] el.offset; } - el.offset = reinterpret_cast(data); + + // Copy data into new unmanaged uint8_t[] + auto newData = new uint8_t[data.size()]; + std::memcpy(newData, data.data(), data.size()); + + el.offset = newData; el.width = pixelData.Width; el.height = pixelData.Height; el.flags = 0; @@ -318,7 +306,7 @@ namespace OpenRCT2::Scripting try { auto newData = GetBufferFromPixelData(ctx, pixelData); - ReplacePixelDataForImage(id, pixelData, newData); + ReplacePixelDataForImage(id, pixelData, std::move(newData)); } catch (const std::runtime_error& e) { From e3a1733cdd06b3e555840117e031b045c2235623 Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 25 Feb 2022 18:49:52 +0000 Subject: [PATCH 04/10] Implement plugin ownership of images --- src/openrct2-ui/scripting/CustomImages.cpp | 95 ++++++++++++++++++++ src/openrct2-ui/scripting/CustomImages.h | 6 ++ src/openrct2-ui/scripting/ScImageManager.hpp | 37 +++++--- src/openrct2-ui/scripting/UiExtensions.cpp | 2 + src/openrct2/drawing/Image.h | 37 +++++++- 5 files changed, 162 insertions(+), 15 deletions(-) diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp index 902f828128..5204148806 100644 --- a/src/openrct2-ui/scripting/CustomImages.cpp +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -11,7 +11,11 @@ # include "CustomImages.h" +# include +# include # include +# include + using namespace OpenRCT2::Drawing; namespace OpenRCT2::Scripting @@ -43,6 +47,97 @@ namespace OpenRCT2::Scripting DukValue Data; }; + struct AllocatedImageList + { + std::shared_ptr Owner; + ImageList Range; + }; + + static std::vector _allocatedImages; + + static void FreeImages(ImageList range) + { + for (ImageIndex i = 0; i < range.Count; i++) + { + auto index = range.BaseId + i; + auto g1 = gfx_get_g1_element(index); + if (g1 != nullptr) + { + // Free pixel data + delete[] g1->offset; + + // Replace slot with empty element + rct_g1_element empty{}; + gfx_set_g1_element(index, &empty); + } + } + gfx_object_free_images(range.BaseId, range.Count); + } + + std::optional AllocateCustomImages(const std::shared_ptr& plugin, uint32_t count) + { + std::vector images; + images.resize(count); + + auto base = gfx_object_allocate_images(images.data(), count); + if (base == ImageIndexUndefined) + { + return {}; + } + auto range = ImageList(base, count); + + AllocatedImageList item; + item.Owner = plugin; + item.Range = range; + _allocatedImages.push_back(std::move(item)); + return range; + } + + bool FreeCustomImages(const std::shared_ptr& plugin, ImageList range) + { + auto it = std::find_if( + _allocatedImages.begin(), _allocatedImages.end(), + [&plugin, range](const AllocatedImageList& item) { return item.Owner == plugin && item.Range == range; }); + if (it == _allocatedImages.end()) + { + return false; + } + + FreeImages(it->Range); + _allocatedImages.erase(it); + return true; + } + + bool DoesPluginOwnImage(const std::shared_ptr& plugin, ImageIndex index) + { + auto it = std::find_if( + _allocatedImages.begin(), _allocatedImages.end(), + [&plugin, index](const AllocatedImageList& item) { return item.Owner == plugin && item.Range.Contains(index); }); + return it != _allocatedImages.end(); + } + + static void FreeCustomImages(const std::shared_ptr& plugin) + { + auto it = _allocatedImages.begin(); + while (it != _allocatedImages.end()) + { + if (it->Owner == plugin) + { + FreeImages(it->Range); + it = _allocatedImages.erase(it); + } + else + { + it++; + } + } + } + + void InitialiseCustomImages(ScriptEngine& scriptEngine) + { + scriptEngine.SubscribeToPluginStoppedEvent([](std::shared_ptr plugin) -> void { FreeCustomImages(plugin); }); + } + DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id) { auto* g1 = gfx_get_g1_element(id); diff --git a/src/openrct2-ui/scripting/CustomImages.h b/src/openrct2-ui/scripting/CustomImages.h index ff9e5af237..98e954833f 100644 --- a/src/openrct2-ui/scripting/CustomImages.h +++ b/src/openrct2-ui/scripting/CustomImages.h @@ -11,11 +11,17 @@ #ifdef ENABLE_SCRIPTING +# include # include # include +# include namespace OpenRCT2::Scripting { + void InitialiseCustomImages(ScriptEngine& scriptEngine); + std::optional AllocateCustomImages(const std::shared_ptr& plugin, uint32_t count); + bool FreeCustomImages(const std::shared_ptr& plugin, ImageList range); + bool DoesPluginOwnImage(const std::shared_ptr& plugin, ImageIndex index); DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id); DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id); void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData); diff --git a/src/openrct2-ui/scripting/ScImageManager.hpp b/src/openrct2-ui/scripting/ScImageManager.hpp index 923f173fb6..ced6dd20d1 100644 --- a/src/openrct2-ui/scripting/ScImageManager.hpp +++ b/src/openrct2-ui/scripting/ScImageManager.hpp @@ -13,6 +13,7 @@ # include "CustomImages.h" +# include # include # include # include @@ -83,22 +84,25 @@ namespace OpenRCT2::Scripting DukValue allocate(int32_t count) { - std::vector images; - images.resize(count); - - auto base = gfx_object_allocate_images(images.data(), count); - if (base == ImageIndexUndefined) - { - return ToDuk(_ctx, nullptr); - } - return CreateImageIndexRange(base, count); + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); + auto range = AllocateCustomImages(plugin, count); + return range ? CreateImageIndexRange(range->BaseId, range->Count) : ToDuk(_ctx, nullptr); } - void free(const DukValue& range) + void free(const DukValue& dukRange) { - auto start = range["start"].as_int(); - auto count = range["count"].as_int(); - gfx_object_free_images(start, count); + auto start = dukRange["start"].as_int(); + auto count = dukRange["count"].as_int(); + + ImageList range(start, count); + + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); + if (!FreeCustomImages(plugin, range)) + { + duk_error(_ctx, DUK_ERR_ERROR, "This plugin did not allocate the specified image range."); + } } DukValue getImageInfo(int32_t id) @@ -113,6 +117,13 @@ namespace OpenRCT2::Scripting void setPixelData(int32_t id, const DukValue& pixelData) { + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); + if (!DoesPluginOwnImage(plugin, id)) + { + duk_error(_ctx, DUK_ERR_ERROR, "This plugin did not allocate the specified image."); + } + DukSetPixelData(_ctx, id, pixelData); } diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 9d43a6430d..3e74f27fe5 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -11,6 +11,7 @@ # include "UiExtensions.h" +# include "CustomImages.h" # include "CustomMenu.h" # include "ScGraphicsContext.hpp" # include "ScImageManager.hpp" @@ -55,6 +56,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) ScTitleSequencePark::Register(ctx); ScWindow::Register(ctx); + InitialiseCustomImages(scriptEngine); InitialiseCustomMenuItems(scriptEngine); scriptEngine.SubscribeToPluginStoppedEvent( [](std::shared_ptr plugin) -> void { CloseWindowsOwnedByPlugin(plugin); }); diff --git a/src/openrct2/drawing/Image.h b/src/openrct2/drawing/Image.h index 359caaa271..114897afc5 100644 --- a/src/openrct2/drawing/Image.h +++ b/src/openrct2/drawing/Image.h @@ -12,15 +12,48 @@ #include #include #include +#include struct rct_g1_element; struct ImageList { - uint32_t BaseId; - uint32_t Count; + ImageIndex BaseId{}; + ImageIndex Count{}; + + ImageList() = default; + ImageList(ImageIndex baseId, ImageIndex count) + : BaseId(baseId) + , Count(count) + { + } + + bool Contains(ImageIndex index) const + { + return index >= BaseId && index < GetEnd(); + } + + ImageIndex GetEnd() const + { + return BaseId + Count; + } + + static ImageList FromBeginEnd(ImageIndex begin, ImageIndex end) + { + return ImageList(begin, end - begin); + } }; +constexpr bool operator==(const ImageList& lhs, const ImageList& rhs) +{ + return lhs.BaseId == rhs.BaseId && lhs.Count == rhs.Count; +} + +constexpr bool operator!=(const ImageList& lhs, const ImageList& rhs) +{ + return !(lhs == rhs); +} + uint32_t gfx_object_allocate_images(const rct_g1_element* images, uint32_t count); void gfx_object_free_images(uint32_t baseImageId, uint32_t count); void gfx_object_check_all_images_freed(); From 818dcacb310455f9dc71a4496c474ee963e30c84 Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 25 Feb 2022 20:05:10 +0000 Subject: [PATCH 05/10] Add draw to image API --- distribution/openrct2.d.ts | 9 ++-- src/openrct2-ui/scripting/CustomImages.cpp | 57 ++++++++++++++++++++ src/openrct2-ui/scripting/CustomImages.h | 1 + src/openrct2-ui/scripting/ScImageManager.hpp | 16 ++++++ src/openrct2/scripting/Plugin.cpp | 5 +- 5 files changed, 83 insertions(+), 5 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 312beba13b..cb3b6329d1 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2829,7 +2829,9 @@ declare global { setPixelData(id: number, data: PixelData): void; /** - * Gets a {@link GraphicsContext} for the given image so that you can draw directly to it. + * Calls the given function with a {@link GraphicsContext} for the given image, allowing the + * ability to draw directly to it. + * * Allocates or reallocates the image if not previously allocated or if the size is changed. * The pixels of the image will persist between calls, so you can draw over the top of what * is currently there. The default pixel colour will be 0 (transparent). @@ -2838,10 +2840,11 @@ declare global { * can in images is a good way to improve performance. * * Will error if given an ID of an image not owned by this plugin. - * @param id The id of the image to get a {@link GraphicsContext} for. + * @param id The id of the image to draw to. * @param size The size the image that should be allocated. + * @param callback The function that will draw to the image. */ - getGraphicsContext(id: number, size: ScreenSize): GraphicsContext; + draw(id: number, size: ScreenSize, callback: (g: GraphicsContext) => void): void; } type PixelData = RawPixelData | RlePixelData | PngPixelData; diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp index 5204148806..826f512fc4 100644 --- a/src/openrct2-ui/scripting/CustomImages.cpp +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -11,9 +11,12 @@ # include "CustomImages.h" +# include "ScGraphicsContext.hpp" + # include # include # include +# include # include using namespace OpenRCT2::Drawing; @@ -393,6 +396,7 @@ namespace OpenRCT2::Scripting el.flags |= G1_FLAG_RLE_COMPRESSION; } gfx_set_g1_element(id, &el); + drawing_engine_invalidate_image(id); } void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData) @@ -409,6 +413,59 @@ namespace OpenRCT2::Scripting } } + void DukDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const DukValue& callback) + { + auto* ctx = scriptEngine.GetContext(); + auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); + + auto drawingEngine = std::make_unique(GetContext()->GetUiContext()); + rct_drawpixelinfo dpi; + dpi.DrawingEngine = drawingEngine.get(); + dpi.width = size.width; + dpi.height = size.height; + + auto createNewImage = false; + auto g1 = gfx_get_g1_element(id); + if (g1 == nullptr || g1->width != size.width || g1->height != size.height || (g1->flags & G1_FLAG_RLE_COMPRESSION)) + { + createNewImage = true; + } + + if (createNewImage) + { + auto bufferSize = size.width* size.height; + dpi.bits = new uint8_t[bufferSize]; + std::memset(dpi.bits, 0, bufferSize); + + // Draw the original image if we are creating a new one + gfx_draw_sprite(&dpi, ImageId(id), { 0, 0 }); + } + else + { + dpi.bits = g1->offset; + } + + auto dukG = GetObjectAsDukValue(ctx, std::make_shared(ctx, dpi)); + scriptEngine.ExecutePluginCall(plugin, callback, { dukG }, false); + + if (createNewImage) + { + rct_g1_element newg1{}; + if (g1 != nullptr) + { + delete[] g1->offset; + newg1 = *g1; + } + newg1.offset = dpi.bits; + newg1.width = size.width; + newg1.height = size.height; + newg1.flags = 0; + gfx_set_g1_element(id, &newg1); + } + + drawing_engine_invalidate_image(id); + } + } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2-ui/scripting/CustomImages.h b/src/openrct2-ui/scripting/CustomImages.h index 98e954833f..930586b8cc 100644 --- a/src/openrct2-ui/scripting/CustomImages.h +++ b/src/openrct2-ui/scripting/CustomImages.h @@ -25,6 +25,7 @@ namespace OpenRCT2::Scripting DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id); DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id); void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData); + void DukDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const DukValue& callback); } // namespace OpenRCT2::Scripting diff --git a/src/openrct2-ui/scripting/ScImageManager.hpp b/src/openrct2-ui/scripting/ScImageManager.hpp index ced6dd20d1..9f1362e10c 100644 --- a/src/openrct2-ui/scripting/ScImageManager.hpp +++ b/src/openrct2-ui/scripting/ScImageManager.hpp @@ -40,6 +40,7 @@ namespace OpenRCT2::Scripting dukglue_register_method(ctx, &ScImageManager::getImageInfo, "getImageInfo"); dukglue_register_method(ctx, &ScImageManager::getPixelData, "getPixelData"); dukglue_register_method(ctx, &ScImageManager::setPixelData, "setPixelData"); + dukglue_register_method(ctx, &ScImageManager::draw, "draw"); } private: @@ -127,6 +128,21 @@ namespace OpenRCT2::Scripting DukSetPixelData(_ctx, id, pixelData); } + void draw(int32_t id, const DukValue& dukSize, const DukValue& callback) + { + auto width = dukSize["width"].as_int(); + auto height = dukSize["height"].as_int(); + + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); + if (!DoesPluginOwnImage(plugin, id)) + { + duk_error(_ctx, DUK_ERR_ERROR, "This plugin did not allocate the specified image."); + } + + DukDrawCustomImage(scriptEngine, id, { width, height }, callback); + } + DukValue CreateImageIndexRange(size_t start, size_t count) const { DukObject obj(_ctx); diff --git a/src/openrct2/scripting/Plugin.cpp b/src/openrct2/scripting/Plugin.cpp index 89afb8f446..17a9e47094 100644 --- a/src/openrct2/scripting/Plugin.cpp +++ b/src/openrct2/scripting/Plugin.cpp @@ -89,17 +89,18 @@ void Plugin::Start() throw std::runtime_error("No main function specified."); } + _hasStarted = true; + mainFunc.push(); auto result = duk_pcall(_context, 0); if (result != DUK_ERR_NONE) { auto val = std::string(duk_safe_to_string(_context, -1)); duk_pop(_context); + _hasStarted = false; throw std::runtime_error("[" + _metadata.Name + "] " + val); } duk_pop(_context); - - _hasStarted = true; } void Plugin::StopBegin() From 372606ab9afae880c91f59247d305928f2e6ebcc Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Mar 2022 23:18:55 +0000 Subject: [PATCH 06/10] Increment plugin API version --- src/openrct2/scripting/ScriptEngine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index a2a16e4c3f..cfeb5505ec 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -46,7 +46,7 @@ namespace OpenRCT2 namespace OpenRCT2::Scripting { - static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 51; + static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 52; // Versions marking breaking changes. static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33; From 6863fb1ecb5e8d953cc43f52f2a0b9a8ef2a12c9 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Mar 2022 23:33:07 +0000 Subject: [PATCH 07/10] Return undefined instead of null --- src/openrct2-ui/scripting/ScImageManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2-ui/scripting/ScImageManager.hpp b/src/openrct2-ui/scripting/ScImageManager.hpp index 9f1362e10c..559ea1a646 100644 --- a/src/openrct2-ui/scripting/ScImageManager.hpp +++ b/src/openrct2-ui/scripting/ScImageManager.hpp @@ -88,7 +88,7 @@ namespace OpenRCT2::Scripting auto& scriptEngine = GetContext()->GetScriptEngine(); auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); auto range = AllocateCustomImages(plugin, count); - return range ? CreateImageIndexRange(range->BaseId, range->Count) : ToDuk(_ctx, nullptr); + return range ? CreateImageIndexRange(range->BaseId, range->Count) : ToDuk(_ctx, undefined); } void free(const DukValue& dukRange) From 46999c122281f56e096d579e15d56385897e0bae Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Mar 2022 23:33:14 +0000 Subject: [PATCH 08/10] Update changelog --- distribution/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index d351dec31d..ad84cedc58 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -22,6 +22,7 @@ - Feature: [#16800] [Plugin] Add lift hill speed properties to API. - Feature: [#16806] Parkobj can load sprites from RCT image archives. - Feature: [#16831] Allow ternary colours for small and large scenery objects. +- Feature: [#16872] [Plugin] Add support for custom images. - Improved: [#3517] Cheats are now saved with the park. - Improved: [#10150] Ride stations are now properly checked if they’re sheltered. - Improved: [#10664, #16072] Visibility status can be modified directly in the Tile Inspector's list. From 273fe52e740d37a3ee51bd9284c808590c93be02 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Mar 2022 23:34:57 +0000 Subject: [PATCH 09/10] Fix formatting --- src/openrct2-ui/scripting/CustomImages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp index 826f512fc4..100cb02b57 100644 --- a/src/openrct2-ui/scripting/CustomImages.cpp +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -433,7 +433,7 @@ namespace OpenRCT2::Scripting if (createNewImage) { - auto bufferSize = size.width* size.height; + auto bufferSize = size.width * size.height; dpi.bits = new uint8_t[bufferSize]; std::memset(dpi.bits, 0, bufferSize); From 886a2ff21832971f734ac652bdb28f7db497c44f Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 24 Mar 2022 23:50:39 +0000 Subject: [PATCH 10/10] Fix include line --- src/openrct2/drawing/Image.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/openrct2/drawing/Image.h b/src/openrct2/drawing/Image.h index 114897afc5..992b3b6d50 100644 --- a/src/openrct2/drawing/Image.h +++ b/src/openrct2/drawing/Image.h @@ -9,10 +9,11 @@ #pragma once +#include "ImageId.hpp" + #include #include #include -#include struct rct_g1_element;