mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 19:13:07 +01:00
Implement loading .png and refactor
This commit is contained in:
6
distribution/openrct2.d.ts
vendored
6
distribution/openrct2.d.ts
vendored
@@ -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:
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
# include "CustomImages.h"
|
||||
|
||||
# include <openrct2/drawing/ImageImporter.h>
|
||||
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<uint8_t> GetBufferFromDukStack(duk_context* ctx)
|
||||
{
|
||||
auto srcEnd = src + srcLen;
|
||||
auto dstLen = static_cast<size_t>(width) * height;
|
||||
if (stride == width)
|
||||
std::vector<uint8_t> result;
|
||||
duk_size_t bufferLen{};
|
||||
const auto* buffer = reinterpret_cast<uint8_t*>(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<size_t>(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<uint8_t> 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<uint8_t*>(duk_get_buffer_data(ctx, -1, &bufferLen));
|
||||
if (buffer != nullptr)
|
||||
{
|
||||
result = std::vector<uint8_t>(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<uint8_t*>(duk_get_buffer_data(ctx, -1, &bufferLen));
|
||||
if (buffer != nullptr)
|
||||
{
|
||||
result = std::vector<uint8_t>(buffer, buffer + bufferLen);
|
||||
}
|
||||
result = GetBufferFromDukStack(ctx);
|
||||
duk_pop(ctx);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t* GetBufferFromPixelData(duk_context* ctx, const PixelData& pixelData)
|
||||
static std::vector<uint8_t> RemovePadding(const std::vector<uint8_t>& 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<uint8_t> 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<uint8_t[]> uniqueImageData(imageData);
|
||||
static std::vector<uint8_t> GetBufferFromPixelData(duk_context* ctx, PixelData& pixelData)
|
||||
{
|
||||
std::vector<uint8_t> 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<uint8_t*>(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<uint8_t*>(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<PixelDataKind>(dukPixelData["type"]);
|
||||
pixelData.Width = dukPixelData["width"].as_int();
|
||||
pixelData.Height = dukPixelData["height"].as_int();
|
||||
pixelData.Palette = FromDuk<PixelDataPaletteKind>(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<uint8_t>&& 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<uint8_t*>(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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user