1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-15 11:03:00 +01:00

Merge pull request #7502 from IntelOrca/refactor/imaging

Refactor imaging and import
This commit is contained in:
Ted John
2018-05-13 11:28:13 +01:00
committed by GitHub
23 changed files with 1482 additions and 771 deletions

View File

@@ -46,6 +46,15 @@
933F2CB820935653001B33FD /* LocalisationService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933F2CB620935653001B33FD /* LocalisationService.cpp */; };
933F2CB920935653001B33FD /* LocalisationService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933F2CB620935653001B33FD /* LocalisationService.cpp */; };
933F2CBB20935668001B33FD /* LocalisationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 933F2CBA20935668001B33FD /* LocalisationService.h */; };
93CBA4C020A74FF200867D56 /* BitmapReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4BF20A74FF200867D56 /* BitmapReader.cpp */; };
93CBA4C320A7502E00867D56 /* Imaging.h in Headers */ = {isa = PBXBuildFile; fileRef = 93CBA4C120A7502D00867D56 /* Imaging.h */; };
93CBA4C420A7502E00867D56 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C220A7502E00867D56 /* Imaging.cpp */; };
93CBA4C520A7502E00867D56 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C220A7502E00867D56 /* Imaging.cpp */; };
93CBA4C620A7502E00867D56 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C220A7502E00867D56 /* Imaging.cpp */; };
93CBA4C920A7504500867D56 /* ImageImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C720A7504400867D56 /* ImageImporter.cpp */; };
93CBA4CA20A7504500867D56 /* ImageImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C720A7504400867D56 /* ImageImporter.cpp */; };
93CBA4CB20A7504500867D56 /* ImageImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93CBA4C720A7504400867D56 /* ImageImporter.cpp */; };
93CBA4CC20A7504500867D56 /* ImageImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 93CBA4C820A7504500867D56 /* ImageImporter.h */; };
C61ADB1F1FB6A0A70024F2EF /* TopToolbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB1E1FB6A0A60024F2EF /* TopToolbar.cpp */; };
C61ADB211FB7DC060024F2EF /* Scenery.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB201FB7DC060024F2EF /* Scenery.cpp */; };
C61ADB231FBBCB8B0024F2EF /* GameBottomToolbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB221FBBCB8A0024F2EF /* GameBottomToolbar.cpp */; };
@@ -404,7 +413,6 @@
F76C86031EC4E88300FA49E2 /* Sprite.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83AF1EC4E7CC00FA49E2 /* Sprite.cpp */; };
F76C86051EC4E88300FA49E2 /* Editor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83B11EC4E7CC00FA49E2 /* Editor.cpp */; };
F76C86071EC4E88300FA49E2 /* FileClassifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83B31EC4E7CC00FA49E2 /* FileClassifier.cpp */; };
F76C860B1EC4E88300FA49E2 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83B71EC4E7CC00FA49E2 /* Imaging.cpp */; };
F76C86451EC4E88300FA49E2 /* Http.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83F61EC4E7CC00FA49E2 /* Http.cpp */; };
F76C86471EC4E88300FA49E2 /* Network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83F81EC4E7CC00FA49E2 /* Network.cpp */; };
F76C86491EC4E88300FA49E2 /* NetworkAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83FA1EC4E7CC00FA49E2 /* NetworkAction.cpp */; };
@@ -869,6 +877,12 @@
9346F9D7208A191900C77D91 /* GuestPathfinding.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GuestPathfinding.cpp; sourceTree = "<group>"; };
933F2CB620935653001B33FD /* LocalisationService.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LocalisationService.cpp; sourceTree = "<group>"; };
933F2CBA20935668001B33FD /* LocalisationService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalisationService.h; sourceTree = "<group>"; };
93CBA4BE20A74FF200867D56 /* BitmapReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitmapReader.h; sourceTree = "<group>"; };
93CBA4BF20A74FF200867D56 /* BitmapReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BitmapReader.cpp; sourceTree = "<group>"; };
93CBA4C120A7502D00867D56 /* Imaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Imaging.h; sourceTree = "<group>"; };
93CBA4C220A7502E00867D56 /* Imaging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Imaging.cpp; sourceTree = "<group>"; };
93CBA4C720A7504400867D56 /* ImageImporter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageImporter.cpp; sourceTree = "<group>"; };
93CBA4C820A7504500867D56 /* ImageImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageImporter.h; sourceTree = "<group>"; };
C61ADB1E1FB6A0A60024F2EF /* TopToolbar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TopToolbar.cpp; sourceTree = "<group>"; };
C61ADB201FB7DC060024F2EF /* Scenery.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Scenery.cpp; sourceTree = "<group>"; };
C61ADB221FBBCB8A0024F2EF /* GameBottomToolbar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameBottomToolbar.cpp; sourceTree = "<group>"; };
@@ -1223,8 +1237,6 @@
F76C83B21EC4E7CC00FA49E2 /* Editor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Editor.h; sourceTree = "<group>"; };
F76C83B31EC4E7CC00FA49E2 /* FileClassifier.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FileClassifier.cpp; sourceTree = "<group>"; };
F76C83B41EC4E7CC00FA49E2 /* FileClassifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileClassifier.h; sourceTree = "<group>"; };
F76C83B71EC4E7CC00FA49E2 /* Imaging.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Imaging.cpp; sourceTree = "<group>"; };
F76C83B81EC4E7CC00FA49E2 /* Imaging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Imaging.h; sourceTree = "<group>"; };
F76C83BA1EC4E7CC00FA49E2 /* input.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = input.h; sourceTree = "<group>"; };
F76C83F61EC4E7CC00FA49E2 /* Http.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Http.cpp; sourceTree = "<group>"; };
F76C83F71EC4E7CC00FA49E2 /* http.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = http.h; sourceTree = "<group>"; };
@@ -1819,8 +1831,6 @@
4CE4623F1FD0710E0001CD98 /* Game.cpp */,
4CE462401FD0710E0001CD98 /* Game.h */,
4C80798D2062AFE400C5850B /* HandleParkLoad.h */,
F76C83B71EC4E7CC00FA49E2 /* Imaging.cpp */,
F76C83B81EC4E7CC00FA49E2 /* Imaging.h */,
4CC4B8E81FE00C5D00660D62 /* Input.cpp */,
4CC4B8E91FE00C5D00660D62 /* Input.h */,
4CC4B8EA1FE00C5D00660D62 /* Intro.cpp */,
@@ -1890,6 +1900,8 @@
children = (
F76C83791EC4E7CC00FA49E2 /* Collections.hpp */,
F76C837A1EC4E7CC00FA49E2 /* Console.cpp */,
93CBA4C220A7502E00867D56 /* Imaging.cpp */,
93CBA4C120A7502D00867D56 /* Imaging.h */,
F76C837B1EC4E7CC00FA49E2 /* Console.hpp */,
C6352B811F477022006CCEE3 /* DataSerialiser.h */,
C6352B821F477022006CCEE3 /* DataSerialiserTraits.h */,
@@ -1936,6 +1948,8 @@
4C7B53D620002CA400A52E21 /* Font.cpp */,
4C7B53CB1FFF995100A52E21 /* Font.h */,
F76C83A31EC4E7CC00FA49E2 /* IDrawingContext.h */,
93CBA4C720A7504400867D56 /* ImageImporter.cpp */,
93CBA4C820A7504500867D56 /* ImageImporter.h */,
F76C83A41EC4E7CC00FA49E2 /* IDrawingEngine.h */,
F76C83A51EC4E7CC00FA49E2 /* Image.cpp */,
4C7B53D720002CA400A52E21 /* LightFX.cpp */,
@@ -2567,6 +2581,8 @@
F76C858D1EC4E82600FA49E2 /* drawing */ = {
isa = PBXGroup;
children = (
93CBA4BF20A74FF200867D56 /* BitmapReader.cpp */,
93CBA4BE20A74FF200867D56 /* BitmapReader.h */,
F76C858E1EC4E82600FA49E2 /* engines */,
);
path = drawing;
@@ -2724,9 +2740,11 @@
C6352B971F477032006CCEE3 /* SetParkEntranceFeeAction.hpp in Headers */,
933F2CBB20935668001B33FD /* LocalisationService.h in Headers */,
C6352B861F477022006CCEE3 /* Endianness.h in Headers */,
93CBA4CC20A7504500867D56 /* ImageImporter.h in Headers */,
C6352B941F477032006CCEE3 /* PlaceParkEntranceAction.hpp in Headers */,
C6352B911F477032006CCEE3 /* GameAction.h in Headers */,
C62D838B1FD36D6F008C04F1 /* EditorObjectSelectionSession.h in Headers */,
93CBA4C320A7502E00867D56 /* Imaging.h in Headers */,
9308DA05209908090079EE96 /* Surface.h in Headers */,
C6352B841F477022006CCEE3 /* DataSerialiser.h in Headers */,
C67B28162002D67A00109C93 /* Window.h in Headers */,
@@ -3059,12 +3077,15 @@
C654DF3D1F69C0430040F43D /* TrackDesignPlace.cpp in Sources */,
C666EE721F37ACB10061AA04 /* Multiplayer.cpp in Sources */,
C654DF371F69C0430040F43D /* Sign.cpp in Sources */,
93CBA4C920A7504500867D56 /* ImageImporter.cpp in Sources */,
93CBA4C020A74FF200867D56 /* BitmapReader.cpp in Sources */,
C68878CC20289B710084B384 /* SoftwareDrawingEngine.cpp in Sources */,
C67CCD681FBBD138004FAE4C /* EditorMain.cpp in Sources */,
C6E415511FAFD6DC00D4A52A /* RideConstruction.cpp in Sources */,
C685E51B1F8907850090598F /* Guest.cpp in Sources */,
C64644F91F3FA4120026AC2D /* EditorInventionsList.cpp in Sources */,
C68878C720289B710084B384 /* OpenGLShaderProgram.cpp in Sources */,
93CBA4C420A7502E00867D56 /* Imaging.cpp in Sources */,
C6D2BEE61F9BAACE008B557C /* TrackList.cpp in Sources */,
C666EE701F37ACB10061AA04 /* LandRights.cpp in Sources */,
C666EE781F37ACB10061AA04 /* ServerList.cpp in Sources */,
@@ -3168,6 +3189,7 @@
C68878CE20289B9B0084B384 /* ObjectList.cpp in Sources */,
C688787620289A780084B384 /* RideGroupManager.cpp in Sources */,
C688788120289ADE0084B384 /* Line.cpp in Sources */,
93CBA4CA20A7504500867D56 /* ImageImporter.cpp in Sources */,
C688792520289B9B0084B384 /* RotoDrop.cpp in Sources */,
F76C85BA1EC4E88300FA49E2 /* CommandLine.cpp in Sources */,
C68878EE20289B9B0084B384 /* BolligerMabillardTrack.cpp in Sources */,
@@ -3247,7 +3269,6 @@
C68878E320289B9B0084B384 /* Android.cpp in Sources */,
F76C86051EC4E88300FA49E2 /* Editor.cpp in Sources */,
F76C86071EC4E88300FA49E2 /* FileClassifier.cpp in Sources */,
F76C860B1EC4E88300FA49E2 /* Imaging.cpp in Sources */,
C688786920289A660084B384 /* CableLift.cpp in Sources */,
C688790020289B9B0084B384 /* ReverseFreefallCoaster.cpp in Sources */,
C6607F481FE2B97E00D3FC0D /* Input.cpp in Sources */,
@@ -3354,6 +3375,7 @@
C688791220289B9B0084B384 /* GhostTrain.cpp in Sources */,
C688787F20289ADE0084B384 /* Font.cpp in Sources */,
C68878CF20289B9B0084B384 /* Litter.cpp in Sources */,
93CBA4C520A7502E00867D56 /* Imaging.cpp in Sources */,
F76C86AF1EC4E88400FA49E2 /* S4Importer.cpp in Sources */,
F76C86B01EC4E88400FA49E2 /* Tables.cpp in Sources */,
C688788520289ADE0084B384 /* Text.cpp in Sources */,
@@ -3441,9 +3463,11 @@
buildActionMask = 2147483647;
files = (
9308DA00209908090079EE96 /* TileElement.cpp in Sources */,
93CBA4CB20A7504500867D56 /* ImageImporter.cpp in Sources */,
9346F9DD208A191900C77D91 /* GuestPathfinding.cpp in Sources */,
9346F9DA208A191900C77D91 /* Guest.cpp in Sources */,
F7D7749E1EC6713200BE6EBC /* Cli.cpp in Sources */,
93CBA4C620A7502E00867D56 /* Imaging.cpp in Sources */,
9308DA03209908090079EE96 /* Surface.cpp in Sources */,
933F2CB920935653001B33FD /* LocalisationService.cpp in Sources */,
);

View File

@@ -20,6 +20,7 @@
#include <openrct2/PlatformEnvironment.h>
#include <openrct2/ui/UiContext.h>
#include "audio/AudioContext.h"
#include "drawing/BitmapReader.h"
#include "Ui.h"
#include "UiContext.h"
@@ -46,6 +47,7 @@ int main(int argc, const char * * argv)
{
int runGame = cmdline_run(argv, argc);
core_init();
RegisterBitmapReader();
if (runGame == 1)
{
if (gOpenRCT2Headless)

View File

@@ -577,69 +577,6 @@ public:
return _windowManager;
}
bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) override
{
auto bitmap = SDL_LoadBMP(path.c_str());
if (bitmap != nullptr)
{
sint32 numChannels = bitmap->format->BytesPerPixel;
if (numChannels < 3 || bitmap->format->BitsPerPixel < 24)
{
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_24_BIT_BITMAP);
SDL_FreeSurface(bitmap);
return false;
}
// Copy pixels over, then discard the surface
*outPixels = nullptr;
*outWidth = bitmap->w;
*outHeight = bitmap->h;
if (SDL_LockSurface(bitmap) == 0)
{
*outPixels = malloc(bitmap->w * bitmap->h * 4);
memset(*outPixels, 0xFF, bitmap->w * bitmap->h);
auto src = (const uint8 *)bitmap->pixels;
auto dst = (uint8 *)*outPixels;
if (numChannels == 4)
{
for (sint32 y = 0; y < bitmap->h; y++)
{
memcpy(dst, src, bitmap->w);
src += bitmap->pitch;
dst += bitmap->w;
}
}
else
{
for (sint32 y = 0; y < bitmap->h; y++)
{
for (sint32 x = 0; x < bitmap->w; x++)
{
memcpy(dst, src, 3);
src += 3;
dst += 4;
}
src += bitmap->pitch - (bitmap->w * 3);
}
}
SDL_UnlockSurface(bitmap);
}
else
{
return false;
}
SDL_FreeSurface(bitmap);
return true;
}
else
{
log_warning("Failed to load bitmap: %s", SDL_GetError());
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_BITMAP);
return false;
}
}
bool SetClipboardText(const utf8* target) override
{
return (SDL_SetClipboardText(target) == 0);

View File

@@ -0,0 +1,112 @@
#pragma region Copyright (c) 2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <algorithm>
#include <stdexcept>
#include <cstring>
#include <SDL2/SDL.h>
#include <openrct2/core/Imaging.h>
#include "BitmapReader.h"
static std::vector<uint8> ReadToVector(std::istream &stream)
{
std::vector<uint8> result;
if (!stream.eof() && !stream.fail())
{
stream.seekg(0, std::ios_base::end);
auto size = stream.tellg();
result.resize(size);
stream.seekg(0, std::ios_base::beg);
stream.read((char *)result.data(), size);
}
return result;
}
// TODO Bitmaps aren't very complicated to read so we should probably just write our
// own implementation in libopenrct2 and spare the AOT implementation registration.
static Image ReadBitmap(std::istream &istream, IMAGE_FORMAT format)
{
auto buffer = ReadToVector(istream);
auto sdlStream = SDL_RWFromConstMem(buffer.data(), (int)buffer.size());
auto bitmap = SDL_LoadBMP_RW(sdlStream, 1);
if (bitmap != nullptr)
{
auto numChannels = bitmap->format->BytesPerPixel;
if (numChannels < 3 || bitmap->format->BitsPerPixel < 24)
{
SDL_FreeSurface(bitmap);
throw std::runtime_error("Only 24-bit bitmaps are supported.");
}
// Copy pixels over, then discard the surface
if (SDL_LockSurface(bitmap) == 0)
{
Image image;
image.Width = bitmap->w;
image.Height = bitmap->h;
image.Depth = 32;
image.Pixels.resize(bitmap->w * bitmap->h * 4);
image.Stride = bitmap->w * 4;
// Clear image with 0xFF
std::fill(image.Pixels.begin(), image.Pixels.end(), 0xFF);
// Copy pixels over
auto src = (const uint8 *)bitmap->pixels;
auto dst = image.Pixels.data();
if (numChannels == 4)
{
for (sint32 y = 0; y < bitmap->h; y++)
{
std::memcpy(dst, src, bitmap->w);
src += bitmap->pitch;
dst += bitmap->w;
}
}
else
{
for (sint32 y = 0; y < bitmap->h; y++)
{
for (sint32 x = 0; x < bitmap->w; x++)
{
std::memcpy(dst, src, 3);
src += 3;
dst += 4;
}
src += bitmap->pitch - (bitmap->w * 3);
}
}
SDL_UnlockSurface(bitmap);
SDL_FreeSurface(bitmap);
return image;
}
else
{
SDL_FreeSurface(bitmap);
throw std::runtime_error("Unable to lock surface.");
}
}
else
{
throw std::runtime_error(SDL_GetError());
}
}
void RegisterBitmapReader()
{
Imaging::SetReader(IMAGE_FORMAT::BITMAP, ReadBitmap);
}

View File

@@ -0,0 +1,17 @@
#pragma region Copyright (c) 2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
void RegisterBitmapReader();

View File

@@ -20,16 +20,15 @@
#include <cstring>
#include <jansson.h>
#include "CmdlineSprite.h"
#include "core/Imaging.h"
#include "drawing/Drawing.h"
#include "Imaging.h"
#include "drawing/ImageImporter.h"
#include "localisation/Language.h"
#include "OpenRCT2.h"
#include "platform/platform.h"
#include "util/Util.h"
#define MODE_DEFAULT 0
#define MODE_CLOSEST 1
#define MODE_DITHERING 2
using namespace OpenRCT2::Drawing;
#pragma pack(push, 1)
@@ -233,287 +232,53 @@ static bool sprite_file_export(sint32 spriteIndex, const char *outPath)
gfx_bmp_sprite_to_buffer((uint8*)spriteFilePalette, spriteHeader->offset, pixels, spriteHeader, &dpi, spriteHeader->height, spriteHeader->width, IMAGE_TYPE_DEFAULT);
}
if (image_io_png_write(&dpi, (rct_palette*)spriteFilePalette, outPath)) {
auto const pixels8 = dpi.bits;
auto const pixelsLen = (dpi.width + dpi.pitch) * dpi.height;
try
{
Image image;
image.Width = dpi.width;
image.Height = dpi.height;
image.Depth = 8;
image.Stride = dpi.width + dpi.pitch;
image.Palette = std::make_unique<rct_palette>(*((rct_palette *)&spriteFilePalette));
image.Pixels = std::vector<uint8>(pixels8, pixels8 + pixelsLen);
Imaging::WriteToFile(outPath, image, IMAGE_FORMAT::PNG);
return true;
} else {
fprintf(stderr, "Error writing PNG");
}
catch (const std::exception& e)
{
fprintf(stderr, "Unable to write png: %s", e.what());
return false;
}
}
static bool is_transparent_pixel(const sint16 *colour){
return colour[3] < 128;
}
// Returns true if pixel index is an index not used for remapping
static bool is_changable_pixel(sint32 palette_index) {
if (palette_index == -1)
return true;
if (palette_index == 0)
return false;
if (palette_index >= 203 && palette_index < 214)
return false;
if (palette_index == 226)
return false;
if (palette_index >= 227 && palette_index < 229)
return false;
if (palette_index >= 243)
return false;
return true;
}
static sint32 get_closest_palette_index(const sint16 *colour){
uint32 smallest_error = (uint32)-1;
sint32 best_match = -1;
for (sint32 x = 0; x < 256; x++){
if (is_changable_pixel(x)){
uint32 error =
((sint16)(spriteFilePalette[x].r) - colour[0]) * ((sint16)(spriteFilePalette[x].r) - colour[0]) +
((sint16)(spriteFilePalette[x].g) - colour[1]) * ((sint16)(spriteFilePalette[x].g) - colour[1]) +
((sint16)(spriteFilePalette[x].b) - colour[2]) * ((sint16)(spriteFilePalette[x].b) - colour[2]);
if (smallest_error == (uint32)-1 || smallest_error > error){
best_match = x;
smallest_error = error;
}
}
}
return best_match;
}
static sint32 get_palette_index(sint16 *colour)
{
if (is_transparent_pixel(colour))
return -1;
for (sint32 i = 0; i < 256; i++) {
if ((sint16)(spriteFilePalette[i].r) != colour[0]) continue;
if ((sint16)(spriteFilePalette[i].g) != colour[1]) continue;
if ((sint16)(spriteFilePalette[i].b) != colour[2]) continue;
return i;
}
return -1;
}
static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offset, bool keep_palette, rct_g1_element *outElement, uint8 **outBuffer, int *outBufferLength, sint32 mode)
{
uint8 *pixels;
uint32 width, height;
sint32 bitDepth;
if (!image_io_png_read(&pixels, &width, &height, !keep_palette, path, &bitDepth))
try
{
fprintf(stderr, "Error reading PNG\n");
return false;
}
if (width > 256 || height > 256)
{
fprintf(stderr, "Only images 256x256 or less are supported.\n");
free(pixels);
return false;
}
if (keep_palette && (bitDepth != 8))
{
fprintf(stderr, "Image is not palletted, it has bit depth of %d\n", bitDepth);
free(pixels);
return false;
}
memcpy(spriteFilePalette, CmdlineSprite::_standardPalette, 256 * 4);
uint8 *buffer = (uint8 *)malloc((height * 2) + (width * height * 16));
memset(buffer, 0, (height * 2) + (width * height * 16));
uint16 *yOffsets = (uint16*)buffer;
// A larger range is needed for proper dithering
uint8 *palettedSrc = pixels;
sint16 *rgbaSrc = keep_palette? nullptr : (sint16 *)malloc(height * width * 4 * 2);
sint16 *rgbaSrc_orig = rgbaSrc;
if (!keep_palette)
{
for (uint32 x = 0; x < height * width * 4; x++)
auto format = IMAGE_FORMAT::PNG_32;
auto flags = ImageImporter::IMPORT_FLAGS::RLE;
if (keep_palette)
{
rgbaSrc[x] = (sint16) pixels[x];
format = IMAGE_FORMAT::PNG;
flags = (ImageImporter::IMPORT_FLAGS)(flags | ImageImporter::IMPORT_FLAGS::KEEP_PALETTE);
}
ImageImporter importer;
auto image = Imaging::ReadFromFile(path, format);
auto result = importer.Import(image, x_offset, y_offset, flags, (ImageImporter::IMPORT_MODE)mode);
*outElement = result.Element;
*outBuffer = (uint8 *)result.Buffer;
*outBufferLength = (int)result.BufferLength;
return true;
}
uint8 *dst = buffer + (height * 2);
for (uint32 y = 0; y < height; y++) {
rle_code *previousCode, *currentCode;
yOffsets[y] = (uint16)(dst - buffer);
previousCode = nullptr;
currentCode = (rle_code*)dst;
dst += 2;
sint32 startX = 0;
sint32 npixels = 0;
bool pushRun = false;
for (uint32 x = 0; x < width; x++) {
sint32 paletteIndex;
if (keep_palette)
{
paletteIndex = *palettedSrc;
// The 1st index is always transparent
if (paletteIndex == 0)
{
paletteIndex = -1;
}
}
else
{
paletteIndex = get_palette_index(rgbaSrc);
if (mode == MODE_CLOSEST || mode == MODE_DITHERING)
{
if (paletteIndex == -1 && !is_transparent_pixel(rgbaSrc))
{
paletteIndex = get_closest_palette_index(rgbaSrc);
}
}
if (mode == MODE_DITHERING)
{
if (!is_transparent_pixel(rgbaSrc) && is_changable_pixel(get_palette_index(rgbaSrc)))
{
sint16 dr = rgbaSrc[0] - (sint16)(spriteFilePalette[paletteIndex].r);
sint16 dg = rgbaSrc[1] - (sint16)(spriteFilePalette[paletteIndex].g);
sint16 db = rgbaSrc[2] - (sint16)(spriteFilePalette[paletteIndex].b);
if (x + 1 < width)
{
if (!is_transparent_pixel(rgbaSrc + 4) && is_changable_pixel(get_palette_index(rgbaSrc + 4)))
{
// Right
rgbaSrc[4] += dr * 7 / 16;
rgbaSrc[5] += dg * 7 / 16;
rgbaSrc[6] += db * 7 / 16;
}
}
if (y + 1 < height)
{
if (x > 0)
{
if (!is_transparent_pixel(rgbaSrc + 4 * (width - 1)) && is_changable_pixel(get_palette_index(rgbaSrc + 4 * (width - 1))))
{
// Bottom left
rgbaSrc[4 * (width - 1)] += dr * 3 / 16;
rgbaSrc[4 * (width - 1) + 1] += dg * 3 / 16;
rgbaSrc[4 * (width - 1) + 2] += db * 3 / 16;
}
}
// Bottom
if (!is_transparent_pixel(rgbaSrc + 4 * width) && is_changable_pixel(get_palette_index(rgbaSrc + 4 * width)))
{
rgbaSrc[4 * width] += dr * 5 / 16;
rgbaSrc[4 * width + 1] += dg * 5 / 16;
rgbaSrc[4 * width + 2] += db * 5 / 16;
}
if (x + 1 < width)
{
if (!is_transparent_pixel(rgbaSrc + 4 * (width + 1)) && is_changable_pixel(get_palette_index(rgbaSrc + 4 * (width + 1))))
{
// Bottom right
rgbaSrc[4 * (width + 1)] += dr * 1 / 16;
rgbaSrc[4 * (width + 1) + 1] += dg * 1 / 16;
rgbaSrc[4 * (width + 1) + 2] += db * 1 / 16;
}
}
}
}
}
}
rgbaSrc += 4;
palettedSrc += 1;
if (paletteIndex == -1)
{
if (npixels != 0)
{
x--;
rgbaSrc -= 4;
palettedSrc -= 1;
pushRun = true;
}
}
else
{
if (npixels == 0)
{
startX = x;
}
npixels++;
*dst++ = (uint8)paletteIndex;
}
if (npixels == 127 || x == width - 1)
{
pushRun = true;
}
if (pushRun)
{
if (npixels > 0)
{
previousCode = currentCode;
currentCode->num_pixels = npixels;
currentCode->offset_x = startX;
if (x == width - 1)
{
currentCode->num_pixels |= 0x80;
}
currentCode = (rle_code*)dst;
dst += 2;
}
else
{
if (previousCode == nullptr)
{
currentCode->num_pixels = 0x80;
currentCode->offset_x = 0;
}
else
{
previousCode->num_pixels |= 0x80;
dst -= 2;
}
}
startX = 0;
npixels = 0;
pushRun = false;
}
}
catch (const std::exception& e)
{
fprintf(stderr, "%s\n", e.what());
return false;
}
free(pixels);
free(rgbaSrc_orig);
sint32 bufferLength = (sint32)(dst - buffer);
buffer = (uint8 *)realloc(buffer, bufferLength);
outElement->offset = buffer;
outElement->width = width;
outElement->height = height;
outElement->flags = G1_FLAG_RLE_COMPRESSION;
outElement->x_offset = x_offset;
outElement->y_offset = y_offset;
outElement->zoomed_offset = 0;
*outBuffer = buffer;
*outBufferLength = bufferLength;
return true;
}
sint32 cmdline_for_sprite(const char **argv, sint32 argc)

View File

@@ -1127,11 +1127,6 @@ void context_input_handle_keyboard(bool isTitle)
windowManager->HandleKeyboard(isTitle);
}
bool context_read_bmp(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const utf8 * path)
{
return GetContext()->GetUiContext()->ReadBMP(outPixels, outWidth, outHeight, std::string(path));
}
void context_quit()
{
GetContext()->Quit();

View File

@@ -1,318 +0,0 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable
#include <algorithm>
#include <png.h>
#include "core/FileStream.hpp"
#include "core/Guard.hpp"
#include "core/Memory.hpp"
#include "drawing/Drawing.h"
#include "Imaging.h"
namespace Imaging
{
static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length);
static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length);
static void PngFlush(png_structp png_ptr);
static void PngWarning(png_structp png_ptr, const char * b);
static void PngError(png_structp png_ptr, const char * b);
bool PngRead(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth)
{
png_structp png_ptr;
png_infop info_ptr;
// Setup PNG structures
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (png_ptr == nullptr)
{
return false;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
return false;
}
// Open PNG file
try
{
unsigned int sig_read = 0;
auto fs = FileStream(path, FILE_MODE_OPEN);
// Set error handling
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
return false;
}
// Setup PNG reading
png_set_read_fn(png_ptr, &fs, PngReadData);
png_set_sig_bytes(png_ptr, sig_read);
uint32 readFlags = PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING;
if (expand)
{
// If we expand the resulting image always be full RGBA
readFlags |= PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_EXPAND;
}
png_read_png(png_ptr, info_ptr, readFlags, nullptr);
// Read header
png_uint_32 pngWidth, pngHeight;
int colourType, interlaceType;
png_get_IHDR(png_ptr, info_ptr, &pngWidth, &pngHeight, bitDepth, &colourType, &interlaceType, nullptr, nullptr);
// Read pixels as 32bpp RGBA data
png_size_t rowBytes = png_get_rowbytes(png_ptr, info_ptr);
png_bytepp rowPointers = png_get_rows(png_ptr, info_ptr);
uint8 * pngPixels = Memory::Allocate<uint8>(pngWidth * pngHeight * 4);
uint8 * dst = pngPixels;
if (colourType == PNG_COLOR_TYPE_RGB)
{
// 24-bit PNG (no alpha)
Guard::Assert(rowBytes == pngWidth * 3, GUARD_LINE);
for (png_uint_32 i = 0; i < pngHeight; i++)
{
uint8 * src = rowPointers[i];
for (png_uint_32 x = 0; x < pngWidth; x++)
{
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
*dst++ = 255;
}
}
}
else if (*bitDepth == 8 && !expand)
{
// 8-bit paletted or grayscale
Guard::Assert(rowBytes == pngWidth, GUARD_LINE);
for (png_uint_32 i = 0; i < pngHeight; i++)
{
std::copy_n(rowPointers[i], rowBytes, dst);
dst += rowBytes;
}
}
else
{
// 32-bit PNG (with alpha)
Guard::Assert(rowBytes == pngWidth * 4, GUARD_LINE);
for (png_uint_32 i = 0; i < pngHeight; i++)
{
std::copy_n(rowPointers[i], rowBytes, dst);
dst += rowBytes;
}
}
// Close the PNG
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
// Return the output data
*pixels = pngPixels;
if (width != nullptr) *width = pngWidth;
if (height != nullptr) *height = pngHeight;
return true;
}
catch (const std::exception &)
{
*pixels = nullptr;
if (width != nullptr) *width = 0;
if (height != nullptr) *height = 0;
return false;
}
}
bool PngWrite(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path)
{
bool result = false;
// Get image size
int stride = dpi->width + dpi->pitch;
// Setup PNG
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (png_ptr == nullptr)
{
return false;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
return false;
}
png_colorp png_palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
for (int i = 0; i < 256; i++)
{
const rct_palette_entry *entry = &palette->entries[i];
png_palette[i].blue = entry->blue;
png_palette[i].green = entry->green;
png_palette[i].red = entry->red;
}
png_set_PLTE(png_ptr, info_ptr, png_palette, PNG_MAX_PALETTE_LENGTH);
try
{
// Open file for writing
auto fs = FileStream(path, FILE_MODE_WRITE);
png_set_write_fn(png_ptr, &fs, PngWriteData, PngFlush);
// Set error handler
if (setjmp(png_jmpbuf(png_ptr)))
{
throw std::runtime_error("PNG ERROR");
}
// Write header
png_set_IHDR(
png_ptr, info_ptr, dpi->width, dpi->height, 8,
PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
);
png_byte transparentIndex = 0;
png_set_tRNS(png_ptr, info_ptr, &transparentIndex, 1, nullptr);
png_write_info(png_ptr, info_ptr);
// Write pixels
uint8 * bits = dpi->bits;
for (int y = 0; y < dpi->height; y++)
{
png_write_row(png_ptr, (png_byte *)bits);
bits += stride;
}
// Finish
png_write_end(png_ptr, nullptr);
result = true;
}
catch (const std::exception &)
{
}
png_free(png_ptr, png_palette);
png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
return result;
}
bool PngWrite32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path)
{
bool result = false;
// Setup PNG
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PngError, PngWarning);
if (png_ptr == nullptr)
{
return false;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
return false;
}
try
{
// Open file for writing
auto fs = FileStream(path, FILE_MODE_WRITE);
png_set_write_fn(png_ptr, &fs, PngWriteData, PngFlush);
// Set error handler
if (setjmp(png_jmpbuf(png_ptr)))
{
throw std::runtime_error("PNG ERROR");
}
// Write header
png_set_IHDR(
png_ptr, info_ptr, width, height, 8,
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
);
png_write_info(png_ptr, info_ptr);
// Write pixels
uint8 * bits = (uint8 *)pixels;
for (int y = 0; y < height; y++)
{
png_write_row(png_ptr, (png_byte *)bits);
bits += width * 4;
}
// Finish
png_write_end(png_ptr, nullptr);
result = true;
}
catch (const std::exception &)
{
}
png_destroy_write_struct(&png_ptr, &info_ptr);
return result;
}
static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length)
{
auto * fs = static_cast<FileStream *>(png_get_io_ptr(png_ptr));
fs->Read(data, length);
}
static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
{
auto * fs = static_cast<FileStream *>(png_get_io_ptr(png_ptr));
fs->Write(data, length);
}
static void PngFlush(png_structp png_ptr)
{
}
static void PngWarning([[maybe_unused]] png_structp png_ptr, const char * b)
{
log_warning(b);
}
static void PngError([[maybe_unused]] png_structp png_ptr, const char * b)
{
log_error(b);
}
}
bool image_io_png_read(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth)
{
return Imaging::PngRead(pixels, width, height, expand, path, bitDepth);
}
bool image_io_png_write(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path)
{
return Imaging::PngWrite(dpi, palette, path);
}
bool image_io_png_write_32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path)
{
return Imaging::PngWrite32bpp(width, height, pixels, path);
}

