Files
filament/libs/imageio/src/ImageDecoder.cpp
2021-10-15 17:30:06 -07:00

504 lines
16 KiB
C++

#include <memory>
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <imageio/ImageDecoder.h>
#include <cstdint>
#include <cstring> // for memcmp
#include <iostream> // for cerr
#include <limits>
#include <memory>
#include <sstream>
#include <png.h>
// for ntohs
#if defined(WIN32)
# include <Winsock2.h>
# include <utils/unwindows.h>
#else
# include <arpa/inet.h>
#endif
#include <math/vec3.h>
#include <math/vec4.h>
#include <tinyexr.h>
#include <vector>
#include <image/ColorTransform.h>
#include <image/ImageOps.h>
#include <imageio/HDRDecoder.h>
namespace image {
class PNGDecoder : public ImageDecoder::Decoder {
public:
static PNGDecoder* create(std::istream& stream);
static bool checkSignature(char const* buf);
PNGDecoder(const PNGDecoder&) = delete;
PNGDecoder& operator=(const PNGDecoder&) = delete;
private:
explicit PNGDecoder(std::istream& stream);
~PNGDecoder() override;
void init();
// ImageDecoder::Decoder interface
LinearImage decode() override;
static void cb_error(png_structp, png_const_charp);
static void cb_stream(png_structp png, png_bytep buffer, png_size_t size);
void error();
void stream(void* buffer, size_t size);
png_structp mPNG = nullptr;
png_infop mInfo = nullptr;
std::istream& mStream;
std::streampos mStreamStartPos;
};
// -----------------------------------------------------------------------------------------------
class PSDDecoder : public ImageDecoder::Decoder {
public:
static PSDDecoder* create(std::istream& stream);
static bool checkSignature(char const* buf);
PSDDecoder(const PSDDecoder&) = delete;
PSDDecoder& operator=(const PSDDecoder&) = delete;
private:
explicit PSDDecoder(std::istream& stream);
~PSDDecoder() override;
// ImageDecoder::Decoder interface
LinearImage decode() override;
static const char sig[];
std::istream& mStream;
std::streampos mStreamStartPos;
};
// -----------------------------------------------------------------------------------------------
class EXRDecoder : public ImageDecoder::Decoder {
public:
static EXRDecoder* create(std::istream& stream, const std::string& sourceName);
static bool checkSignature(char const* buf);
EXRDecoder(const EXRDecoder&) = delete;
EXRDecoder& operator = (const EXRDecoder&) = delete;
private:
EXRDecoder(std::istream& stream, const std::string& sourceName);
~EXRDecoder() override;
// ImageDecoder::Decoder interface
LinearImage decode() override;
static const char sig[];
std::istream& mStream;
std::streampos mStreamStartPos;
std::string mSourceName;
};
// -----------------------------------------------------------------------------------------------
LinearImage ImageDecoder::decode(std::istream& stream, const std::string& sourceName,
ColorSpace sourceSpace) {
Format format = Format::NONE;
std::streampos pos = stream.tellg();
char buf[16];
stream.read(buf, sizeof(buf));
if (PNGDecoder::checkSignature(buf)) {
format = Format::PNG;
} else if (HDRDecoder::checkSignature(buf)) {
format = Format::HDR;
} else if (PSDDecoder::checkSignature(buf)) {
format = Format::PSD;
} else if (EXRDecoder::checkSignature(buf)) {
format = Format::EXR;
}
stream.seekg(pos);
std::unique_ptr<Decoder> decoder;
switch (format) {
case Format::NONE:
return LinearImage();
case Format::PNG:
decoder.reset(PNGDecoder::create(stream));
decoder->setColorSpace(sourceSpace);
break;
case Format::HDR:
decoder.reset(HDRDecoder::create(stream));
decoder->setColorSpace(ColorSpace::LINEAR);
break;
case Format::PSD:
decoder.reset(PSDDecoder::create(stream));
decoder->setColorSpace(ColorSpace::LINEAR);
break;
case Format::EXR:
decoder.reset(EXRDecoder::create(stream, sourceName));
decoder->setColorSpace(ColorSpace::LINEAR);
break;
}
return decoder->decode();
}
// -----------------------------------------------------------------------------------------------
static inline float read32(std::istream& istream) {
uint32_t data;
istream.read(reinterpret_cast<char*>(&data), sizeof(uint32_t));
data = ntohl(data);
return *reinterpret_cast<float*>(&data);
}
static inline float read16(std::istream& istream) {
uint16_t data;
istream.read(reinterpret_cast<char*>(&data), sizeof(uint16_t));
return static_cast<float>(ntohs(data)) / std::numeric_limits<uint16_t>::max();
}
// -----------------------------------------------------------------------------------------------
PNGDecoder* PNGDecoder::create(std::istream& stream) {
PNGDecoder* decoder = new PNGDecoder(stream);
decoder->init();
return decoder;
}
bool PNGDecoder::checkSignature(char const* buf) {
return png_check_sig(png_const_bytep(buf), 8);
}
PNGDecoder::PNGDecoder(std::istream& stream)
: mStream(stream), mStreamStartPos(stream.tellg()) {
mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
}
void PNGDecoder::init() {
png_set_error_fn(mPNG, this, cb_error, nullptr);
png_set_read_fn(mPNG, this, cb_stream);
}
PNGDecoder::~PNGDecoder() {
png_destroy_read_struct(&mPNG, &mInfo, nullptr);
}
LinearImage PNGDecoder::decode() {
std::unique_ptr<uint8_t[]> imageData;
try {
mInfo = png_create_info_struct(mPNG);
png_read_info(mPNG, mInfo);
int colorType = png_get_color_type(mPNG, mInfo);
int bitDepth = png_get_bit_depth(mPNG, mInfo);
if (colorType == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(mPNG);
}
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
if (bitDepth < 8) {
png_set_expand_gray_1_2_4_to_8(mPNG);
}
png_set_gray_to_rgb(mPNG);
}
if (png_get_valid(mPNG, mInfo, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(mPNG);
}
if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) {
double gamma = 1.0;
png_get_gAMA(mPNG, mInfo, &gamma);
if (gamma != 1.0) {
png_set_alpha_mode(mPNG, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB);
}
} else {
png_set_gamma_fixed(mPNG, PNG_FP_1, PNG_FP_1);
png_set_alpha_mode(mPNG, PNG_ALPHA_PNG, PNG_GAMMA_LINEAR);
}
if (bitDepth < 16) {
png_set_expand_16(mPNG);
}
png_read_update_info(mPNG, mInfo);
// Read updated color type since we may have asked for a conversion before
colorType = png_get_color_type(mPNG, mInfo);
uint32_t width = png_get_image_width(mPNG, mInfo);
uint32_t height = png_get_image_height(mPNG, mInfo);
size_t rowBytes = png_get_rowbytes(mPNG, mInfo);
imageData = std::make_unique<uint8_t[]>(height * rowBytes);
std::unique_ptr<png_bytep[]> rowPointers(new png_bytep[height]);
for (size_t y = 0 ; y < height ; y++) {
rowPointers[y] = &imageData[y * rowBytes];
}
png_read_image(mPNG, rowPointers.get());
png_read_end(mPNG, mInfo);
if (colorType == PNG_COLOR_TYPE_RGBA) {
if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) {
return toLinearWithAlpha<uint16_t>(width, height, rowBytes, imageData,
[](uint16_t v) -> uint16_t { return ntohs(v); },
sRGBToLinear<filament::math::float4>);
} else {
return toLinearWithAlpha<uint16_t>(width, height, rowBytes, imageData,
[](uint16_t v) -> uint16_t { return ntohs(v); },
[](const filament::math::float4& color) -> filament::math::float4 { return color; });
}
} else {
// Convert to linear float (PNG 16 stores data in network order (big endian).
if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) {
return toLinear<uint16_t>(width, height, rowBytes, imageData,
[](uint16_t v) -> uint16_t { return ntohs(v); },
sRGBToLinear< filament::math::float3>);
} else {
return toLinear<uint16_t>(width, height, rowBytes, imageData,
[](uint16_t v) -> uint16_t { return ntohs(v); },
[](const filament::math::float3& color) -> filament::math::float3 { return color; });
}
}
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while decoding PNG: " << e.what() << std::endl;
mStream.seekg(mStreamStartPos);
imageData.release();
}
return LinearImage();
}
void PNGDecoder::cb_stream(png_structp png, png_bytep buffer, png_size_t size) {
PNGDecoder* that = static_cast<PNGDecoder*>(png_get_io_ptr(png));
that->stream(buffer, size);
}
void PNGDecoder::stream(void* buffer, size_t size) {
mStream.read(static_cast<char *>(buffer), size);
if (!mStream.good()) {
throw std::runtime_error("Problem with the PNG stream.");
}
}
void PNGDecoder::cb_error(png_structp png, png_const_charp) {
PNGDecoder* that = static_cast<PNGDecoder*>(png_get_error_ptr(png));
that->error();
}
void PNGDecoder::error() {
throw std::runtime_error("Error while decoding PNG stream.");
}
// -----------------------------------------------------------------------------------------------
const char PSDDecoder::sig[] = { '8', 'B', 'P', 'S', 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
PSDDecoder* PSDDecoder::create(std::istream& stream) {
PSDDecoder* decoder = new PSDDecoder(stream);
return decoder;
}
bool PSDDecoder::checkSignature(char const* buf) {
return !memcmp(buf, sig, sizeof(sig));
}
PSDDecoder::PSDDecoder(std::istream& stream)
: mStream(stream), mStreamStartPos(stream.tellg()) {
}
PSDDecoder::~PSDDecoder() = default;
LinearImage PSDDecoder::decode() {
#pragma pack(push, 1)
// IMPORTANT NOTE: PSD files use big endian storage
struct Header {
char signature[4];
uint16_t version;
char padding[6];
uint16_t channels;
uint32_t height;
uint32_t width;
uint16_t depth;
uint16_t mode;
};
#pragma pack(pop)
static const uint16_t kColorModeRGB = 3;
static const uint16_t kCompressionRAW = 0;
try {
Header h = { };
mStream.read(reinterpret_cast<char*>(&h), sizeof(Header));
if (ntohs(h.channels) != 3) {
throw std::runtime_error("the image must have 3 channels only");
}
uint16_t depth = ntohs(h.depth);
if (depth != 16 && depth != 32) {
throw std::runtime_error("the image depth must be 16 or 32 bits per pixel");
}
if (ntohs(h.mode) != kColorModeRGB) {
throw std::runtime_error("the image must be RGB");
}
uint32_t width = ntohl(h.width);
uint32_t height = ntohl(h.height);
uint32_t length;
// color mode data section
mStream.read(reinterpret_cast<char*>(&length), sizeof(uint32_t));
mStream.seekg(ntohl(length), std::istream::cur);
// image resources
mStream.read(reinterpret_cast<char*>(&length), sizeof(uint32_t));
mStream.seekg(ntohl(length), std::istream::cur);
// layer and mask info section
mStream.read(reinterpret_cast<char*>(&length), sizeof(uint32_t));
mStream.seekg(ntohl(length), std::istream::cur);
// compression format
uint16_t compression;
mStream.read(reinterpret_cast<char*>(&compression), sizeof(uint16_t));
if (ntohs(compression) != kCompressionRAW) {
throw std::runtime_error("compressed images are not supported");
}
LinearImage image(width, height, 3);
if (depth == 32) {
for (size_t i = 0; i < 3; i++) {
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
filament::math::float3& pixel =
*reinterpret_cast< filament::math::float3*>(image.getPixelRef(x, y));
pixel[i] = read32(mStream);
}
}
}
} else {
for (size_t i = 0; i < 3; i++) {
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
filament::math::float3& pixel =
*reinterpret_cast< filament::math::float3*>(image.getPixelRef(x, y));
pixel[i] = read16(mStream);
}
}
}
}
return image;
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while decoding PSD: " << e.what() << std::endl;
mStream.seekg(mStreamStartPos);
}
return LinearImage();
}
// -----------------------------------------------------------------------------------------------
const char EXRDecoder::sig[] = { 0x76, 0x2f, 0x31, 0x01 };
EXRDecoder* EXRDecoder::create(std::istream& stream, const std::string& sourceName) {
EXRDecoder* decoder = new EXRDecoder(stream, sourceName);
return decoder;
}
bool EXRDecoder::checkSignature(char const* buf) {
return !memcmp(buf, sig, sizeof(sig));
}
EXRDecoder::EXRDecoder(std::istream& stream, const std::string& sourceName)
: mStream(stream), mStreamStartPos(stream.tellg()), mSourceName(sourceName) {
}
EXRDecoder::~EXRDecoder() = default;
LinearImage EXRDecoder::decode() {
try {
// copy the EXR data in memory
std::vector<unsigned char> src;
unsigned char buffer[4096];
while (mStream.read(reinterpret_cast<char*>(buffer), sizeof(buffer))) {
src.insert(src.end(), &buffer[0], &buffer[4096]);
}
src.insert(src.end(), &buffer[0], &buffer[mStream.gcount()]);
int width;
int height;
float* rgba;
const char* error;
int ret = LoadEXRFromMemory(&rgba, &width, &height, src.data(), src.size(), &error);
if (ret != TINYEXR_SUCCESS) {
std::cerr << "Could not decode OpenEXR: " << error << std::endl;
FreeEXRErrorMessage(error);
mStream.seekg(mStreamStartPos);
return LinearImage();
}
src.clear();
src.shrink_to_fit();
LinearImage image((uint32_t) width, (uint32_t) height, 3);
size_t i = 0;
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
filament::math::float3& pixel =
*reinterpret_cast< filament::math::float3*>(image.getPixelRef(x, y));
pixel.r = rgba[i++];
pixel.g = rgba[i++];
pixel.b = rgba[i++];
// skip alpha
i++;
}
}
free(rgba);
return image;
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while decoding OpenEXR: " << e.what() << std::endl;
mStream.seekg(mStreamStartPos);
}
return LinearImage();
}
} // namespace image