1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-17 20:13:07 +01:00
Files
OpenRCT2/src/openrct2/core/Imaging.cpp
Ted John a1d9e52046 Rename ToUtf16 to ToWideChar
This is more correct as wchar_t size can differ, for example Linux typically uses 32 bits for wchar_t where as Windows uses 16 bits.
2019-07-22 23:44:03 +01:00

347 lines
11 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable
#include "Imaging.h"
#include "../drawing/Drawing.h"
#include "Guard.hpp"
#include "IStream.hpp"
#include "Memory.hpp"
#include "String.hpp"
#include <algorithm>
#include <fstream>
#include <png.h>
#include <stdexcept>
#include <unordered_map>
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_t 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_t>(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_t 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:
{
#if defined(_WIN32) && !defined(__MINGW32__)
auto pathW = String::ToWideChar(path);
std::ifstream fs(pathW, std::ios::binary);
#else
std::ifstream fs(path.data(), std::ios::binary);
#endif
return ReadFromStream(fs, format);
}
}
}
Image ReadFromBuffer(const std::vector<uint8_t>& buffer, IMAGE_FORMAT format)
{
ivstream<uint8_t> 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:
{
#if defined(_WIN32) && !defined(__MINGW32__)
auto pathW = String::ToWideChar(path);
std::ofstream fs(pathW, std::ios::binary);
#else
std::ofstream fs(path.data(), std::ios::binary);
#endif
WritePng(fs, image);
break;
}
default:
throw std::runtime_error(EXCEPTION_IMAGE_FORMAT_UNKNOWN);
}
}
} // namespace Imaging