View File

@@ -1,33 +0,0 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include "common.h"
struct rct_drawpixelinfo;
struct rct_palette;
namespace Imaging
{
bool PngRead(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth);
bool PngWrite(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path);
bool PngWrite32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path);
}
bool image_io_png_read(uint8 * * pixels, uint32 * width, uint32 * height, bool expand, const utf8 * path, sint32 * bitDepth);
bool image_io_png_write(const rct_drawpixelinfo * dpi, const rct_palette * palette, const utf8 * path);
bool image_io_png_write_32bpp(sint32 width, sint32 height, const void * pixels, const utf8 * path);

View File

@@ -0,0 +1,372 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable
#include <algorithm>
#include <fstream>
#include <stdexcept>
#include <streambuf>
#include <unordered_map>
#include <png.h>
#include "FileStream.hpp"
#include "Guard.hpp"
#include "Imaging.h"
#include "Memory.hpp"
#include "String.hpp"
#include "../drawing/Drawing.h"
template<typename T>
class ivstream : public std::istream
{
private:
class vector_streambuf : public std::basic_streambuf<char, std::char_traits<char>>
{
public:
explicit vector_streambuf(const std::vector<T>& vec)
{
this->setg((char *)vec.data(), (char *)vec.data(), (char *)(vec.data() + vec.size()));
}
};
vector_streambuf _streambuf;
public:
ivstream(const std::vector<T>& vec)
: std::istream(&_streambuf),
_streambuf(vec)
{
}
};
namespace Imaging
{
constexpr auto EXCEPTION_IMAGE_FORMAT_UNKNOWN = "Unknown image format.";
static std::unordered_map<IMAGE_FORMAT, ImageReaderFunc> _readerImplementations;
static void PngReadData(png_structp png_ptr, png_bytep data, png_size_t length)
{
auto istream = static_cast<std::istream *>(png_get_io_ptr(png_ptr));
istream->read((char *)data, length);
}
static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
{
auto ostream = static_cast<std::ostream *>(png_get_io_ptr(png_ptr));
ostream->write((const char *)data, length);
}
static void PngFlush(png_structp png_ptr)
{
auto ostream = static_cast<std::ostream *>(png_get_io_ptr(png_ptr));
ostream->flush();
}
static void PngWarning(png_structp, const char * b)
{
log_warning(b);
}
static void PngError(png_structp, const char * b)
{
log_error(b);
}
static Image ReadPng(std::istream& istream, bool expandTo32)
{
png_structp png_ptr;
png_infop info_ptr;
try
{
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (png_ptr == nullptr)
{
throw std::runtime_error("png_create_read_struct failed.");
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
throw std::runtime_error("png_create_info_struct failed.");
}
// Set error handling
if (setjmp(png_jmpbuf(png_ptr)))
{
throw std::runtime_error("png error.");
}
// Setup PNG reading
int sig_read = 0;
png_set_read_fn(png_ptr, &istream, PngReadData);
png_set_sig_bytes(png_ptr, sig_read);
uint32 readFlags = PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING;
if (expandTo32)
{
// If we expand the resulting image always be full RGBA
readFlags |= PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_EXPAND;
}
png_read_png(png_ptr, info_ptr, readFlags, nullptr);
// Read header
png_uint_32 pngWidth, pngHeight;
int bitDepth, colourType, interlaceType;
png_get_IHDR(png_ptr, info_ptr, &pngWidth, &pngHeight, &bitDepth, &colourType, &interlaceType, nullptr, nullptr);
// Read pixels as 32bpp RGBA data
auto rowBytes = png_get_rowbytes(png_ptr, info_ptr);
auto rowPointers = png_get_rows(png_ptr, info_ptr);
auto pngPixels = std::vector<uint8>(pngWidth * pngHeight * 4);
auto dst = pngPixels.data();
if (colourType == PNG_COLOR_TYPE_RGB)
{
// 24-bit PNG (no alpha)
Guard::Assert(rowBytes == pngWidth * 3, GUARD_LINE);
for (png_uint_32 i = 0; i < pngHeight; i++)
{
auto src = rowPointers[i];
for (png_uint_32 x = 0; x < pngWidth; x++)
{
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
*dst++ = 255;
}
}
}
else if (bitDepth == 8 && !expandTo32)
{
// 8-bit paletted or grayscale
Guard::Assert(rowBytes == pngWidth, GUARD_LINE);
for (png_uint_32 i = 0; i < pngHeight; i++)
{
std::copy_n(rowPointers[i], rowBytes, dst);
dst += rowBytes;
}
}
else
{
// 32-bit PNG (with alpha)
Guard::Assert(rowBytes == pngWidth * 4, GUARD_LINE);
for (png_uint_32 i = 0; i < pngHeight; i++)
{
std::copy_n(rowPointers[i], rowBytes, dst);
dst += rowBytes;
}
}
// Close the PNG
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
// Return the output data
Image img;
img.Width = pngWidth;
img.Height = pngHeight;
img.Depth = expandTo32 ? 32 : 8;
img.Pixels = std::move(pngPixels);
img.Stride = pngWidth * (expandTo32 ? 4 : 1);
return img;
}
catch (const std::exception &)
{
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
throw;
}
}
static void WritePng(std::ostream& ostream, const Image& image)
{
png_structp png_ptr = nullptr;
png_colorp png_palette = nullptr;
try
{
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PngError, PngWarning);
if (png_ptr == nullptr)
{
throw std::runtime_error("png_create_write_struct failed.");
}
auto info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
throw std::runtime_error("png_create_info_struct failed.");
}
if (image.Depth == 8)
{
if (image.Palette == nullptr)
{
throw std::runtime_error("Expected a palette for 8-bit image.");
}
// Set the palette
png_palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
if (png_palette == nullptr)
{
throw std::runtime_error("png_malloc failed.");
}
for (size_t i = 0; i < PNG_MAX_PALETTE_LENGTH; i++)
{
const auto entry = &image.Palette->entries[i];
png_palette[i].blue = entry->blue;
png_palette[i].green = entry->green;
png_palette[i].red = entry->red;
}
png_set_PLTE(png_ptr, info_ptr, png_palette, PNG_MAX_PALETTE_LENGTH);
}
png_set_write_fn(png_ptr, &ostream, PngWriteData, PngFlush);
// Set error handler
if (setjmp(png_jmpbuf(png_ptr)))
{
throw std::runtime_error("PNG ERROR");
}
// Write header
auto colourType = PNG_COLOR_TYPE_RGB_ALPHA;
if (image.Depth == 8)
{
png_byte transparentIndex = 0;
png_set_tRNS(png_ptr, info_ptr, &transparentIndex, 1, nullptr);
colourType = PNG_COLOR_TYPE_PALETTE;
}
png_set_IHDR(
png_ptr,
info_ptr,
image.Width,
image.Height,
8,
colourType,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
// Write pixels
auto pixels = image.Pixels.data();
for (uint32 y = 0; y < image.Height; y++)
{
png_write_row(png_ptr, (png_byte *)pixels);
pixels += image.Stride;
}
png_write_end(png_ptr, nullptr);
png_free(png_ptr, png_palette);
png_destroy_write_struct(&png_ptr, nullptr);
}
catch (const std::exception&)
{
png_free(png_ptr, png_palette);
png_destroy_write_struct(&png_ptr, nullptr);
throw;
}
}
IMAGE_FORMAT GetImageFormatFromPath(const std::string_view& path)
{
if (String::EndsWith(path, ".png", true))
{
return IMAGE_FORMAT::PNG;
}
else if (String::EndsWith(path, ".bmp", true))
{
return IMAGE_FORMAT::BITMAP;
}
else
{
return IMAGE_FORMAT::UNKNOWN;
}
}
static ImageReaderFunc GetReader(IMAGE_FORMAT format)
{
auto result = _readerImplementations.find(format);
if (result != _readerImplementations.end())
{
return result->second;
}
return {};
}
void SetReader(IMAGE_FORMAT format, ImageReaderFunc impl)
{
_readerImplementations[format] = impl;
}
static Image ReadFromStream(std::istream& istream, IMAGE_FORMAT format)
{
switch (format)
{
case IMAGE_FORMAT::PNG:
return ReadPng(istream, false);
case IMAGE_FORMAT::PNG_32:
return ReadPng(istream, true);
case IMAGE_FORMAT::AUTOMATIC:
throw std::invalid_argument("format can not be automatic.");
default:
{
auto impl = GetReader(format);
if (impl)
{
return impl(istream, format);
}
throw std::runtime_error(EXCEPTION_IMAGE_FORMAT_UNKNOWN);
}
}
}
Image ReadFromFile(const std::string_view& path, IMAGE_FORMAT format)
{
switch (format)
{
case IMAGE_FORMAT::AUTOMATIC:
return ReadFromFile(path, GetImageFormatFromPath(path));
default:
{
std::ifstream fs(path.data(), std::ios::binary);
return ReadFromStream(fs, format);
}
}
}
Image ReadFromBuffer(const std::vector<uint8>& buffer, IMAGE_FORMAT format)
{
ivstream<uint8> istream(buffer);
return ReadFromStream(istream, format);
}
void WriteToFile(const std::string_view& path, const Image& image, IMAGE_FORMAT format)
{
switch (format)
{
case IMAGE_FORMAT::AUTOMATIC:
WriteToFile(path, image, GetImageFormatFromPath(path));
break;
case IMAGE_FORMAT::PNG:
{
std::ofstream fs(path.data(), std::ios::binary);
WritePng(fs, image);
break;
}
default:
throw std::runtime_error(EXCEPTION_IMAGE_FORMAT_UNKNOWN);
}
}
}

