1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-18 20:43:04 +01:00

Start working on custom image APIs

This commit is contained in:
Ted John
2022-02-23 02:02:07 +00:00
parent 0e607ed0a3
commit 8f30ed910f
3 changed files with 254 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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<rct_g1_element> 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<size_t>(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<size_t>(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<uint8_t*>(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<uint8_t*>(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);

View File

@@ -590,6 +590,11 @@ void ScriptEngine::StopUnloadRegisterAllPlugins()
void ScriptEngine::LoadTransientPlugins()
{
if (!_initialised)
{
Initialise();
RefreshPlugins();
}
_transientPluginsEnabled = true;
}