mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-02-02 11:45:13 +01:00
Merge pull request #25844 from Gymnasiast/refactor/json-in-sprite
Allow importing palettes in `sprite build`
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
0.4.31 (in development)
|
||||
------------------------------------------------------------------------
|
||||
- Feature: [#25844] The sprite builder now also supports adding JSON-based palettes.
|
||||
- Improved: [#25765] The ‘View options’ and ‘Special track elements’ dropdowns no longer need click-and-hold.
|
||||
- Fix: [#4643, #25167] Many metal supports draw with a filled in top when they didn't in vanilla, causing some slight misalignment and glitching.
|
||||
- Fix: [#25739] Game freezes when a tab in the New Ride window contains more than 384 items.
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace OpenRCT2::CommandLine::Sprite
|
||||
|
||||
std::unordered_map<u8string, Image> images{};
|
||||
|
||||
ImageImporter importer;
|
||||
|
||||
// Note: jsonSprite is deliberately left non-const: json_t behaviour changes when const
|
||||
for (auto& [jsonKey, jsonSprite] : jsonSprites.items())
|
||||
{
|
||||
@@ -73,45 +75,55 @@ namespace OpenRCT2::CommandLine::Sprite
|
||||
return -1;
|
||||
}
|
||||
|
||||
json_t path = jsonSprite["path"];
|
||||
if (!path.is_string())
|
||||
json_t colours = jsonSprite["colours"];
|
||||
if (colours.is_array())
|
||||
{
|
||||
fprintf(stderr, "Error: no path provided for sprite %s\n", jsonKey.c_str());
|
||||
return -1;
|
||||
}
|
||||
std::string strPath = Json::GetString(path);
|
||||
auto importResult = importer.importJSONPalette(jsonSprite);
|
||||
spriteFile.addPalette(importResult);
|
||||
|
||||
auto meta = createImageImportMetaFromJson(jsonSprite);
|
||||
meta.importMode = spriteMode;
|
||||
|
||||
auto imagePath = Path::GetAbsolute(Path::Combine(directoryPath, strPath));
|
||||
|
||||
const auto image_iter = images.find(imagePath);
|
||||
if (image_iter != images.end())
|
||||
{
|
||||
ImageImporter importer;
|
||||
auto importResult = importer.Import(image_iter->second, meta);
|
||||
spriteFile.AddImage(importResult);
|
||||
if (!silent)
|
||||
fprintf(stdout, "Added palette\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto image = SpriteImageLoad(imagePath, meta);
|
||||
if (image == std::nullopt)
|
||||
json_t path = jsonSprite["path"];
|
||||
if (!path.is_string())
|
||||
{
|
||||
fprintf(stderr, "Could not read image file: %s\nCanceling\n", imagePath.c_str());
|
||||
fprintf(stderr, "Error: no path provided for sprite %s\n", jsonKey.c_str());
|
||||
return -1;
|
||||
}
|
||||
images[imagePath] = image.value();
|
||||
std::string strPath = Json::GetString(path);
|
||||
|
||||
ImageImporter importer;
|
||||
auto importResult = importer.Import(image.value(), meta);
|
||||
auto meta = createImageImportMetaFromJson(jsonSprite);
|
||||
meta.importMode = spriteMode;
|
||||
|
||||
spriteFile.AddImage(importResult);
|
||||
auto imagePath = Path::GetAbsolute(Path::Combine(directoryPath, strPath));
|
||||
|
||||
const auto image_iter = images.find(imagePath);
|
||||
if (image_iter != images.end())
|
||||
{
|
||||
auto importResult = importer.Import(image_iter->second, meta);
|
||||
spriteFile.AddImage(importResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto image = SpriteImageLoad(imagePath, meta);
|
||||
if (image == std::nullopt)
|
||||
{
|
||||
fprintf(stderr, "Could not read image file: %s\nCancelling\n", imagePath.c_str());
|
||||
return -1;
|
||||
}
|
||||
images[imagePath] = image.value();
|
||||
|
||||
auto importResult = importer.Import(image.value(), meta);
|
||||
|
||||
spriteFile.AddImage(importResult);
|
||||
}
|
||||
|
||||
if (!silent)
|
||||
fprintf(stdout, "Added: %s\n", imagePath.c_str());
|
||||
}
|
||||
|
||||
if (!silent)
|
||||
fprintf(stdout, "Added: %s\n", imagePath.c_str());
|
||||
|
||||
numSuccessful++;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,11 @@ namespace OpenRCT2::CommandLine::Sprite
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteFile::addPalette(Drawing::PaletteImportResult& palette)
|
||||
{
|
||||
AddImage(*reinterpret_cast<Drawing::ImageImportResult*>(&palette));
|
||||
}
|
||||
|
||||
bool SpriteFile::Save(const utf8* path)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
namespace OpenRCT2::Drawing
|
||||
{
|
||||
struct ImageImportResult;
|
||||
}
|
||||
struct PaletteImportResult;
|
||||
} // namespace OpenRCT2::Drawing
|
||||
|
||||
namespace OpenRCT2::CommandLine::Sprite
|
||||
{
|
||||
@@ -28,6 +29,7 @@ namespace OpenRCT2::CommandLine::Sprite
|
||||
std::vector<G1Element> Entries;
|
||||
std::vector<uint8_t> Data;
|
||||
void AddImage(Drawing::ImageImportResult& image);
|
||||
void addPalette(Drawing::PaletteImportResult& image);
|
||||
bool Save(const utf8* path);
|
||||
static std::optional<SpriteFile> Open(const utf8* path);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "ImageImporter.h"
|
||||
|
||||
#include "../core/Guard.hpp"
|
||||
#include "../core/Imaging.h"
|
||||
#include "../core/Json.hpp"
|
||||
|
||||
@@ -59,6 +60,53 @@ namespace OpenRCT2::Drawing
|
||||
return result;
|
||||
}
|
||||
|
||||
PaletteImportResult ImageImporter::importJSONPalette(json_t& jPalette) const
|
||||
{
|
||||
Guard::Assert(jPalette.is_object(), "ImageImporter::importJSONPalette expects parameter jPalette to be an object");
|
||||
|
||||
auto jColours = jPalette["colours"];
|
||||
auto numColours = jColours.size();
|
||||
|
||||
std::vector<BGRColour> buffer;
|
||||
buffer.reserve(numColours);
|
||||
|
||||
for (auto& jColour : jColours)
|
||||
{
|
||||
BGRColour colour{};
|
||||
if (jColour.is_string())
|
||||
{
|
||||
colour = parseJSONPaletteColour(Json::GetString(jColour));
|
||||
}
|
||||
buffer.push_back(colour);
|
||||
}
|
||||
|
||||
G1Palette outElement = {};
|
||||
outElement.numColours = static_cast<int16_t>(numColours);
|
||||
outElement.startIndex = Json::GetNumber<int16_t>(jPalette["index"]);
|
||||
outElement.flags = { G1Flag::isPalette };
|
||||
|
||||
PaletteImportResult result;
|
||||
result.element = outElement;
|
||||
result.buffer = std::move(buffer);
|
||||
result.element.palette = result.buffer.data();
|
||||
return result;
|
||||
}
|
||||
|
||||
BGRColour ImageImporter::parseJSONPaletteColour(const std::string& s) const
|
||||
{
|
||||
uint8_t r = 0;
|
||||
uint8_t g = 0;
|
||||
uint8_t b = 0;
|
||||
if (s[0] == '#' && s.size() == 7)
|
||||
{
|
||||
// Expect #RRGGBB
|
||||
r = std::stoul(s.substr(1, 2), nullptr, 16) & 0xFF;
|
||||
g = std::stoul(s.substr(3, 2), nullptr, 16) & 0xFF;
|
||||
b = std::stoul(s.substr(5, 2), nullptr, 16) & 0xFF;
|
||||
}
|
||||
return { b, g, r };
|
||||
}
|
||||
|
||||
std::vector<int32_t> ImageImporter::GetPixels(const Image& image, const ImageImportMeta& meta)
|
||||
{
|
||||
const uint8_t* pixels = image.Pixels.data();
|
||||
|
||||
@@ -57,6 +57,11 @@ namespace OpenRCT2::Drawing
|
||||
G1Element Element{};
|
||||
std::vector<uint8_t> Buffer;
|
||||
};
|
||||
struct PaletteImportResult
|
||||
{
|
||||
G1Palette element{};
|
||||
std::vector<BGRColour> buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Imports images to the internal RCT G1 format.
|
||||
@@ -65,6 +70,7 @@ namespace OpenRCT2::Drawing
|
||||
{
|
||||
public:
|
||||
ImageImportResult Import(const Image& image, ImageImportMeta& meta) const;
|
||||
PaletteImportResult importJSONPalette(json_t& jPalette) const;
|
||||
|
||||
private:
|
||||
enum class PaletteIndexType : uint8_t
|
||||
@@ -88,6 +94,7 @@ namespace OpenRCT2::Drawing
|
||||
static bool IsChangablePixel(int32_t paletteIndex);
|
||||
static PaletteIndexType GetPaletteIndexType(int32_t paletteIndex);
|
||||
static int32_t GetClosestPaletteIndex(const GamePalette& palette, const int16_t* colour);
|
||||
BGRColour parseJSONPaletteColour(const std::string& s) const;
|
||||
};
|
||||
|
||||
// Note: jsonSprite is deliberately left non-const: json_t behaviour changes when const.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../core/IStream.hpp"
|
||||
#include "../core/Json.hpp"
|
||||
#include "../drawing/Drawing.h"
|
||||
#include "../drawing/ImageImporter.h"
|
||||
#include "../localisation/Formatter.h"
|
||||
#include "../localisation/Language.h"
|
||||
#include "../localisation/StringIds.h"
|
||||
@@ -101,45 +102,10 @@ namespace OpenRCT2
|
||||
|
||||
void WaterObject::ReadJsonPalette(json_t& jPalette)
|
||||
{
|
||||
Guard::Assert(jPalette.is_object(), "WaterObject::ReadJsonPalette expects parameter jPalette to be object");
|
||||
|
||||
auto jColours = jPalette["colours"];
|
||||
auto numColours = jColours.size();
|
||||
|
||||
// This pointer gets memcopied in ImageTable::AddImage so it's fine for the unique_ptr to go out of scope
|
||||
auto data = std::make_unique<Drawing::BGRColour[]>(numColours);
|
||||
size_t dataIndex = 0;
|
||||
|
||||
for (auto& jColour : jColours)
|
||||
{
|
||||
if (jColour.is_string())
|
||||
{
|
||||
data[dataIndex] = ParseColour(Json::GetString(jColour));
|
||||
}
|
||||
dataIndex++;
|
||||
}
|
||||
|
||||
G1Palette g1 = {};
|
||||
g1.palette = data.get();
|
||||
g1.numColours = static_cast<int16_t>(numColours);
|
||||
g1.startIndex = Json::GetNumber<int16_t>(jPalette["index"]);
|
||||
auto importer = Drawing::ImageImporter();
|
||||
const auto importResult = importer.importJSONPalette(jPalette);
|
||||
|
||||
auto& imageTable = GetImageTable();
|
||||
imageTable.addPalette(g1);
|
||||
}
|
||||
|
||||
Drawing::BGRColour WaterObject::ParseColour(const std::string& s) const
|
||||
{
|
||||
uint8_t r = 0;
|
||||
uint8_t g = 0;
|
||||
uint8_t b = 0;
|
||||
if (s[0] == '#' && s.size() == 7)
|
||||
{
|
||||
// Expect #RRGGBB
|
||||
r = std::stoul(s.substr(1, 2), nullptr, 16) & 0xFF;
|
||||
g = std::stoul(s.substr(3, 2), nullptr, 16) & 0xFF;
|
||||
b = std::stoul(s.substr(5, 2), nullptr, 16) & 0xFF;
|
||||
}
|
||||
return { b, g, r };
|
||||
imageTable.addPalette(importResult.element);
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -36,6 +36,5 @@ namespace OpenRCT2
|
||||
|
||||
private:
|
||||
void ReadJsonPalette(json_t& jPalette);
|
||||
Drawing::BGRColour ParseColour(const std::string& s) const;
|
||||
};
|
||||
} // namespace OpenRCT2
|
||||
|
||||
Reference in New Issue
Block a user