Files
filament/libs/imageio/src/ImageEncoder.cpp

1167 lines
40 KiB
C++

/*
* 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/ImageEncoder.h>
#include <algorithm>
#include <cstdint>
#include <cstring> // for memset
#include <functional>
#include <limits>
#include <memory>
#include <iostream> // for cerr
#if defined(WIN32)
#include <Winsock2.h>
#include <utils/unwindows.h>
#else
#include <arpa/inet.h>
#endif
#include <png.h>
#include <tinyexr.h>
#include <math/half.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/compiler.h>
#include <image/ColorTransform.h>
using namespace filament::math;
namespace image {
class PNGEncoder : public ImageEncoder::Encoder {
public:
enum class PixelFormat {
sRGB, // 8-bits sRGB
RGBM, // 8-bits RGBM
LINEAR_RGB, // 8-bits RGB
RGB_10_11_11_REV,
};
static PNGEncoder* create(std::ostream& stream, PixelFormat format = PixelFormat::sRGB);
PNGEncoder(const PNGEncoder&) = delete;
PNGEncoder& operator=(const PNGEncoder&) = delete;
private:
PNGEncoder(std::ostream& stream, PixelFormat format);
~PNGEncoder() override;
void init();
// ImageEncoder::Encoder interface
bool encode(const LinearImage& image) override;
int chooseColorType(const LinearImage& image) const;
uint32_t getChannelsCount(int colorType) const;
static void cb_error(png_structp png, png_const_charp error);
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::ostream& mStream;
std::streampos mStreamStartPos;
PixelFormat mFormat;
};
// ------------------------------------------------------------------------------------------------
class HDREncoder : public ImageEncoder::Encoder {
public:
static HDREncoder* create(std::ostream& stream);
HDREncoder(const HDREncoder&) = delete;
HDREncoder& operator=(const HDREncoder&) = delete;
private:
explicit HDREncoder(std::ostream& stream);
~HDREncoder() override = default;
// ImageEncoder::Encoder interface
bool encode(const LinearImage& image) override;
static void float2rgbe(uint8_t rgbe[4], const float3& in);
static size_t countRepeats(uint8_t const* data, size_t length);
static size_t countNonRepeats(uint8_t const* data, size_t length);
static void rle(std::ostream& out, uint8_t const* data, size_t length);
std::ostream& mStream;
std::streampos mStreamStartPos;
};
// ------------------------------------------------------------------------------------------------
class PSDEncoder : public ImageEncoder::Encoder {
public:
static PSDEncoder* create(std::ostream& stream, const std::string& compression);
PSDEncoder(const PSDEncoder&) = delete;
PSDEncoder& operator=(const PSDEncoder&) = delete;
private:
PSDEncoder(std::ostream& stream, const std::string& compression);
~PSDEncoder() override = default;
// ImageEncoder::Encoder interface
bool encode(const LinearImage& image) override;
std::ostream& mStream;
std::streampos mStreamStartPos;
std::string mCompression;
static const char sig[];
};
// ------------------------------------------------------------------------------------------------
class EXREncoder : public ImageEncoder::Encoder {
public:
static EXREncoder* create(std::ostream& stream, const std::string& compression,
const std::string& destName);
EXREncoder(const EXREncoder&) = delete;
EXREncoder& operator=(const EXREncoder&) = delete;
private:
EXREncoder(std::ostream& stream, const std::string& compression, const std::string& destName);
~EXREncoder() override = default;
// ImageEncoder::Encoder interface
bool encode(const LinearImage& image) override;
std::ostream& mStream;
std::streampos mStreamStartPos;
std::string mDestName;
std::string mCompression;
};
// ------------------------------------------------------------------------------------------------
class DDSEncoder : public ImageEncoder::Encoder {
public:
enum class PixelFormat {
sRGB, // sRGB
LINEAR_RGB, // RGB
};
static DDSEncoder* create(std::ostream& stream, const std::string& compression,
PixelFormat format = PixelFormat::sRGB);
DDSEncoder(const DDSEncoder&) = delete;
DDSEncoder& operator=(const DDSEncoder&) = delete;
private:
DDSEncoder(std::ostream& stream, const std::string& compression, PixelFormat format);
~DDSEncoder() override = default;
// ImageEncoder::Encoder interface
bool encode(const LinearImage& image) override;
std::ostream& mStream;
std::streampos mStreamStartPos;
std::string mCompression;
PixelFormat mFormat;
};
// ------------------------------------------------------------------------------------------------
bool ImageEncoder::encode(std::ostream& stream, Format format, const LinearImage& image,
const std::string& compression, const std::string& destName) {
std::unique_ptr<Encoder> encoder;
switch(format) {
case Format::PNG:
encoder.reset(PNGEncoder::create(stream));
break;
case Format::PNG_LINEAR:
encoder.reset(PNGEncoder::create(stream, PNGEncoder::PixelFormat::LINEAR_RGB));
break;
case Format::RGB_10_11_11_REV:
encoder.reset(PNGEncoder::create(stream, PNGEncoder::PixelFormat::RGB_10_11_11_REV));
break;
case Format::HDR:
encoder.reset(HDREncoder::create(stream));
break;
case Format::RGBM:
encoder.reset(PNGEncoder::create(stream, PNGEncoder::PixelFormat::RGBM));
break;
case Format::PSD:
encoder.reset(PSDEncoder::create(stream, compression));
break;
case Format::EXR:
encoder.reset(EXREncoder::create(stream, compression, destName));
break;
case Format::DDS:
encoder.reset(DDSEncoder::create(stream, compression));
break;
case Format::DDS_LINEAR:
encoder.reset(DDSEncoder::create(stream, compression, DDSEncoder::PixelFormat::LINEAR_RGB));
break;
}
return encoder->encode(image);
}
ImageEncoder::Format ImageEncoder::chooseFormat(const std::string& name, bool forceLinear) {
std::string ext;
size_t index = name.rfind('.');
if (index != std::string::npos && index != 0) {
ext = name.substr(index + 1);
}
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "png") return forceLinear ? Format::PNG_LINEAR : Format::PNG;
if (ext == "rgbm") return Format::PNG;
if (ext == "rgb32f") return Format::RGB_10_11_11_REV;
if (ext == "hdr") return Format::HDR;
if (ext == "psd") return Format::PSD;
if (ext == "exr") return Format::EXR;
if (ext == "dds") return forceLinear ? Format::DDS_LINEAR : Format::DDS;
// PNG by default
return forceLinear ? Format::PNG_LINEAR : Format::PNG;
}
std::string ImageEncoder::chooseExtension(ImageEncoder::Format format) {
switch (format) {
case Format::PNG:
case Format::PNG_LINEAR:
return ".png";
case Format::RGB_10_11_11_REV:
return ".rgb32f";
case Format::RGBM:
return ".rgbm";
case Format::HDR:
return ".hdr";
case Format::PSD:
return ".psd";
case Format::EXR:
return ".exr";
case Format::DDS:
case Format::DDS_LINEAR:
return ".dds";
}
}
//-------------------------------------------------------------------------------------------------
PNGEncoder* PNGEncoder::create(std::ostream& stream, PixelFormat format) {
PNGEncoder* encoder = new PNGEncoder(stream, format);
encoder->init();
return encoder;
}
PNGEncoder::PNGEncoder(std::ostream& stream, PixelFormat format)
: mPNG(png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr)),
mStream(stream), mStreamStartPos(stream.tellp()), mFormat(format) {
}
PNGEncoder::~PNGEncoder() {
png_destroy_write_struct(&mPNG, &mInfo);
}
void PNGEncoder::init() {
png_set_error_fn(mPNG, this, cb_error, nullptr);
png_set_write_fn(mPNG, this, cb_stream, nullptr);
}
int PNGEncoder::chooseColorType(const LinearImage& image) const {
size_t channels = image.getChannels();
switch (channels) {
case 1:
return PNG_COLOR_TYPE_GRAY;
case 3:
switch (mFormat) {
case PixelFormat::RGBM:
case PixelFormat::RGB_10_11_11_REV:
return PNG_COLOR_TYPE_RGBA;
default:
return PNG_COLOR_TYPE_RGB;
}
case 4:
return PNG_COLOR_TYPE_RGBA;
default:
std::cerr << "Warning: strange number of channels in PNG" << std::endl;
return PNG_COLOR_TYPE_RGB;
}
}
uint32_t PNGEncoder::getChannelsCount(int colorType) const {
switch (mFormat) {
case PixelFormat::RGBM:
case PixelFormat::RGB_10_11_11_REV:
return 4;
default:
switch (colorType) {
case PNG_COLOR_TYPE_GRAY: return 1;
case PNG_COLOR_TYPE_RGB: return 3;
case PNG_COLOR_TYPE_RGBA: return 4;
}
return 3;
}
}
bool PNGEncoder::encode(const LinearImage& image) {
size_t srcChannels = image.getChannels();
switch (mFormat) {
case PixelFormat::RGBM:
case PixelFormat::RGB_10_11_11_REV:
if (srcChannels != 3) {
std::cerr << "Cannot encode PNG: " << srcChannels << " channels." << std::endl;
return false;
}
break;
default:
if (srcChannels != 1 && srcChannels != 3 && srcChannels != 4) {
std::cerr << "Cannot encode PNG: " << srcChannels << " channels." << std::endl;
return false;
}
break;
}
try {
mInfo = png_create_info_struct(mPNG);
// Write header (8 bit colour depth)
size_t width = image.getWidth();
size_t height = image.getHeight();
int colorType = chooseColorType(image);
png_set_IHDR(mPNG, mInfo, width, height,
8, colorType, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if (mFormat == PixelFormat::LINEAR_RGB || mFormat == PixelFormat::RGB_10_11_11_REV) {
png_set_gAMA(mPNG, mInfo, 1.0);
} else {
png_set_sRGB_gAMA_and_cHRM(mPNG, mInfo, PNG_sRGB_INTENT_PERCEPTUAL);
}
png_write_info(mPNG, mInfo);
std::unique_ptr<png_bytep[]> row_pointers(new png_bytep[height]);
std::unique_ptr<uint8_t[]> data;
uint32_t dstChannels;
if (srcChannels == 1) {
dstChannels = 1;
data = fromLinearToGrayscale<uint8_t>(image);
} else {
dstChannels = getChannelsCount(colorType);
switch (mFormat) {
case PixelFormat::RGBM:
data = fromLinearToRGBM<uint8_t>(image);
break;
case PixelFormat::RGB_10_11_11_REV:
data = fromLinearToRGB_10_11_11_REV(image);
break;
case PixelFormat::sRGB:
if (dstChannels == 4) {
data = fromLinearTosRGB<uint8_t, 4>(image);
} else {
data = fromLinearTosRGB<uint8_t, 3>(image);
}
break;
case PixelFormat::LINEAR_RGB:
if (dstChannels == 4) {
data = fromLinearToRGB<uint8_t, 4>(image);
} else {
data = fromLinearToRGB<uint8_t, 3>(image);
}
break;
}
}
for (size_t y = 0; y < height; y++) {
row_pointers[y] = reinterpret_cast<png_bytep>
(&data[y * width * dstChannels * sizeof(uint8_t)]);
}
png_write_image(mPNG, row_pointers.get());
png_write_end(mPNG, mInfo);
mStream.flush();
} catch (std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while encoding PNG: " << e.what() << std::endl;
mStream.seekp(mStreamStartPos);
return false;
}
return true;
}
void PNGEncoder::cb_stream(png_structp png, png_bytep buffer, png_size_t size) {
PNGEncoder* that = static_cast<PNGEncoder*>(png_get_io_ptr(png));
that->stream(buffer, size);
}
void PNGEncoder::stream(void* buffer, size_t size) {
mStream.write(static_cast<char *>(buffer), size);
if (!mStream.good()) {
throw std::runtime_error("Problem with the PNG stream.");
}
}
void PNGEncoder::cb_error(png_structp png, png_const_charp) {
PNGEncoder* that = static_cast<PNGEncoder*>(png_get_error_ptr(png));
that->error();
}
void PNGEncoder::error() {
throw std::runtime_error("Error while encoding PNG stream.");
}
//-------------------------------------------------------------------------------------------------
HDREncoder* HDREncoder::create(std::ostream& stream) {
HDREncoder* encoder = new HDREncoder(stream);
return encoder;
}
HDREncoder::HDREncoder(std::ostream& stream)
: mStream(stream), mStreamStartPos(stream.tellp()) {
}
void HDREncoder::float2rgbe(uint8_t rgbe[4], const float3& in) {
int e;
// RGBE can't handle negative floats
float3 color(in);
if (color.r < 0) color.r = 0;
if (color.g < 0) color.g = 0;
if (color.b < 0) color.b = 0;
float v = std::max(color.r, std::max(color.g, color.b));
if (v < 1e-32f) {
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
} else {
v = std::frexp(v, &e) * 256 / v; // m*2^e = v
rgbe[0] = uint8_t(color.r * v);
rgbe[1] = uint8_t(color.g * v);
rgbe[2] = uint8_t(color.b * v);
rgbe[3] = uint8_t(e + 128);
}
}
size_t HDREncoder::countRepeats(uint8_t const* data, size_t length) {
length = std::min(size_t(127), length);
uint8_t v = data[0];
for (size_t i=1 ; i<length ; i++) {
if (data[i] != v) {
return i;
}
}
return length;
}
size_t HDREncoder::countNonRepeats(uint8_t const* data, size_t length) {
length = std::min(size_t(128), length);
size_t same = 1;
uint8_t v = data[0];
for (size_t i=1 ; i<length ; i++) {
if (data[i] == v) {
same++;
if (same >= 3) {
// non-repeats are always at least 3 bytes long
return i;
}
} else {
same = 1;
v = data[i];
}
}
return length;
}
void HDREncoder::rle(std::ostream& out, uint8_t const* data, size_t length) {
uint8_t const* const end = data + length;
while (data < end) {
size_t c = countRepeats(data, end-data);
if (c >= 3) {
out.put((char)(c + 128));
out.put(data[0]);
data += c;
continue;
}
c = countNonRepeats(data, end-data);
out.put((char)c);
out.write((char const*)data , c);
data += c;
}
}
bool HDREncoder::encode(const LinearImage& image) {
if (image.getChannels() != 3) {
return false;
}
try {
// Write header (8 bit color depth)
size_t width = image.getWidth();
size_t height = image.getHeight();
mStream << "#?RADIANCE" << std::endl;
mStream << "# cmgen" << std::endl;
mStream << "FORMAT=32-bit_rle_rgbe" << std::endl;
mStream << "GAMMA=" << std::to_string(1) << std::endl;
mStream << "EXPOSURE=" << std::to_string(0) << std::endl;
mStream << std::endl;
mStream << "-Y " << std::to_string(height) << " "
<< "+X " << std::to_string(width) << std::endl;
// The Radiance format is not expected to use RLE encoding when
// scanlines are less than 8 pixels or more than 32,767 pixels
if (width < 8 || width > 32767) {
for (uint32_t y = 0; y < height; y++) {
uint8_t p[4];
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; ++x, ++data) {
float2rgbe(p, *data);
mStream.write((char*) &p, 4);
}
}
} else {
std::unique_ptr<uint8_t[]> rgbe(new uint8_t[width*4]);
uint8_t* const r = &rgbe[0];
uint8_t* const g = &rgbe[width];
uint8_t* const b = &rgbe[2*width];
uint8_t* const e = &rgbe[3*width];
uint16_t magic = 0x0202;
uint16_t widthNetwork = htons(width);
for (uint32_t y = 0; y < height; y++) {
// convert one scanline to RGBE
uint8_t p[4];
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; ++x, ++data) {
float2rgbe(p, *data);
r[x] = p[0];
g[x] = p[1];
b[x] = p[2];
e[x] = p[3];
}
// now RLE-compress each plane
mStream.write((char*) &magic, 2);
mStream.write((char*) &widthNetwork, 2);
rle(mStream, r, width);
rle(mStream, g, width);
rle(mStream, b, width);
rle(mStream, e, width);
}
}
mStream.flush();
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while encoding HDR: " << e.what() << std::endl;
mStream.seekp(mStreamStartPos);
return false;
}
return true;
}
//-------------------------------------------------------------------------------------------------
const char PSDEncoder::sig[] = { '8', 'B', 'P', 'S', 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
PSDEncoder* PSDEncoder::create(std::ostream& stream, const std::string& compression) {
PSDEncoder* encoder = new PSDEncoder(stream, compression);
return encoder;
}
PSDEncoder::PSDEncoder(std::ostream& stream, const std::string& compression)
: mStream(stream), mStreamStartPos(stream.tellp()), mCompression(compression) {
}
static inline void write32(std::ostream& stream, float f) {
uint32_t data = htonl(*reinterpret_cast<uint32_t*>(&f));
stream.write(reinterpret_cast<char*>(&data), sizeof(uint32_t));
}
static inline void write16(std::ostream& stream, float f) {
uint16_t data = htons(static_cast<uint16_t>(std::min(std::max(0.0f, f), 1.0f) * 65535.0f));
stream.write(reinterpret_cast<char*>(&data), sizeof(uint16_t));
}
static inline void write32i(std::ostream& stream, uint32_t v) {
uint32_t word = htonl(v);
stream.write(reinterpret_cast<char*>(&word), sizeof(uint32_t));
}
static inline void write16i(std::ostream& stream, uint16_t v) {
uint16_t word = htons(v);
stream.write(reinterpret_cast<char*>(&word), sizeof(uint16_t));
}
static inline void write8i(std::ostream& stream, uint8_t v) {
stream.write(reinterpret_cast<const char*>(&v), sizeof(uint8_t));
}
bool PSDEncoder::encode(const LinearImage& image) {
static const uint16_t kColorModeRGB = 3;
static const uint16_t kCompressionRAW = 0;
// preview mode: 0 = highlight compression, 1 = exposure & gamma
static const uint32_t kToningPreviewExposureGamma = 1;
if (image.getChannels() != 3) {
return false;
}
try {
size_t width = image.getWidth();
size_t height = image.getHeight();
uint16_t depth = mCompression == "32" ?
static_cast<uint16_t>(32) : static_cast<uint16_t>(16);
mStream.write(sig, sizeof(sig));
write16i(mStream, 3); // channels
write32i(mStream, static_cast<uint32_t>(height));
write32i(mStream, static_cast<uint32_t>(width));
write16i(mStream, depth);
write16i(mStream, kColorModeRGB);
// color mode data section
// 32 bits images need a lot of magic HDR toning information
// this information is undocumented so we simply use what seems
// to be Photoshop's default toning data (as of Photoshop CC 2015)
if (depth == 16) {
write32i(mStream, 0);
} else {
write32i(mStream, 112);
mStream.write("hdrt", 4);
write32i(mStream, 3); // version?
write32(mStream, 0.23f); // edge glow strength
write32i(mStream, 2); // ??
write32i(mStream, 8);
const uint8_t presetName[] = {
// "Default" in UTF-16, null-terminated
0x00, 0x44, 0x00, 0x65, 0x00, 0x66, 0x00, 0x61,
0x00, 0x75, 0x00, 0x6C, 0x00, 0x74, 0x00, 0x00
};
mStream.write(reinterpret_cast<const char*>(presetName), sizeof(presetName));
// toning curve
write16i(mStream, 2); // ??
write16i(mStream, 2); // number of points
// point 1
write16i(mStream, 0); // input
write16i(mStream, 0); // output
// point 2
write16i(mStream, 255); // input
write16i(mStream, 255); // output
// corners (0 = corner, 1 = not a corner)
write8i(mStream, 1); // point 1
write8i(mStream, 1); // point 2
write32i(mStream, 0); // ??
write32i(mStream, 0); // ??
write32(mStream, 16.0f); // edge glow radius
write32i(mStream, kToningPreviewExposureGamma); // preview mode
write32(mStream, 0.0f); // exposure
write32(mStream, 1.0f); // gamma
mStream.write("hdra", 4);
write32i(mStream, 6); // version?
write32(mStream, 0.0f); // exposure
write32(mStream, 20.0f); // saturation, in %
write32(mStream, 30.0f); // detail, in %
write32(mStream, 0.0f); // shadow, in %
write32(mStream, 0.0f); // highlight, in %
write32(mStream, 1.0f); // gamma
write32(mStream, 0.0f); // vibrance
write16i(mStream, 0); // flags, 0x0 = smooth edges off
}
// image resources section
write32i(mStream, 0);
// layer and mask info section
write32i(mStream, 0);
// compression format
write16i(mStream, kCompressionRAW);
if (depth == 32) {
for (size_t channel = 0; channel < 3; channel++) {
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
write32(mStream, (*data)[channel]);
data++;
}
}
}
} else {
for (size_t channel = 0; channel < 3; channel++) {
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
write16(mStream, linearTosRGB((*data)[channel]));
data++;
}
}
}
}
mStream.flush();
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while encoding PSD: " << e.what() << std::endl;
mStream.seekp(mStreamStartPos);
return false;
}
return true;
}
//-------------------------------------------------------------------------------------------------
EXREncoder* EXREncoder::create(std::ostream& stream, const std::string& compression,
const std::string& destName) {
EXREncoder* encoder = new EXREncoder(stream, compression, destName);
return encoder;
}
EXREncoder::EXREncoder(std::ostream& stream, const std::string& compression,
const std::string& destName)
: mStream(stream), mStreamStartPos(stream.tellp()),
mDestName(destName), mCompression(compression) {
}
static int toEXRCompression(const std::string& c) {
if (c.empty()) {
return TINYEXR_COMPRESSIONTYPE_PIZ;
} else if (c == "RAW") {
return TINYEXR_COMPRESSIONTYPE_NONE;
} else if (c == "RLE") {
return TINYEXR_COMPRESSIONTYPE_ZIPS;
} else if (c == "ZIPS") {
return TINYEXR_COMPRESSIONTYPE_ZIPS;
} else if (c == "ZIP") {
return TINYEXR_COMPRESSIONTYPE_ZIP;
} else if (c == "PIZ") {
return TINYEXR_COMPRESSIONTYPE_PIZ;
}
throw std::runtime_error("unknown compression scheme " + c);
}
bool EXREncoder::encode(const LinearImage& image) {
if (image.getChannels() != 3) {
return false;
}
try {
EXRHeader header;
InitEXRHeader(&header);
EXRImage exrImage;
InitEXRImage(&exrImage);
size_t width = image.getWidth();
size_t height = image.getHeight();
exrImage.num_channels = 3;
exrImage.width = static_cast<int>(width);
exrImage.height = static_cast<int>(height);
std::unique_ptr<float[]> r(new float[width * height]);
std::unique_ptr<float[]> g(new float[width * height]);
std::unique_ptr<float[]> b(new float[width * height]);
size_t i = 0;
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++, data++) {
r[i] = data->r;
g[i] = data->g;
b[i] = data->b;
i++;
}
}
float* imageData[3];
imageData[0] = &b[0];
imageData[1] = &g[0];
imageData[2] = &r[0];
exrImage.images = (unsigned char**) imageData;
header.num_channels = 3;
header.compression_type = toEXRCompression(mCompression);
header.channels = (EXRChannelInfo*) malloc(sizeof(EXRChannelInfo) * header.num_channels);
header.channels[0].name[0] = 'B';
header.channels[0].name[1] = '\0';
header.channels[1].name[0] = 'G';
header.channels[1].name[1] = '\0';
header.channels[2].name[0] = 'R';
header.channels[2].name[1] = '\0';
header.pixel_types = (int*) malloc(sizeof(int) * header.num_channels);
header.requested_pixel_types = (int*) malloc(sizeof(int) * header.num_channels);
for (i = 0; i < header.num_channels; i++) {
header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_HALF;
}
unsigned char* outData;
const char* error;
size_t size = SaveEXRImageToMemory(&exrImage, &header, &outData, &error);
if (size > 0 && outData) {
mStream.write(reinterpret_cast<char*>(outData), size);
free(outData);
} else {
std::cerr << "Runtime error while encoding EXR: " << error << std::endl;
mStream.seekp(mStreamStartPos);
}
free(header.channels);
free(header.pixel_types);
free(header.requested_pixel_types);
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while encoding EXR: " << e.what() << std::endl;
mStream.seekp(mStreamStartPos);
return false;
}
return true;
}
//-------------------------------------------------------------------------------------------------
const uint32_t DDS_MAGIC = 0x20534444; // "DDS"
const uint32_t DDS_FOURCC_DX10 = 0x30315844; // "DX10"
#pragma pack(push, 1)
struct DDS_PIXELFORMAT {
uint32_t UTILS_UNUSED dwSize;
uint32_t dwFlags;
uint32_t UTILS_UNUSED dwFourCC;
uint32_t UTILS_UNUSED dwRGBBitCount;
uint32_t UTILS_UNUSED dwRBitMask;
uint32_t UTILS_UNUSED dwGBitMask;
uint32_t UTILS_UNUSED dwBBitMask;
uint32_t UTILS_UNUSED dwABitMask;
};
struct DDS_HEADER {
uint32_t UTILS_UNUSED dwSize;
uint32_t dwFlags;
uint32_t UTILS_UNUSED dwHeight;
uint32_t UTILS_UNUSED dwWidth;
uint32_t UTILS_UNUSED dwPitchOrLinearSize;
uint32_t UTILS_UNUSED dwDepth;
uint32_t UTILS_UNUSED dwMipMapCount;
uint32_t UTILS_UNUSED dwReserved1[11];
DDS_PIXELFORMAT UTILS_UNUSED ddspf;
uint32_t UTILS_UNUSED dwCaps;
uint32_t UTILS_UNUSED dwCaps2;
uint32_t UTILS_UNUSED dwCaps3;
uint32_t UTILS_UNUSED dwCaps4;
uint32_t UTILS_UNUSED dwReserved2;
};
struct DDS_HEADER_DXT10 {
uint32_t dxgiFormat;
uint32_t UTILS_UNUSED resourceDimension;
uint32_t UTILS_UNUSED miscFlag;
uint32_t UTILS_UNUSED arraySize;
uint32_t UTILS_UNUSED miscFlags2;
};
#pragma pack(pop)
#define DDSD_CAPS 0x1
#define DDSD_HEIGHT 0x2
#define DDSD_WIDTH 0x4
#define DDSD_PITCH 0x8
#define DDSD_PIXELFORMAT 0x1000
#define DDSCAPS_TEXTURE 0x1000
#define DDPF_FOURCC 0x4
#define DDS_RESOURCE_DIMENSION_TEXTURE2D 0x3
#define DXGI_FORMAT_R32G32B32A32_FLOAT 2
#define DXGI_FORMAT_R16G16B16A16_FLOAT 10
#define DXGI_FORMAT_R32G32_FLOAT 16
#define DXGI_FORMAT_R8G8B8A8_UINT 30
#define DXGI_FORMAT_R16G16_FLOAT 34
#define DXGI_FORMAT_R32_FLOAT 41
#define DXGI_FORMAT_R8G8_UINT 50
#define DXGI_FORMAT_R16_FLOAT 54
#define DXGI_FORMAT_R8_UINT 62
DDSEncoder* DDSEncoder::create(std::ostream& stream, const std::string& compression,
PixelFormat format) {
DDSEncoder* encoder = new DDSEncoder(stream, compression, format);
return encoder;
}
DDSEncoder::DDSEncoder(std::ostream& stream, const std::string& compression, PixelFormat format)
: mStream(stream), mStreamStartPos(stream.tellp()),
mCompression(compression), mFormat(format) {
if (format == PixelFormat::sRGB) {
if (compression != "8") mFormat = PixelFormat::LINEAR_RGB;
}
}
static uint32_t chooseBpp(const LinearImage& image, const std::string& compression) {
size_t depth = 16;
if (compression == "8") depth = 8;
if (compression == "32") depth = 32;
size_t index = static_cast<size_t>(std::log2(depth)) - 3;
static const uint32_t formats[3][3] = {
{ 1, 2, 4 },
{ 2, 4, 8 },
{ 4, 8, 16 },
};
return formats[image.getChannels() - 1][index];
}
static uint32_t chooseDXGIFormat(const LinearImage& image, const std::string& compression) {
size_t depth = 16;
if (compression == "8") depth = 8;
if (compression == "32") depth = 32;
size_t index = static_cast<size_t>(std::log2(depth)) - 3;
static const uint32_t formats[3][3] = {
{ DXGI_FORMAT_R8_UINT, DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_R32_FLOAT },
{ DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_R32G32_FLOAT },
{ DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT },
};
return formats[image.getChannels() - 1][index];
}
bool DDSEncoder::encode(const LinearImage& image) {
try {
size_t width = image.getWidth();
size_t height = image.getHeight();
DDS_PIXELFORMAT ddspf = { };
ddspf.dwSize = sizeof(ddspf);
ddspf.dwFlags = DDPF_FOURCC;
ddspf.dwFourCC = DDS_FOURCC_DX10;
DDS_HEADER header = { };
header.dwSize = sizeof(header);
header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |
DDSD_PIXELFORMAT | DDSD_PITCH;
header.dwHeight = static_cast<uint32_t>(height);
header.dwWidth = static_cast<uint32_t>(width);
header.dwDepth = 1;
header.dwMipMapCount = 1;
header.dwPitchOrLinearSize = static_cast<uint32_t>(width * chooseBpp(image, mCompression));
header.ddspf = ddspf;
header.dwCaps = DDSCAPS_TEXTURE;
DDS_HEADER_DXT10 headerDX10 = { };
headerDX10.dxgiFormat = chooseDXGIFormat(image, mCompression);
headerDX10.resourceDimension = DDS_RESOURCE_DIMENSION_TEXTURE2D;
headerDX10.arraySize = 1;
mStream.write((const char*) &DDS_MAGIC, sizeof(DDS_MAGIC));
mStream.write((const char*) &header, sizeof(header));
mStream.write((const char*) &headerDX10, sizeof(headerDX10));
switch (headerDX10.dxgiFormat) {
case DXGI_FORMAT_R8_UINT: {
switch (mFormat) {
case PixelFormat::sRGB:
for (uint32_t y = 0; y < height; y++) {
const float* data = image.getPixelRef(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t b = (uint8_t) (linearTosRGB(saturate(*data)) * 255);
mStream.write((const char*) &b, 1);
data++;
}
}
break;
case PixelFormat::LINEAR_RGB:
for (uint32_t y = 0; y < height; y++) {
const float* data = image.getPixelRef(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t b = (uint8_t) (saturate(*data) * 255);
mStream.write((const char*) &b, 1);
data++;
}
}
break;
}
break;
}
case DXGI_FORMAT_R16_FLOAT: {
for (uint32_t y = 0; y < height; y++) {
const float* data = image.getPixelRef(0, y);
for (size_t x = 0; x < width; x++) {
half p = half(*data);
mStream.write((const char*) &p, 2);
data++;
}
}
break;
}
case DXGI_FORMAT_R32_FLOAT: {
for (uint32_t y = 0; y < height; y++) {
const float* data = image.getPixelRef(0, y);
mStream.write((const char*) data, width * sizeof(float));
}
break;
}
case DXGI_FORMAT_R8G8_UINT: {
switch (mFormat) {
case PixelFormat::sRGB:
for (uint32_t y = 0; y < height; y++) {
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
for (size_t x = 0; x < width; x++) {
uint8_t b;
b = (uint8_t) (linearTosRGB(saturate(data->g)) * 255);
mStream.write((const char*) &b, 1);
b = (uint8_t) (linearTosRGB(saturate(data->r)) * 255);
mStream.write((const char*) &b, 1);
data++;
}
}
break;
case PixelFormat::LINEAR_RGB:
for (uint32_t y = 0; y < height; y++) {
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
for (size_t x = 0; x < width; x++) {
uint8_t b;
b = (uint8_t) (saturate(data->g) * 255);
mStream.write((const char*) &b, 1);
b = (uint8_t) (saturate(data->r) * 255);
mStream.write((const char*) &b, 1);
data++;
}
}
break;
}
break;
}
case DXGI_FORMAT_R16G16_FLOAT: {
for (uint32_t y = 0; y < height; y++) {
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
for (size_t x = 0; x < width; x++) {
half2 p = half2(*data);
mStream.write((const char*) &p, sizeof(p));
data++;
}
}
break;
}
case DXGI_FORMAT_R32G32_FLOAT: {
for (uint32_t y = 0; y < height; y++) {
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
mStream.write((const char*) data, width * sizeof(float2));
}
break;
}
case DXGI_FORMAT_R8G8B8A8_UINT: {
switch (mFormat) {
case PixelFormat::sRGB:
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t r = (uint8_t) (linearTosRGB(saturate(data->r)) * 255);
uint8_t g = (uint8_t) (linearTosRGB(saturate(data->g)) * 255);
uint8_t b = (uint8_t) (linearTosRGB(saturate(data->b)) * 255);
uint32_t p = (uint8_t) 0xff << 24 | b << 16 | g << 8 | r;
mStream.write((const char*) &p, 4);
data++;
}
}
break;
case PixelFormat::LINEAR_RGB:
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t r = (uint8_t) (saturate(data->r) * 255);
uint8_t g = (uint8_t) (saturate(data->g) * 255);
uint8_t b = (uint8_t) (saturate(data->b) * 255);
uint32_t p = (uint8_t) 0xff << 24 | b << 16 | g << 8 | r;
mStream.write((const char*) &p, 4);
data++;
}
}
break;
}
break;
}
case DXGI_FORMAT_R16G16B16A16_FLOAT: {
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
half4 p = half4(half3(*data), 1);
mStream.write((const char*) &p, sizeof(ushort4));
data++;
}
}
break;
}
case DXGI_FORMAT_R32G32B32A32_FLOAT: {
for (uint32_t y = 0; y < height; y++) {
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
float4 p = float4(3.0f, 3.0f, 3.0f, 1.0f);
mStream.write((const char*) &p, sizeof(float4));
data++;
}
}
break;
}
default:
break;
}
mStream.flush();
} catch(std::runtime_error& e) {
// reset the stream, like we found it
std::cerr << "Runtime error while encoding PSD: " << e.what() << std::endl;
mStream.seekp(mStreamStartPos);
return false;
}
return true;
}
} // namespace image