1
0
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:
Ted John
2022-02-24 20:49:35 +00:00
parent e74396b464
commit a977bbaebe
2 changed files with 98 additions and 106 deletions

View File

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

View File

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