Merge pull request #7512 from IntelOrca/feature/parkobj

Introduce parkobj and json object .png support
This commit is contained in:
Ted John 2018-05-14 18:08:54 +01:00 committed by GitHub
commit afdcff8f9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 376 additions and 110 deletions

View File

@ -52,7 +52,7 @@ public:
size_t GetNumFiles() const override
{
return zip_get_num_files(_zip);
return zip_get_num_entries(_zip, 0);
}
std::string GetFileName(size_t index) const override
@ -82,11 +82,11 @@ public:
std::vector<uint8> GetFileData(const std::string_view& path) const override
{
std::vector<uint8> result;
auto index = (size_t)zip_name_locate(_zip, path.data(), 0);
auto index = GetIndexFromPath(path);
auto dataSize = GetFileSize(index);
if (dataSize > 0 && dataSize < SIZE_MAX)
{
auto zipFile = zip_fopen(_zip, path.data(), 0);
auto zipFile = zip_fopen_index(_zip, index, 0);
if (zipFile != nullptr)
{
result.resize((size_t)dataSize);
@ -110,7 +110,7 @@ public:
const auto& writeBuffer = *_writeBuffers.rbegin();
auto source = zip_source_buffer(_zip, writeBuffer.data(), writeBuffer.size(), 0);
auto index = zip_name_locate(_zip, path.data(), 0);
auto index = GetIndexFromPath(path);
if (index == -1)
{
zip_add(_zip, path.data(), source);
@ -123,15 +123,55 @@ public:
void DeleteFile(const std::string_view& path) override
{
auto index = zip_name_locate(_zip, path.data(), 0);
auto index = GetIndexFromPath(path);
zip_delete(_zip, index);
}
void RenameFile(const std::string_view& path, const std::string_view& newPath) override
{
auto index = zip_name_locate(_zip, path.data(), 0);
auto index = GetIndexFromPath(path);
zip_file_rename(_zip, index, newPath.data(), ZIP_FL_ENC_GUESS);
}
private:
/**
* Normalises both the given path and the stored paths and finds the first match.
*/
zip_int64_t GetIndexFromPath(const std::string_view& path) const
{
auto normalisedPath = NormalisePath(path);
if (!normalisedPath.empty())
{
auto numFiles = zip_get_num_entries(_zip, 0);
for (zip_int64_t i = 0; i < numFiles; i++)
{
auto normalisedZipPath = NormalisePath(zip_get_name(_zip, i, ZIP_FL_ENC_GUESS));
if (normalisedZipPath == normalisedPath)
{
return i;
}
}
}
return -1;
}
static std::string NormalisePath(const std::string_view& path)
{
std::string result;
if (!path.empty())
{
// Convert back slashes to forward slashes
result = std::string(path);
for (auto ch = result.data(); *ch != '\0'; ch++)
{
if (*ch == '\\')
{
*ch = '/';
}
}
}
return result;
}
};
namespace Zip

View File

@ -23,12 +23,6 @@
using namespace OpenRCT2::Drawing;
using ImportResult = ImageImporter::ImportResult;
struct RLECode
{
uint8 NumPixels{};
uint8 OffsetX{};
};
constexpr sint32 PALETTE_TRANSPARENT = -1;
ImportResult ImageImporter::Import(
@ -48,18 +42,34 @@ ImportResult ImageImporter::Import(
throw std::invalid_argument("Image is not palletted, it has bit depth of " + std::to_string(image.Depth));
}
if (!(flags & IMPORT_FLAGS::RLE))
{
throw std::invalid_argument("Only RLE image import is currently supported.");
}
const auto width = image.Width;
const auto height = image.Height;
const auto pixels = image.Pixels.data();
auto buffer = (uint8 *)std::malloc((height * 2) + (width * height * 16));
std::memset(buffer, 0, (height * 2) + (width * height * 16));
auto yOffsets = (uint16 *)buffer;
auto pixels = GetPixels(image.Pixels.data(), width, height, flags, mode);
auto [buffer, bufferLength] = flags & IMPORT_FLAGS::RLE ?
EncodeRLE(pixels.data(), width, height) :
EncodeRaw(pixels.data(), width, height);
rct_g1_element outElement;
outElement.offset = (uint8 *)buffer;
outElement.width = width;
outElement.height = height;
outElement.flags = (flags & IMPORT_FLAGS::RLE ? G1_FLAG_RLE_COMPRESSION : G1_FLAG_BMP);
outElement.x_offset = offsetX;
outElement.y_offset = offsetY;
outElement.zoomed_offset = 0;
ImportResult result;
result.Element = outElement;
result.Buffer = buffer;
result.BufferLength = bufferLength;
return result;
}
std::vector<sint32> ImageImporter::GetPixels(const uint8 * pixels, uint32 width, uint32 height, IMPORT_FLAGS flags, IMPORT_MODE mode)
{
std::vector<sint32> buffer;
buffer.reserve(width * height);
// A larger range is needed for proper dithering
auto palettedSrc = pixels;
@ -78,18 +88,8 @@ ImportResult ImageImporter::Import(
}
}
auto dst = buffer + (height * 2);
for (uint32 y = 0; y < height; y++)
{
yOffsets[y] = (uint16)(dst - buffer);
auto previousCode = (RLECode *)nullptr;
auto currentCode = (RLECode *)dst;
dst += 2;
auto startX = 0;
auto npixels = 0;
bool pushRun = false;
for (uint32 x = 0; x < width; x++)
{
sint32 paletteIndex;
@ -110,13 +110,63 @@ ImportResult ImageImporter::Import(
rgbaSrc += 4;
palettedSrc += 1;
buffer.push_back(paletteIndex);
}
}
return buffer;
}
std::tuple<void *, size_t> ImageImporter::EncodeRaw(const sint32 * pixels, uint32 width, uint32 height)
{
auto bufferLength = width * height;
auto buffer = (uint8 *)std::malloc(bufferLength);
for (size_t i = 0; i < bufferLength; i++)
{
auto p = pixels[i];
buffer[i] = (p == PALETTE_TRANSPARENT ? 0 : (uint8)p);
}
return std::make_tuple(buffer, bufferLength);
}
std::tuple<void *, size_t> ImageImporter::EncodeRLE(const sint32 * pixels, uint32 width, uint32 height)
{
struct RLECode
{
uint8 NumPixels{};
uint8 OffsetX{};
};
auto src = pixels;
auto buffer = (uint8 *)std::malloc((height * 2) + (width * height * 16));
if (buffer == nullptr)
{
throw std::bad_alloc();
}
std::memset(buffer, 0, (height * 2) + (width * height * 16));
auto yOffsets = (uint16 *)buffer;
auto dst = buffer + (height * 2);
for (uint32 y = 0; y < height; y++)
{
yOffsets[y] = (uint16)(dst - buffer);
auto previousCode = (RLECode *)nullptr;
auto currentCode = (RLECode *)dst;
dst += 2;
auto startX = 0;
auto npixels = 0;
bool pushRun = false;
for (uint32 x = 0; x < width; x++)
{
sint32 paletteIndex = *src++;
if (paletteIndex == PALETTE_TRANSPARENT)
{
if (npixels != 0)
{
x--;
rgbaSrc -= 4;
palettedSrc -= 1;
src--;
pushRun = true;
}
}
@ -173,22 +223,12 @@ ImportResult ImageImporter::Import(
}
auto bufferLength = (size_t)(dst - buffer);
buffer = (uint8 *)realloc(buffer, bufferLength);
rct_g1_element outElement;
outElement.offset = buffer;
outElement.width = width;
outElement.height = height;
outElement.flags = G1_FLAG_RLE_COMPRESSION;
outElement.x_offset = offsetX;
outElement.y_offset = offsetY;
outElement.zoomed_offset = 0;
ImportResult result;
result.Element = outElement;
result.Buffer = buffer;
result.BufferLength = bufferLength;
return result;
buffer = (uint8 * )realloc(buffer, bufferLength);
if (buffer == nullptr)
{
throw std::bad_alloc();
}
return std::make_tuple(buffer, bufferLength);
}
sint32 ImageImporter::CalculatePaletteIndex(IMPORT_MODE mode, sint16 * rgbaSrc, sint32 x, sint32 y, sint32 width, sint32 height)

View File

@ -15,6 +15,7 @@
#pragma endregion
#include <string_view>
#include <tuple>
#include "../core/Imaging.h"
#include "Drawing.h"
@ -59,6 +60,10 @@ namespace OpenRCT2::Drawing
private:
static const PaletteBGRA StandardPalette[256];
static std::vector<sint32> GetPixels(const uint8 * pixels, uint32 width, uint32 height, IMPORT_FLAGS flags, IMPORT_MODE mode);
static std::tuple<void *, size_t> EncodeRaw(const sint32 * pixels, uint32 width, uint32 height);
static std::tuple<void *, size_t> EncodeRLE(const sint32 * pixels, uint32 width, uint32 height);
static sint32 CalculatePaletteIndex(IMPORT_MODE mode, sint16 * rgbaSrc, sint32 x, sint32 y, sint32 width, sint32 height);
static sint32 GetPaletteIndex(const PaletteBGRA * palette, sint16 * colour);
static bool IsTransparentPixel(const sint16 * colour);

View File

@ -736,8 +736,12 @@ void FASTCALL gfx_draw_sprite_raw_masked_software(rct_drawpixelinfo *dpi, sint32
return;
}
assert(imgMask->flags & G1_FLAG_BMP);
assert(imgColour->flags & G1_FLAG_BMP);
// Only BMP format is supported for masking
if (!(imgMask->flags & G1_FLAG_BMP) || !(imgColour->flags & G1_FLAG_BMP))
{
gfx_draw_sprite_software(dpi, colourImage, x, y, 0);
return;
}
if (dpi->zoom_level != 0) {
// TODO: Implement other zoom levels (probably not used though)

View File

@ -16,6 +16,8 @@
#pragma once
#include <string_view>
#include <vector>
#include "../common.h"
#include "../core/Json.hpp"
#include "ImageTable.h"
@ -122,6 +124,7 @@ interface IReadObjectContext
virtual IObjectRepository& GetObjectRepository() abstract;
virtual bool ShouldLoadImages() abstract;
virtual std::vector<uint8> GetData(const std::string_view& path) abstract;
virtual void LogWarning(uint32 code, const utf8 * text) abstract;
virtual void LogError(uint32 code, const utf8 * text) abstract;

View File

@ -15,11 +15,14 @@
#pragma endregion
#include "../core/Console.hpp"
#include "../core/File.h"
#include "../core/FileStream.hpp"
#include "../core/Json.hpp"
#include "../core/Memory.hpp"
#include "../core/MemoryStream.h"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../core/Zip.h"
#include "../OpenRCT2.h"
#include "../rct12/SawyerChunkReader.h"
#include "BannerObject.h"
@ -38,13 +41,56 @@
#include "WallObject.h"
#include "WaterObject.h"
interface IFileDataRetriever
{
virtual ~IFileDataRetriever() = default;
virtual std::vector<uint8> GetData(const std::string_view& path) const abstract;
};
class FileSystemDataRetriever : public IFileDataRetriever
{
private:
std::string _basePath;
public:
FileSystemDataRetriever(const std::string_view& basePath)
: _basePath(basePath)
{
}
std::vector<uint8> GetData(const std::string_view& path) const override
{
auto absolutePath = Path::Combine(_basePath, path.data());
return File::ReadAllBytes(absolutePath);
}
};
class ZipDataRetriever : public IFileDataRetriever
{
private:
const IZipArchive& _zipArchive;
public:
ZipDataRetriever(const IZipArchive& zipArchive)
: _zipArchive(zipArchive)
{
}
std::vector<uint8> GetData(const std::string_view& path) const override
{
return _zipArchive.GetFileData(path);
}
};
class ReadObjectContext : public IReadObjectContext
{
private:
IObjectRepository& _objectRepository;
const IFileDataRetriever * _fileDataRetriever;
std::string _objectName;
bool _loadImages;
std::string _basePath;
bool _wasWarning = false;
bool _wasError = false;
@ -52,8 +98,13 @@ public:
bool WasWarning() const { return _wasWarning; }
bool WasError() const { return _wasError; }
ReadObjectContext(IObjectRepository& objectRepository, const std::string &objectName, bool loadImages)
ReadObjectContext(
IObjectRepository& objectRepository,
const std::string &objectName,
bool loadImages,
const IFileDataRetriever * fileDataRetriever)
: _objectRepository(objectRepository),
_fileDataRetriever(fileDataRetriever),
_objectName(objectName),
_loadImages(loadImages)
{
@ -69,6 +120,15 @@ public:
return _loadImages;
}
std::vector<uint8> GetData(const std::string_view& path) override
{
if (_fileDataRetriever != nullptr)
{
return _fileDataRetriever->GetData(path);
}
return {};
}
void LogWarning(uint32 code, const utf8 * text) override
{
_wasWarning = true;
@ -92,6 +152,8 @@ public:
namespace ObjectFactory
{
static Object * CreateObjectFromJson(IObjectRepository& objectRepository, const json_t * jRoot, const IFileDataRetriever * fileRetriever);
static void ReadObjectLegacy(Object * object, IReadObjectContext * context, IStream * stream)
{
try
@ -130,7 +192,7 @@ namespace ObjectFactory
log_verbose(" size: %zu", chunk->GetLength());
auto chunkStream = MemoryStream(chunk->GetData(), chunk->GetLength());
auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless);
auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless, nullptr);
ReadObjectLegacy(result, &readContext, &chunkStream);
if (readContext.WasError())
{
@ -156,7 +218,7 @@ namespace ObjectFactory
utf8 objectName[DAT_NAME_LENGTH + 1];
object_entry_get_name_fixed(objectName, sizeof(objectName), entry);
auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless);
auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless, nullptr);
auto chunkStream = MemoryStream(data, dataSize);
ReadObjectLegacy(result, &readContext, &chunkStream);
@ -228,6 +290,38 @@ namespace ObjectFactory
return 0xFF;
}
Object * CreateObjectFromZipFile(IObjectRepository& objectRepository, const std::string_view& path)
{
Object * result = nullptr;
try
{
auto archive = Zip::Open(path, ZIP_ACCESS::READ);
auto jsonBytes = archive->GetFileData("object.json");
if (jsonBytes.empty())
{
throw std::runtime_error("Unable to open object.json.");
}
json_error_t jsonLoadError;
auto jRoot = json_loadb((const char *)jsonBytes.data(), jsonBytes.size(), 0, &jsonLoadError);
if (jRoot == nullptr)
{
throw JsonException(&jsonLoadError);
}
auto fileDataRetriever = ZipDataRetriever(*archive);
return CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
}
catch (const std::exception& e)
{
Console::Error::WriteLine("Unable to open or read '%s': %s", path.data(), e.what());
delete result;
result = nullptr;
}
return result;
}
Object * CreateObjectFromJsonFile(IObjectRepository& objectRepository, const std::string &path)
{
log_verbose("CreateObjectFromJsonFile(\"%s\")", path.c_str());
@ -236,35 +330,8 @@ namespace ObjectFactory
try
{
auto jRoot = Json::ReadFromFile(path.c_str());
auto jObjectType = json_object_get(jRoot, "objectType");
if (json_is_string(jObjectType))
{
auto objectType = ParseObjectType(json_string_value(jObjectType));
if (objectType != 0xFF)
{
auto id = json_string_value(json_object_get(jRoot, "id"));
rct_object_entry entry = { 0 };
auto originalId = String::ToStd(json_string_value(json_object_get(jRoot, "originalId")));
auto originalName = originalId;
if (originalId.length() == 8 + 1 + 8 + 1 + 8)
{
entry.flags = std::stoul(originalId.substr(0, 8), 0, 16);
originalName = originalId.substr(9, 8);
entry.checksum = std::stoul(originalId.substr(18, 8), 0, 16);
}
auto minLength = std::min<size_t>(8, originalName.length());
memcpy(entry.name, originalName.c_str(), minLength);
result = CreateObject(entry);
auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2Headless);
result->ReadJson(&readContext, jRoot);
if (readContext.WasError())
{
throw std::runtime_error("Object has errors");
}
}
}
auto fileDataRetriever = FileSystemDataRetriever(Path::GetDirectory(path));
result = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
json_decref(jRoot);
}
catch (const std::runtime_error &err)
@ -276,4 +343,41 @@ namespace ObjectFactory
}
return result;
}
} // namespace ObjectFactory
Object * CreateObjectFromJson(IObjectRepository& objectRepository, const json_t * jRoot, const IFileDataRetriever * fileRetriever)
{
log_verbose("CreateObjectFromJson(...)");
Object * result = nullptr;
auto jObjectType = json_object_get(jRoot, "objectType");
if (json_is_string(jObjectType))
{
auto objectType = ParseObjectType(json_string_value(jObjectType));
if (objectType != 0xFF)
{
auto id = json_string_value(json_object_get(jRoot, "id"));
rct_object_entry entry = { 0 };
auto originalId = String::ToStd(json_string_value(json_object_get(jRoot, "originalId")));
auto originalName = originalId;
if (originalId.length() == 8 + 1 + 8 + 1 + 8)
{
entry.flags = std::stoul(originalId.substr(0, 8), 0, 16);
originalName = originalId.substr(9, 8);
entry.checksum = std::stoul(originalId.substr(18, 8), 0, 16);
}
auto minLength = std::min<size_t>(8, originalName.length());
memcpy(entry.name, originalName.c_str(), minLength);
result = CreateObject(entry);
auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2Headless, fileRetriever);
result->ReadJson(&readContext, jRoot);
if (readContext.WasError())
{
throw std::runtime_error("Object has errors");
}
}
}
return result;
}
}

View File

@ -16,6 +16,7 @@
#pragma once
#include <string_view>
#include "../common.h"
interface IObjectRepository;
@ -26,6 +27,7 @@ namespace ObjectFactory
{
Object * CreateObjectFromLegacyFile(IObjectRepository& objectRepository, const utf8 * path);
Object * CreateObjectFromLegacyData(IObjectRepository& objectRepository, const rct_object_entry * entry, const void * data, size_t dataSize);
Object * CreateObjectFromZipFile(IObjectRepository& objectRepository, const std::string_view& path);
Object * CreateObject(const rct_object_entry &entry);
Object * CreateObjectFromJsonFile(IObjectRepository& objectRepository, const std::string &path);

View File

@ -26,6 +26,7 @@
#include "../core/Memory.hpp"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../drawing/ImageImporter.h"
#include "../interface/Cursors.h"
#include "../localisation/Language.h"
#include "../PlatformEnvironment.h"
@ -35,6 +36,7 @@
#include "ObjectJsonHelpers.h"
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
namespace ObjectJsonHelpers
{
@ -316,6 +318,63 @@ namespace ObjectJsonHelpers
result = LoadObjectImages(context, name, range);
}
}
else
{
try
{
auto imageData = context->GetData(s);
auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE);
result.push_back(importResult.Element);
}
catch (const std::exception& e)
{
auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what());
context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str());
rct_g1_element emptyg1 = { 0 };
result.push_back(emptyg1);
}
}
return result;
}
static std::vector<rct_g1_element> ParseImages(IReadObjectContext * context, json_t * el)
{
auto path = GetString(el, "path");
auto x = GetInteger(el, "x");
auto y = GetInteger(el, "y");
auto raw = (GetString(el, "format") == "raw");
std::vector<rct_g1_element> result;
try
{
auto flags = ImageImporter::IMPORT_FLAGS::NONE;
if (!raw)
{
flags = (ImageImporter::IMPORT_FLAGS)(flags | ImageImporter::IMPORT_FLAGS::RLE);
}
auto imageData = context->GetData(path);
auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, flags);
auto g1Element = importResult.Element;
g1Element.x_offset = x;
g1Element.y_offset = y;
result.push_back(g1Element);
}
catch (const std::exception& e)
{
auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what());
context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str());
rct_g1_element emptyg1 = { 0 };
result.push_back(emptyg1);
}
return result;
}
@ -359,10 +418,20 @@ namespace ObjectJsonHelpers
if (context->ShouldLoadImages())
{
auto jsonImages = json_object_get(root, "images");
auto imageElements = GetJsonStringArray(jsonImages);
for (const auto &ie : imageElements)
size_t i;
json_t * el;
json_array_foreach(jsonImages, i, el)
{
auto images = ParseImages(context, ie);
std::vector<rct_g1_element> images;
if (json_is_string(el))
{
auto s = json_string_value(el);
images = ParseImages(context, s);
}
else if (json_is_object(el))
{
images = ParseImages(context, el);
}
for (const auto &g1 : images)
{
imageTable.AddImage(&g1);

View File

@ -82,7 +82,7 @@ class ObjectFileIndex final : public FileIndex<ObjectRepositoryItem>
private:
static constexpr uint32 MAGIC_NUMBER = 0x5844494F; // OIDX
static constexpr uint16 VERSION = 17;
static constexpr auto PATTERN = "*.dat;*.pob;*.json";
static constexpr auto PATTERN = "*.dat;*.pob;*.json;*.parkobj";
IObjectRepository& _objectRepository;
@ -103,34 +103,29 @@ public:
public:
std::tuple<bool, ObjectRepositoryItem> Create(sint32 language, const std::string &path) const override
{
Object * object = nullptr;
auto extension = Path::GetExtension(path);
if (String::Equals(extension, ".json", true))
{
auto object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path);
if (object != nullptr)
{
ObjectRepositoryItem item = { 0 };
item.ObjectEntry = *object->GetObjectEntry();
item.Path = String::Duplicate(path);
item.Name = String::Duplicate(object->GetName(language));
object->SetRepositoryItem(&item);
delete object;
return std::make_tuple(true, item);
}
object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path);
}
else if (String::Equals(extension, ".parkobj", true))
{
object = ObjectFactory::CreateObjectFromZipFile(_objectRepository, path);
}
else
{
auto object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str());
if (object != nullptr)
{
ObjectRepositoryItem item = { 0 };
item.ObjectEntry = *object->GetObjectEntry();
item.Path = String::Duplicate(path);
item.Name = String::Duplicate(object->GetName());
object->SetRepositoryItem(&item);
delete object;
return std::make_tuple(true, item);
}
object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str());
}
if (object != nullptr)
{
ObjectRepositoryItem item = { 0 };
item.ObjectEntry = *object->GetObjectEntry();
item.Path = String::Duplicate(path);
item.Name = String::Duplicate(object->GetName());
object->SetRepositoryItem(&item);
delete object;
return std::make_tuple(true, item);
}
return std::make_tuple(false, ObjectRepositoryItem());
}
@ -285,6 +280,10 @@ public:
{
return ObjectFactory::CreateObjectFromJsonFile(*this, ori->Path);
}
else if (String::Equals(extension, ".parkobj", true))
{
return ObjectFactory::CreateObjectFromZipFile(*this, ori->Path);
}
else
{
return ObjectFactory::CreateObjectFromLegacyFile(*this, ori->Path);