View File

@@ -0,0 +1,70 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <functional>
#include <istream>
#include <memory>
#include <string_view>
#include <vector>
#include "../common.h"
#include "../drawing/Drawing.h"
struct rct_drawpixelinfo;
struct rct_palette;
struct PaletteBGRA
{
uint8 Blue{};
uint8 Green{};
uint8 Red{};
uint8 Alpha{};
};
enum class IMAGE_FORMAT
{
UNKNOWN,
AUTOMATIC, // Automatically detect from file extension
BITMAP,
PNG,
PNG_32, // Force load to 32bpp buffer
};
struct Image
{
// Meta
uint32 Width{};
uint32 Height{};
uint32 Depth{};
// Data
std::vector<uint8> Pixels;
std::unique_ptr<rct_palette> Palette;
uint32 Stride{};
};
using ImageReaderFunc = std::function<Image(std::istream&, IMAGE_FORMAT)>;
namespace Imaging
{
IMAGE_FORMAT GetImageFormatFromPath(const std::string_view& path);
Image ReadFromFile(const std::string_view& path, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC);
Image ReadFromBuffer(const std::vector<uint8>& buffer, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC);
void WriteToFile(const std::string_view& path, const Image& image, IMAGE_FORMAT format = IMAGE_FORMAT::AUTOMATIC);
void SetReader(IMAGE_FORMAT format, ImageReaderFunc impl);
}

View File

@@ -160,6 +160,16 @@ namespace String
return StartsWith(str.c_str(), match.c_str(), ignoreCase);
}
bool EndsWith(const std::string_view& str, const std::string_view& match, bool ignoreCase)
{
if (str.size() >= match.size())
{
auto view = str.substr(str.size() - match.size());
return Equals(view.data(), match.data(), ignoreCase);
}
return false;
}
size_t IndexOf(const utf8 * str, utf8 match, size_t startIndex)
{
const utf8 * ch = str + startIndex;

View File

@@ -52,6 +52,7 @@ namespace String
bool Equals(const utf8 * a, const utf8 * b, bool ignoreCase = false);
bool StartsWith(const utf8 * str, const utf8 * match, bool ignoreCase = false);
bool StartsWith(const std::string &str, const std::string &match, bool ignoreCase = false);
bool EndsWith(const std::string_view& str, const std::string_view& match, bool ignoreCase = false);
size_t IndexOf(const utf8 * str, utf8 match, size_t startIndex = 0);
ptrdiff_t LastIndexOf(const utf8 * str, utf8 match);

View File

@@ -0,0 +1,610 @@
#pragma region Copyright (c) 2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <cstring>
#include <stdexcept>
#include <string>
#include "../core/Imaging.h"
#include "ImageImporter.h"
using namespace OpenRCT2::Drawing;
using ImportResult = ImageImporter::ImportResult;
struct RLECode
{
uint8 NumPixels{};
uint8 OffsetX{};
};
constexpr sint32 PALETTE_TRANSPARENT = -1;
ImportResult ImageImporter::Import(
const Image& image,
sint32 offsetX,
sint32 offsetY,
IMPORT_FLAGS flags,
IMPORT_MODE mode) const
{
if (image.Width > 256 || image.Height > 256)
{
throw std::invalid_argument("Only images 256x256 or less are supported.");
}
if ((flags & IMPORT_FLAGS::KEEP_PALETTE) && image.Depth != 8)
{
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;
// A larger range is needed for proper dithering
auto palettedSrc = pixels;
std::unique_ptr<sint16[]> rgbaSrcBuffer;
if (!(flags & IMPORT_FLAGS::KEEP_PALETTE))
{
rgbaSrcBuffer = std::make_unique<sint16[]>(height * width * 4);
}
auto rgbaSrc = rgbaSrcBuffer.get();
if (!(flags & IMPORT_FLAGS::KEEP_PALETTE))
{
for (uint32 x = 0; x < height * width * 4; x++)
{
rgbaSrc[x] = (sint16)pixels[x];
}
}
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;
if (flags & IMPORT_FLAGS::KEEP_PALETTE)
{
paletteIndex = *palettedSrc;
// The 1st index is always transparent
if (paletteIndex == 0)
{
paletteIndex = PALETTE_TRANSPARENT;
}
}
else
{
paletteIndex = CalculatePaletteIndex(mode, rgbaSrc, x, y, width, height);
}
rgbaSrc += 4;
palettedSrc += 1;
if (paletteIndex == PALETTE_TRANSPARENT)
{
if (npixels != 0)
{
x--;
rgbaSrc -= 4;
palettedSrc -= 1;
pushRun = true;
}
}
else
{
if (npixels == 0)
{
startX = x;
}
npixels++;
*dst++ = (uint8)paletteIndex;
}
if (npixels == 127 || x == width - 1)
{
pushRun = true;
}
if (pushRun)
{
if (npixels > 0)
{
previousCode = currentCode;
currentCode->NumPixels = npixels;
currentCode->OffsetX = startX;
if (x == width - 1)
{
currentCode->NumPixels |= 0x80;
}
currentCode = (RLECode *)dst;
dst += 2;
}
else
{
if (previousCode == nullptr)
{
currentCode->NumPixels = 0x80;
currentCode->OffsetX = 0;
}
else
{
previousCode->NumPixels |= 0x80;
dst -= 2;
}
}
startX = 0;
npixels = 0;
pushRun = false;
}
}
}
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;
}
sint32 ImageImporter::CalculatePaletteIndex(IMPORT_MODE mode, sint16 * rgbaSrc, sint32 x, sint32 y, sint32 width, sint32 height)
{
auto palette = StandardPalette;
auto paletteIndex = GetPaletteIndex(palette, rgbaSrc);
if (mode == IMPORT_MODE::CLOSEST || mode == IMPORT_MODE::DITHERING)
{
if (paletteIndex == PALETTE_TRANSPARENT && !IsTransparentPixel(rgbaSrc))
{
paletteIndex = GetClosestPaletteIndex(palette, rgbaSrc);
}
}
if (mode == IMPORT_MODE::DITHERING)
{
if (!IsTransparentPixel(rgbaSrc) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc)))
{
auto dr = rgbaSrc[0] - (sint16)(palette[paletteIndex].Red);
auto dg = rgbaSrc[1] - (sint16)(palette[paletteIndex].Green);
auto db = rgbaSrc[2] - (sint16)(palette[paletteIndex].Blue);
if (x + 1 < width)
{
if (!IsTransparentPixel(rgbaSrc + 4) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4)))
{
// Right
rgbaSrc[4] += dr * 7 / 16;
rgbaSrc[5] += dg * 7 / 16;
rgbaSrc[6] += db * 7 / 16;
}
}
if (y + 1 < height)
{
if (x > 0)
{
if (!IsTransparentPixel(rgbaSrc + 4 * (width - 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width - 1))))
{
// Bottom left
rgbaSrc[4 * (width - 1)] += dr * 3 / 16;
rgbaSrc[4 * (width - 1) + 1] += dg * 3 / 16;
rgbaSrc[4 * (width - 1) + 2] += db * 3 / 16;
}
}
// Bottom
if (!IsTransparentPixel(rgbaSrc + 4 * width) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * width)))
{
rgbaSrc[4 * width] += dr * 5 / 16;
rgbaSrc[4 * width + 1] += dg * 5 / 16;
rgbaSrc[4 * width + 2] += db * 5 / 16;
}
if (x + 1 < width)
{
if (!IsTransparentPixel(rgbaSrc + 4 * (width + 1)) && IsChangablePixel(GetPaletteIndex(palette, rgbaSrc + 4 * (width + 1))))
{
// Bottom right
rgbaSrc[4 * (width + 1)] += dr * 1 / 16;
rgbaSrc[4 * (width + 1) + 1] += dg * 1 / 16;
rgbaSrc[4 * (width + 1) + 2] += db * 1 / 16;
}
}
}
}
}
return paletteIndex;
}
sint32 ImageImporter::GetPaletteIndex(const PaletteBGRA * palette, sint16 * colour)
{
if (!IsTransparentPixel(colour))
{
for (sint32 i = 0; i < 256; i++)
{
if ((sint16)(palette[i].Red) == colour[0] &&
(sint16)(palette[i].Green) == colour[1] &&
(sint16)(palette[i].Blue) == colour[2])
{
return i;
}
}
}
return PALETTE_TRANSPARENT;
}
bool ImageImporter::IsTransparentPixel(const sint16 * colour)
{
return colour[3] < 128;
}
/**
* @returns true if pixel index is an index not used for remapping.
*/
bool ImageImporter::IsChangablePixel(sint32 paletteIndex)
{
if (paletteIndex == PALETTE_TRANSPARENT)
return true;
if (paletteIndex == 0)
return false;
if (paletteIndex >= 203 && paletteIndex < 214)
return false;
if (paletteIndex == 226)
return false;
if (paletteIndex >= 227 && paletteIndex < 229)
return false;
if (paletteIndex >= 243)
return false;
return true;
}
sint32 ImageImporter::GetClosestPaletteIndex(const PaletteBGRA * palette, const sint16 * colour)
{
auto smallestError = (uint32)-1;
auto bestMatch = PALETTE_TRANSPARENT;
for (sint32 x = 0; x < 256; x++)
{
if (IsChangablePixel(x))
{
uint32 error =
((sint16)(palette[x].Red) - colour[0]) * ((sint16)(palette[x].Red) - colour[0]) +
((sint16)(palette[x].Green) - colour[1]) * ((sint16)(palette[x].Green) - colour[1]) +
((sint16)(palette[x].Blue) - colour[2]) * ((sint16)(palette[x].Blue) - colour[2]);
if (smallestError == (uint32)-1 || smallestError > error)
{
bestMatch = x;
smallestError = error;
}
}
}
return bestMatch;
}
const PaletteBGRA ImageImporter::StandardPalette[256] =
{
// 0 (unused)
{ 0, 0, 0, 255 },
// 1 - 9 (misc. e.g. font and water)
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
//
{ 35, 35, 23, 255 },
{ 51, 51, 35, 255 },
{ 67, 67, 47, 255 },
{ 83, 83, 63, 255 },
{ 99, 99, 75, 255 },
{ 115, 115, 91, 255 },
{ 131, 131, 111, 255 },
{ 151, 151, 131, 255 },
{ 175, 175, 159, 255 },
{ 195, 195, 183, 255 },
{ 219, 219, 211, 255 },
{ 243, 243, 239, 255 },
{ 0, 47, 51, 255 },
{ 0, 59, 63, 255 },
{ 11, 75, 79, 255 },
{ 19, 91, 91, 255 },
{ 31, 107, 107, 255 },
{ 47, 123, 119, 255 },
{ 59, 139, 135, 255 },
{ 79, 155, 151, 255 },
{ 95, 175, 167, 255 },
{ 115, 191, 187, 255 },
{ 139, 207, 203, 255 },
{ 163, 227, 223, 255 },
{ 7, 43, 67, 255 },
{ 11, 59, 87, 255 },
{ 23, 75, 111, 255 },
{ 31, 87, 127, 255 },
{ 39, 99, 143, 255 },
{ 51, 115, 159, 255 },
{ 67, 131, 179, 255 },
{ 87, 151, 191, 255 },
{ 111, 175, 203, 255 },
{ 135, 199, 219, 255 },
{ 163, 219, 231, 255 },
{ 195, 239, 247, 255 },
{ 0, 27, 71, 255 },
{ 0, 43, 95, 255 },
{ 0, 63, 119, 255 },
{ 7, 83, 143, 255 },
{ 7, 111, 167, 255 },
{ 15, 139, 191, 255 },
{ 19, 167, 215, 255 },
{ 27, 203, 243, 255 },
{ 47, 231, 255, 255 },
{ 95, 243, 255, 255 },
{ 143, 251, 255, 255 },
{ 195, 255, 255, 255 },
{ 0, 0, 35, 255 },
{ 0, 0, 79, 255 },
{ 7, 7, 95, 255 },
{ 15, 15, 111, 255 },
{ 27, 27, 127, 255 },
{ 39, 39, 143, 255 },
{ 59, 59, 163, 255 },
{ 79, 79, 179, 255 },
{ 103, 103, 199, 255 },
{ 127, 127, 215, 255 },
{ 159, 159, 235, 255 },
{ 191, 191, 255, 255 },
{ 19, 51, 27, 255 },
{ 23, 63, 35, 255 },
{ 31, 79, 47, 255 },
{ 39, 95, 59, 255 },
{ 43, 111, 71, 255 },
{ 51, 127, 87, 255 },
{ 59, 143, 99, 255 },
{ 67, 155, 115, 255 },
{ 75, 171, 131, 255 },
{ 83, 187, 147, 255 },
{ 95, 203, 163, 255 },
{ 103, 219, 183, 255 },
{ 27, 55, 31, 255 },
{ 35, 71, 47, 255 },
{ 43, 83, 59, 255 },
{ 55, 99, 75, 255 },
{ 67, 111, 91, 255 },
{ 79, 135, 111, 255 },
{ 95, 159, 135, 255 },
{ 111, 183, 159, 255 },
{ 127, 207, 183, 255 },
{ 147, 219, 195, 255 },
{ 167, 231, 207, 255 },
{ 191, 247, 223, 255 },
{ 0, 63, 15, 255 },
{ 0, 83, 19, 255 },
{ 0, 103, 23, 255 },
{ 0, 123, 31, 255 },
{ 7, 143, 39, 255 },
{ 23, 159, 55, 255 },
{ 39, 175, 71, 255 },
{ 63, 191, 91, 255 },
{ 87, 207, 111, 255 },
{ 115, 223, 139, 255 },
{ 143, 239, 163, 255 },
{ 179, 255, 195, 255 },
{ 19, 43, 79, 255 },
{ 27, 55, 99, 255 },
{ 43, 71, 119, 255 },
{ 59, 87, 139, 255 },
{ 67, 99, 167, 255 },
{ 83, 115, 187, 255 },
{ 99, 131, 207, 255 },
{ 115, 151, 215, 255 },
{ 131, 171, 227, 255 },
{ 151, 191, 239, 255 },
{ 171, 207, 247, 255 },
{ 195, 227, 255, 255 },
{ 55, 19, 15, 255 },
{ 87, 43, 39, 255 },
{ 103, 55, 51, 255 },
{ 119, 67, 63, 255 },
{ 139, 83, 83, 255 },
{ 155, 99, 99, 255 },
{ 175, 119, 119, 255 },
{ 191, 139, 139, 255 },
{ 207, 159, 159, 255 },
{ 223, 183, 183, 255 },
{ 239, 211, 211, 255 },
{ 255, 239, 239, 255 },
{ 111, 27, 0, 255 },
{ 151, 39, 0, 255 },
{ 167, 51, 7, 255 },
{ 187, 67, 15, 255 },
{ 203, 83, 27, 255 },
{ 223, 103, 43, 255 },
{ 227, 135, 67, 255 },
{ 231, 163, 91, 255 },
{ 239, 187, 119, 255 },
{ 243, 211, 143, 255 },
{ 251, 231, 175, 255 },
{ 255, 247, 215, 255 },
{ 15, 43, 11, 255 },
{ 23, 55, 15, 255 },
{ 31, 71, 23, 255 },
{ 43, 83, 35, 255 },
{ 59, 99, 47, 255 },
{ 75, 115, 59, 255 },
{ 95, 135, 79, 255 },
{ 119, 155, 99, 255 },
{ 139, 175, 123, 255 },
{ 167, 199, 147, 255 },
{ 195, 219, 175, 255 },
{ 223, 243, 207, 255 },
{ 95, 0, 63, 255 },
{ 115, 7, 75, 255 },
{ 127, 15, 83, 255 },
{ 143, 31, 95, 255 },
{ 155, 43, 107, 255 },
{ 171, 63, 123, 255 },
{ 187, 83, 135, 255 },
{ 199, 103, 155, 255 },
{ 215, 127, 171, 255 },
{ 231, 155, 191, 255 },
{ 243, 195, 215, 255 },
{ 255, 235, 243, 255 },
{ 0, 0, 63, 255 },
{ 0, 0, 87, 255 },
{ 0, 0, 115, 255 },
{ 0, 0, 143, 255 },
{ 0, 0, 171, 255 },
{ 0, 0, 199, 255 },
{ 0, 7, 227, 255 },
{ 0, 7, 255, 255 },
{ 67, 79, 255, 255 },
{ 115, 123, 255, 255 },
{ 163, 171, 255, 255 },
{ 215, 219, 255, 255 },
{ 0, 39, 79, 255 },
{ 0, 51, 111, 255 },
{ 0, 63, 147, 255 },
{ 0, 71, 183, 255 },
{ 0, 79, 219, 255 },
{ 0, 83, 255, 255 },
{ 23, 111, 255, 255 },
{ 51, 139, 255, 255 },
{ 79, 163, 255, 255 },
{ 107, 183, 255, 255 },
{ 135, 203, 255, 255 },
{ 163, 219, 255, 255 },
{ 47, 51, 0, 255 },
{ 55, 63, 0, 255 },
{ 67, 75, 0, 255 },
{ 79, 87, 0, 255 },
{ 99, 107, 7, 255 },
{ 119, 127, 23, 255 },
{ 143, 147, 43, 255 },
{ 163, 167, 71, 255 },
{ 187, 187, 99, 255 },
{ 207, 207, 131, 255 },
{ 231, 231, 171, 255 },
{ 255, 255, 207, 255 },
// 203 - 214 (Secondary remap)
{ 27, 0, 63, 255 },
{ 51, 0, 103, 255 },
{ 63, 11, 123, 255 },
{ 79, 23, 143, 255 },
{ 95, 31, 163, 255 },
{ 111, 39, 183, 255 },
{ 143, 59, 219, 255 },
{ 171, 91, 239, 255 },
{ 187, 119, 243, 255 },
{ 203, 151, 247, 255 },
{ 223, 183, 251, 255 },
{ 239, 215, 255, 255 },
// 214 - 225 (Brown)
{ 0, 19, 39, 255 },
{ 7, 31, 55, 255 },
{ 15, 47, 71, 255 },
{ 31, 63, 91, 255 },
{ 51, 83, 107, 255 },
{ 75, 103, 123, 255 },
{ 107, 127, 143, 255 },
{ 127, 147, 163, 255 },
{ 147, 171, 187, 255 },
{ 171, 195, 207, 255 },
{ 195, 219, 231, 255 },
{ 223, 243, 255, 255 },
// 226 (unknown)
{ 75, 75, 55, 255 },
// 227 - 229 (tertiary remap)
{ 0, 183, 255, 255 },
{ 0, 219, 255, 255 },
{ 0, 255, 255, 255 },
// 230 - 239 (water)
{ 99, 107, 7, 255 },
{ 99, 107, 7, 255 },
{ 135, 143, 39, 255 },
{ 123, 131, 27, 255 },
{ 99, 107, 7, 255 },
{ 151, 155, 55, 255 },
{ 151, 155, 55, 255 },
{ 227, 227, 155, 255 },
{ 203, 203, 115, 255 },
{ 151, 155, 55, 255 },
// 240 - 242 (chain lift)
{ 91, 91, 67, 255 },
{ 107, 107, 83, 255 },
{ 123, 123, 99, 255 },
// Old 243 - 245, changed to nice shade remap below
// { 47, 47, 47, 255 },
// { 47, 47, 47, 255 },
// { 47, 71, 87, 255 },
// 243 to 254 (primary remap)
{ 47, 51, 111, 255 },
{ 47, 55, 131, 255 },
{ 51, 63, 151, 255 },
{ 51, 67, 171, 255 },
{ 47, 75, 191, 255 },
{ 43, 79, 211, 255 },
{ 35, 87, 231, 255 },
{ 31, 95, 255, 255 },
{ 39, 127, 255, 255 },
{ 51, 155, 255, 255 },
{ 63, 183, 255, 255 },
{ 75, 207, 255, 255 },
// 255 (unused?)
{ 0, 0, 0, 255 }
};

View File

@@ -0,0 +1,68 @@
#pragma region Copyright (c) 2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <string_view>
#include "../core/Imaging.h"
#include "Drawing.h"
struct Image;
namespace OpenRCT2::Drawing
{
/**
* Imports images to the internal RCT G1 format.
*/
class ImageImporter
{
public:
struct ImportResult
{
rct_g1_element Element{};
void * Buffer{};
size_t BufferLength{};
};
enum class IMPORT_MODE
{
DEFAULT,
CLOSEST,
DITHERING,
};
enum IMPORT_FLAGS
{
NONE = 0,
KEEP_PALETTE = 1 << 0,
RLE = 1 << 1,
};
ImportResult Import(
const Image& image,
sint32 offsetX = 0,
sint32 offsetY = 0,
IMPORT_FLAGS flags = IMPORT_FLAGS::NONE,
IMPORT_MODE mode = IMPORT_MODE::DEFAULT) const;
private:
static const PaletteBGRA StandardPalette[256];
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);
static bool IsChangablePixel(sint32 paletteIndex);
static sint32 GetClosestPaletteIndex(const PaletteBGRA * palette, const sint16 * colour);
};
}

View File

@@ -21,7 +21,7 @@
#include "../audio/audio.h"
#include "../Context.h"
#include "../core/Console.hpp"
#include "../Imaging.h"
#include "../core/Imaging.h"
#include "../OpenRCT2.h"
#include "Screenshot.h"
@@ -41,6 +41,29 @@ using namespace OpenRCT2;
uint8 gScreenshotCountdown = 0;
static bool WriteDpiToFile(const std::string_view& path, const rct_drawpixelinfo * dpi, const rct_palette& palette)
{
auto const pixels8 = dpi->bits;
auto const pixelsLen = (dpi->width + dpi->pitch) * dpi->height;
try
{
Image image;
image.Width = dpi->width;
image.Height = dpi->height;
image.Depth = 8;
image.Stride = dpi->width + dpi->pitch;
image.Palette = std::make_unique<rct_palette>(palette);
image.Pixels = std::vector<uint8>(pixels8, pixels8 + pixelsLen);
Imaging::WriteToFile(path, image, IMAGE_FORMAT::PNG);
return true;
}
catch (const std::exception& e)
{
log_error("Unable to write png: %s", e.what());
return false;
}
}
/**
*
* rct2: 0x006E3AEC
@@ -153,9 +176,12 @@ sint32 screenshot_dump_png(rct_drawpixelinfo *dpi)
rct_palette renderedPalette;
screenshot_get_rendered_palette(&renderedPalette);
if (image_io_png_write(dpi, &renderedPalette, path)) {
if (WriteDpiToFile(path, dpi, renderedPalette))
{
return index;
} else {
}
else
{
return -1;
}
}
@@ -169,9 +195,23 @@ sint32 screenshot_dump_png_32bpp(sint32 width, sint32 height, const void *pixels
return -1;
}
if (image_io_png_write_32bpp(width, height, pixels, path)) {
const auto pixels8 = (const uint8 *)pixels;
const auto pixelsLen = width * 4 * height;
try
{
Image image;
image.Width = width;
image.Height = height;
image.Depth = 32;
image.Stride = width * 4;
image.Pixels = std::vector<uint8>(pixels8, pixels8 + pixelsLen);
Imaging::WriteToFile(path, image, IMAGE_FORMAT::PNG_32);
return index;
} else {
}
catch (const std::exception &e)
{
log_error("Unable to save screenshot: %s", e.what());
return -1;
}
}
@@ -259,7 +299,7 @@ void screenshot_giant()
rct_palette renderedPalette;
screenshot_get_rendered_palette(&renderedPalette);
image_io_png_write(&dpi, &renderedPalette, path);
WriteDpiToFile(path, &dpi, renderedPalette);
free(dpi.bits);
@@ -576,7 +616,7 @@ sint32 cmdline_for_screenshot(const char * * argv, sint32 argc, ScreenshotOption
rct_palette renderedPalette;
screenshot_get_rendered_palette(&renderedPalette);
image_io_png_write(&dpi, &renderedPalette, outputPath);
WriteDpiToFile(outputPath, &dpi, renderedPalette);
free(dpi.bits);
drawing_engine_dispose();

View File

@@ -91,9 +91,6 @@ namespace OpenRCT2::Ui
return _windowManager;
}
// Misc
bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) override { return false; }
// Clipboard
bool SetClipboardText(const utf8* target) override { return false; }

View File

@@ -140,11 +140,6 @@ namespace OpenRCT2
// In-game UI
virtual IWindowManager * GetWindowManager() abstract;
// Misc.
// HACK: This should either be implemented ourselves in libopenrct2
// or the mapgen height map code is moved to libopenrct2ui.
virtual bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) abstract;
// Clipboard
virtual bool SetClipboardText(const utf8* target) abstract;
};

View File

@@ -25,7 +25,7 @@
#include "../core/String.hpp"
#include "../core/Util.hpp"
#include "../Game.h"
#include "../Imaging.h"
#include "../core/Imaging.h"
#include "../localisation/StringIds.h"
#include "../object/Object.h"
#include "../platform/platform.h"
@@ -647,75 +647,67 @@ static void mapgen_simplex(mapgen_settings * settings)
bool mapgen_load_heightmap(const utf8 * path)
{
const char * extension = path_get_extension(path);
uint8 * pixels;
size_t pitch;
uint32 numChannels;
uint32 width, height;
if (String::Equals(extension, ".png", false))
auto format = Imaging::GetImageFormatFromPath(path);
if (format == IMAGE_FORMAT::PNG)
{
sint32 bitDepth;
if (!image_io_png_read(&pixels, &width, &height, true, path, &bitDepth))
// Promote to 32-bit
format = IMAGE_FORMAT::PNG_32;
}
try
{
auto image = Imaging::ReadFromFile(path, format);
if (image.Width != image.Height)
{
log_warning("Error reading PNG");
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_PNG);
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_WIDTH_AND_HEIGHT_DO_NOT_MATCH);
return false;
}
numChannels = 4;
pitch = width * numChannels;
}
else if (strcicmp(extension, ".bmp") == 0)
{
if (!context_read_bmp((void **) &pixels, &width, &height, path))
auto size = image.Width;
if (image.Width > MAXIMUM_MAP_SIZE_PRACTICAL)
{
// ReadBMP contains context_show_error calls
return false;
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_HEIHGT_MAP_TOO_BIG);
size = std::min<uint32>(image.Height, MAXIMUM_MAP_SIZE_PRACTICAL);
}
numChannels = 4;
pitch = width * numChannels;
}
else
{
openrct2_assert(false, "A file with an invalid file extension was selected.");
return false;
}
// Allocate memory for the height map values, one byte pixel
delete[] _heightMapData.mono_bitmap;
_heightMapData.mono_bitmap = new uint8[size * size];
_heightMapData.width = size;
_heightMapData.height = size;
if (width != height)
{
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_WIDTH_AND_HEIGHT_DO_NOT_MATCH);
free(pixels);
return false;
}
if (width > MAXIMUM_MAP_SIZE_PRACTICAL)
{
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_HEIHGT_MAP_TOO_BIG);
width = height = Math::Min(height, (uint32)MAXIMUM_MAP_SIZE_PRACTICAL);
}
// Allocate memory for the height map values, one byte pixel
delete[] _heightMapData.mono_bitmap;
_heightMapData.mono_bitmap = new uint8[width * height];
_heightMapData.width = width;
_heightMapData.height = height;
// Copy average RGB value to mono bitmap
for (uint32 x = 0; x < _heightMapData.width; x++)
{
for (uint32 y = 0; y < _heightMapData.height; y++)
// Copy average RGB value to mono bitmap
constexpr auto numChannels = 4;
const auto pitch = image.Stride;
const auto pixels = image.Pixels.data();
for (uint32 x = 0; x < _heightMapData.width; x++)
{
const uint8 red = pixels[x * numChannels + y * pitch];
const uint8 green = pixels[x * numChannels + y * pitch + 1];
const uint8 blue = pixels[x * numChannels + y * pitch + 2];
_heightMapData.mono_bitmap[x + y * _heightMapData.width] = (red + green + blue) / 3;
for (uint32 y = 0; y < _heightMapData.height; y++)
{
const auto red = pixels[x * numChannels + y * pitch];
const auto green = pixels[x * numChannels + y * pitch + 1];
const auto blue = pixels[x * numChannels + y * pitch + 2];
_heightMapData.mono_bitmap[x + y * _heightMapData.width] = (red + green + blue) / 3;
}
}
return true;
}
catch (const std::exception& e)
{
switch (format)
{
case IMAGE_FORMAT::BITMAP:
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_BITMAP);
break;
case IMAGE_FORMAT::PNG_32:
context_show_error(STR_HEIGHT_MAP_ERROR, STR_ERROR_READING_PNG);
break;
default:
log_error("Unable to load height map image: %s", e.what());
break;
}
return false;
}
free(pixels);
return true;
}
/**

View File

@@ -147,6 +147,12 @@ add_executable(test_localisation ${STRING_TEST_SOURCES})
target_link_libraries(test_localisation ${GTEST_LIBRARIES} test-common ${LDL} z)
add_test(NAME localisation COMMAND test_localisation)
# ImageImporter tests
add_executable(test_imageimporter "${CMAKE_CURRENT_LIST_DIR}/ImageImporterTests.cpp"
"${CMAKE_CURRENT_LIST_DIR}/TestData.cpp")
target_link_libraries(test_imageimporter ${GTEST_LIBRARIES} libopenrct2)
add_test(NAME ImageImporter COMMAND test_imageimporter)
# Ride ratings test
set(RIDE_RATINGS_TEST_SOURCES "${CMAKE_CURRENT_LIST_DIR}/RideRatings.cpp"
"${CMAKE_CURRENT_LIST_DIR}/TestData.cpp")

View File

@@ -0,0 +1,48 @@
#include <string_view>
#include <openrct2/core/Path.hpp>
#include <openrct2/drawing/ImageImporter.h>
#include <gtest/gtest.h>
#include "TestData.h"
using namespace OpenRCT2::Drawing;
class ImageImporterTests : public testing::Test
{
public:
static std::string GetImagePath(const std::string_view& name)
{
return Path::Combine(TestData::GetBasePath(), "images", name.data());
}
static uint32 GetHash(void * buffer, size_t bufferLength)
{
uint32 hash = 27;
for (size_t i = 0; i < bufferLength; i++)
{
hash = (13 * hash) + ((uint8 *)buffer)[i];
}
return hash;
}
};
TEST_F(ImageImporterTests, Import_Logo)
{
auto logoPath = GetImagePath("logo.png");
ImageImporter importer;
auto image = Imaging::ReadFromFile(logoPath, IMAGE_FORMAT::PNG_32);
auto result = importer.Import(image, 3, 5, ImageImporter::IMPORT_FLAGS::RLE);
ASSERT_EQ(result.Buffer, result.Element.offset);
ASSERT_EQ(128, result.Element.width);
ASSERT_EQ(128, result.Element.height);
ASSERT_EQ(3, result.Element.x_offset);
ASSERT_EQ(5, result.Element.y_offset);
ASSERT_EQ(0, result.Element.zoomed_offset);
// Check to ensure RLE data doesn't change unexpectedly.
// Update expected hash if change is expected.
ASSERT_NE(nullptr, result.Buffer);
auto hash = GetHash(result.Buffer, result.BufferLength);
ASSERT_EQ(0xCEF27C7D, hash);
}

BIN
test/tests/testdata/images/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -57,6 +57,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="LanguagePackTest.cpp" />
<ClCompile Include="ImageImporterTests.cpp" />
<ClCompile Include="IniReaderTest.cpp" />
<ClCompile Include="IniWriterTest.cpp" />
<ClCompile Include="Localisation.cpp" />