OpenEXR I/O

This commit is contained in:
shun-iwasawa 2021-12-17 09:33:46 +09:00 committed by manongjohn
parent 5ba47b0238
commit 594093c66c
8 changed files with 9562 additions and 1 deletions

View file

@ -0,0 +1,62 @@
tinyexr : Tiny OpenEXR image loader/saver library
https://github.com/syoyo/tinyexr
Copyright (c) 2014 - 2021, Syoyo Fujita and many contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Syoyo Fujita nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///////////////////////////////////////////////////////////////////////////
tinyexr contains some OpenEXR code, which is licensed as follows.
Note that OpenEXR had joined Academy Software Foudation projects in 2019.
The latest code of OpenEXR can be found in
https://github.com/AcademySoftwareFoundation/openexr .
///////////////////////////////////////////////////////////////////////////
OpenEXR
Copyright (c) 2002, Industrial Light & Magic, a division of Lucas Digital Ltd. LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Industrial Light & Magic nor the names of
its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

8530
thirdparty/tinyexr/tinyexr.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -420,7 +420,9 @@ TImageP TImageReader::load0() {
// assert(info.m_samplePerPixel == 3 || info.m_samplePerPixel == 4);
TRasterP _ras;
if (info.m_bitsPerSample == 16) {
// currently m_bitsPerSample == 32 is only possible when loading
// full-float / uint EXR images
if (info.m_bitsPerSample == 16 || info.m_bitsPerSample == 32) {
if (m_is64BitEnabled || m_path.getType() != "tif") {
// Standard 64-bit case.

View file

@ -21,6 +21,8 @@ set(HEADERS
ffmpeg/tiio_ffmpeg.h
sprite/tiio_sprite.h
mesh/tiio_mesh.h
exr/tinyexr_otmod.h
exr/tiio_exr.h
)
set(SOURCES
@ -47,6 +49,7 @@ set(SOURCES
ffmpeg/tiio_ffmpeg.cpp
sprite/tiio_sprite.cpp
mesh/tiio_mesh.cpp
exr/tiio_exr.cpp
)
@ -121,6 +124,10 @@ if(BUILD_TARGET_WIN AND PLATFORM EQUAL 32)
)
endif()
include_directories(
${SDKROOT}/tinyexr
)
_find_toonz_library(TNZLIBS "tnzcore;tnzbase;toonzlib")

View file

@ -0,0 +1,426 @@
#define TINYEXR_USE_MINIZ 0
#include "zlib.h"
#define TINYEXR_OTMOD_IMPLEMENTATION
#include "tinyexr_otmod.h"
#include "tiio_exr.h"
#include "tpixel.h"
#include <QMap>
namespace {
inline unsigned char ftouc(float f, float gamma = 2.2f) {
int i = static_cast<int>(255.0f * powf(f, 1.0f / gamma));
if (i > 255) i = 255;
if (i < 0) i = 0;
return static_cast<unsigned char>(i);
}
inline float uctof(unsigned char uc, float gamma = 2.2f) {
return powf(static_cast<float>(uc) / 255.0f, gamma);
}
inline unsigned short ftous(float f, float gamma = 2.2f) {
int i = static_cast<int>(65535.0f * powf(f, 1.0f / gamma));
if (i > 65535) i = 65535;
if (i < 0) i = 0;
return static_cast<unsigned short>(i);
}
inline float ustof(unsigned short us, float gamma = 2.2f) {
return powf(static_cast<float>(us) / 65535.0f, gamma);
}
const QMap<int, std::wstring> ExrCompTypeStr = {
{TINYEXR_COMPRESSIONTYPE_NONE, L"None"},
{TINYEXR_COMPRESSIONTYPE_RLE, L"RLE"},
{TINYEXR_COMPRESSIONTYPE_ZIPS, L"ZIPS"},
{TINYEXR_COMPRESSIONTYPE_ZIP, L"ZIP"},
{TINYEXR_COMPRESSIONTYPE_PIZ, L"PIZ"}};
const std::wstring EXR_STORAGETYPE_SCANLINE = L"Store Image as Scanlines";
const std::wstring EXR_STORAGETYPE_TILE = L"Store Image as Tiles";
} // namespace
//**************************************************************************
// ExrReader implementation
//**************************************************************************
class ExrReader final : public Tiio::Reader {
float* m_rgbaBuf;
int m_row;
EXRHeader* m_exr_header;
FILE* m_fp;
public:
ExrReader();
~ExrReader();
void open(FILE* file) override;
Tiio::RowOrder getRowOrder() const override;
bool read16BitIsEnabled() const override;
int skipLines(int lineCount) override;
;
void readLine(char* buffer, int x0, int x1, int shrink) override;
void readLine(short* buffer, int x0, int x1, int shrink) override;
void loadImage();
};
ExrReader::ExrReader() : m_rgbaBuf(nullptr), m_row(0), m_exr_header(nullptr) {}
ExrReader::~ExrReader() {
if (m_rgbaBuf) free(m_rgbaBuf);
if (m_exr_header) FreeEXRHeader(m_exr_header);
}
void ExrReader::open(FILE* file) {
m_fp = file;
m_exr_header = new EXRHeader();
const char* err;
{
int ret = LoadEXRHeaderFromFileHandle(*m_exr_header, file, &err);
if (ret != 0) {
m_exr_header = nullptr;
throw(std::string(err));
}
}
m_info.m_lx =
m_exr_header->data_window.max_x - m_exr_header->data_window.min_x + 1;
m_info.m_ly =
m_exr_header->data_window.max_y - m_exr_header->data_window.min_y + 1;
m_info.m_samplePerPixel = m_exr_header->num_channels;
int bps = 16;
switch (m_exr_header->pixel_types[0]) {
case TINYEXR_PIXELTYPE_UINT:
case TINYEXR_PIXELTYPE_FLOAT:
bps = 32;
break;
case TINYEXR_PIXELTYPE_HALF:
bps = 16;
break;
}
m_info.m_bitsPerSample = bps;
}
Tiio::RowOrder ExrReader::getRowOrder() const { return Tiio::TOP2BOTTOM; }
bool ExrReader::read16BitIsEnabled() const { return true; }
int ExrReader::skipLines(int lineCount) {
m_row += lineCount;
return lineCount;
}
void ExrReader::loadImage() {
assert(!m_rgbaBuf);
const char* err;
{
int ret =
LoadEXRImageBufFromFileHandle(&m_rgbaBuf, *m_exr_header, m_fp, &err);
if (ret != 0) {
m_exr_header = nullptr;
throw(std::string(err));
}
}
// header memory is freed after loading image
m_exr_header = nullptr;
}
void ExrReader::readLine(char* buffer, int x0, int x1, int shrink) {
const int pixelSize = 4;
if (m_row < 0 || m_row >= m_info.m_ly) {
memset(buffer, 0, (x1 - x0 + 1) * pixelSize);
m_row++;
return;
}
if (!m_rgbaBuf) loadImage();
TPixel32* pix = (TPixel32*)buffer;
float* v = m_rgbaBuf + m_row * m_info.m_lx * 4;
pix += x0;
v += x0 * 4;
int width =
(x1 < x0) ? (m_info.m_lx - 1) / shrink + 1 : (x1 - x0) / shrink + 1;
for (int i = 0; i < width; i++) {
pix->r = ftouc(v[0]);
pix->g = ftouc(v[1]);
pix->b = ftouc(v[2]);
pix->m = ftouc(v[3]);
v += shrink * 4;
pix += shrink;
}
m_row++;
}
void ExrReader::readLine(short* buffer, int x0, int x1, int shrink) {
const int pixelSize = 8;
if (m_row < 0 || m_row >= m_info.m_ly) {
memset(buffer, 0, (x1 - x0 + 1) * pixelSize);
m_row++;
return;
}
if (!m_rgbaBuf) loadImage();
TPixel64* pix = (TPixel64*)buffer;
float* v = m_rgbaBuf + m_row * m_info.m_lx * 4;
pix += x0;
v += x0 * 4;
int width =
(x1 < x0) ? (m_info.m_lx - 1) / shrink + 1 : (x1 - x0) / shrink + 1;
for (int i = 0; i < width; i++) {
pix->r = ftous(v[0]);
pix->g = ftous(v[1]);
pix->b = ftous(v[2]);
pix->m = ftous(v[3], 1.0f);
v += shrink * 4;
pix += shrink;
}
m_row++;
}
//============================================================
Tiio::ExrWriterProperties::ExrWriterProperties()
: m_compressionType("Compression Type")
, m_storageType("Storage Type")
, m_bitsPerPixel("Bits Per Pixel") {
m_bitsPerPixel.addValue(L"48(RGB)");
m_bitsPerPixel.addValue(L"64(RGBA)");
m_bitsPerPixel.setValue(L"64(RGBA)");
m_compressionType.addValue(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_NONE));
m_compressionType.addValue(ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_RLE));
m_compressionType.addValue(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_ZIPS));
m_compressionType.addValue(ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_ZIP));
m_compressionType.addValue(ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_PIZ));
m_compressionType.setValue(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_NONE));
m_storageType.addValue(EXR_STORAGETYPE_SCANLINE);
m_storageType.addValue(EXR_STORAGETYPE_TILE);
m_storageType.setValue(EXR_STORAGETYPE_SCANLINE);
bind(m_bitsPerPixel);
bind(m_compressionType);
bind(m_storageType);
}
void Tiio::ExrWriterProperties::updateTranslation() {
m_bitsPerPixel.setQStringName(tr("Bits Per Pixel"));
m_bitsPerPixel.setItemUIName(L"48(RGB)", tr("48(RGB Half Float)"));
m_bitsPerPixel.setItemUIName(L"64(RGBA)", tr("64(RGBA Half Float)"));
m_compressionType.setQStringName(tr("Compression Type"));
m_compressionType.setItemUIName(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_NONE), tr("No compression"));
m_compressionType.setItemUIName(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_RLE),
tr("Run Length Encoding (RLE)"));
m_compressionType.setItemUIName(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_ZIPS),
tr("ZIP compression per Scanline (ZIPS)"));
m_compressionType.setItemUIName(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_ZIP),
tr("ZIP compression per scanline band (ZIP)"));
m_compressionType.setItemUIName(
ExrCompTypeStr.value(TINYEXR_COMPRESSIONTYPE_PIZ),
tr("PIZ-based wavelet compression (PIZ)"));
m_storageType.setQStringName(tr("Storage Type"));
m_storageType.setItemUIName(EXR_STORAGETYPE_SCANLINE, tr("Scan-line based"));
m_storageType.setItemUIName(EXR_STORAGETYPE_TILE, tr("Tile based"));
}
//============================================================
class ExrWriter final : public Tiio::Writer {
std::vector<float> m_imageBuf[4];
EXRHeader m_header;
EXRImage m_image;
int m_row;
FILE* m_fp;
int m_bpp;
public:
ExrWriter();
~ExrWriter();
void open(FILE* file, const TImageInfo& info) override;
void writeLine(char* buffer) override;
void writeLine(short* buffer) override;
void flush() override;
Tiio::RowOrder getRowOrder() const override { return Tiio::TOP2BOTTOM; }
// m_bpp is set to "Bits Per Pixel" property value in the function open()
bool writeAlphaSupported() const override { return m_bpp == 64; }
};
ExrWriter::ExrWriter() : m_row(0), m_bpp(64) {}
ExrWriter::~ExrWriter() {
free(m_header.channels);
free(m_header.pixel_types);
free(m_header.requested_pixel_types);
}
void ExrWriter::open(FILE* file, const TImageInfo& info) {
m_fp = file;
m_info = info;
InitEXRHeader(&m_header);
InitEXRImage(&m_image);
if (!m_properties) m_properties = new Tiio::ExrWriterProperties();
TEnumProperty* bitsPerPixel =
(TEnumProperty*)(m_properties->getProperty("Bits Per Pixel"));
m_bpp = bitsPerPixel ? std::stoi(bitsPerPixel->getValue()) : 64;
assert(m_bpp == 48 || m_bpp == 64);
std::wstring compressionType =
((TEnumProperty*)(m_properties->getProperty("Compression Type")))
->getValue();
m_header.compression_type = ExrCompTypeStr.key(compressionType);
std::wstring storageType =
((TEnumProperty*)(m_properties->getProperty("Storage Type")))->getValue();
if (storageType == EXR_STORAGETYPE_TILE) {
m_header.tiled = 1;
m_header.tile_size_x = 128;
m_header.tile_size_y = 128;
m_header.tile_level_mode = TINYEXR_TILE_ONE_LEVEL;
} else
m_header.tiled = 0;
m_image.num_channels = (m_bpp == 64) ? 4 : 3;
for (int c = 0; c < m_image.num_channels; c++)
m_imageBuf[c].resize(m_info.m_lx * m_info.m_ly);
m_image.width = m_info.m_lx;
m_image.height = m_info.m_ly;
m_header.num_channels = m_image.num_channels;
m_header.channels =
(EXRChannelInfo*)malloc(sizeof(EXRChannelInfo) * m_header.num_channels);
// Must be BGR(A) order, since most of EXR viewers expect this channel order.
if (m_bpp == 64) {
strncpy(m_header.channels[0].name, "A", 255);
m_header.channels[0].name[strlen("A")] = '\0';
strncpy(m_header.channels[1].name, "B", 255);
m_header.channels[1].name[strlen("B")] = '\0';
strncpy(m_header.channels[2].name, "G", 255);
m_header.channels[2].name[strlen("G")] = '\0';
strncpy(m_header.channels[3].name, "R", 255);
m_header.channels[3].name[strlen("R")] = '\0';
} else {
strncpy(m_header.channels[0].name, "B", 255);
m_header.channels[0].name[strlen("B")] = '\0';
strncpy(m_header.channels[1].name, "G", 255);
m_header.channels[1].name[strlen("G")] = '\0';
strncpy(m_header.channels[2].name, "R", 255);
m_header.channels[2].name[strlen("R")] = '\0';
}
m_header.pixel_types = (int*)malloc(sizeof(int) * m_header.num_channels);
m_header.requested_pixel_types =
(int*)malloc(sizeof(int) * m_header.num_channels);
for (int i = 0; i < m_header.num_channels; i++) {
m_header.pixel_types[i] =
TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
m_header.requested_pixel_types[i] =
TINYEXR_PIXELTYPE_HALF; // pixel type of output image to be stored in
// .EXR
}
}
void ExrWriter::writeLine(char* buffer) {
TPixel32* pix = (TPixel32*)buffer;
TPixel32* endPix = pix + m_info.m_lx;
float* r_p = &m_imageBuf[0][m_row * m_info.m_lx];
float* g_p = &m_imageBuf[1][m_row * m_info.m_lx];
float* b_p = &m_imageBuf[2][m_row * m_info.m_lx];
float* a_p;
if (m_bpp == 64) a_p = &m_imageBuf[3][m_row * m_info.m_lx];
while (pix < endPix) {
*r_p++ = uctof(pix->r);
*g_p++ = uctof(pix->g);
*b_p++ = uctof(pix->b);
if (m_bpp == 64) *a_p++ = uctof(pix->m, 1.0f);
pix++;
}
m_row++;
}
void ExrWriter::writeLine(short* buffer) {
TPixel64* pix = (TPixel64*)buffer;
TPixel64* endPix = pix + m_info.m_lx;
float* r_p = &m_imageBuf[0][m_row * m_info.m_lx];
float* g_p = &m_imageBuf[1][m_row * m_info.m_lx];
float* b_p = &m_imageBuf[2][m_row * m_info.m_lx];
float* a_p;
if (m_bpp == 64) a_p = &m_imageBuf[3][m_row * m_info.m_lx];
while (pix < endPix) {
*r_p++ = ustof(pix->r);
*g_p++ = ustof(pix->g);
*b_p++ = ustof(pix->b);
if (m_bpp == 64) *a_p++ = ustof(pix->m, 1.0f);
pix++;
}
m_row++;
}
void ExrWriter::flush() {
if (m_bpp == 64) {
float* image_ptr[4];
image_ptr[0] = &(m_imageBuf[3].at(0)); // B
image_ptr[1] = &(m_imageBuf[2].at(0)); // G
image_ptr[2] = &(m_imageBuf[1].at(0)); // R
image_ptr[3] = &(m_imageBuf[0].at(0)); // A
m_image.images = (unsigned char**)image_ptr;
const char* err;
int ret = SaveEXRImageToFileHandle(&m_image, &m_header, m_fp, &err);
if (ret != TINYEXR_SUCCESS) {
throw(std::string(err));
}
} else {
float* image_ptr[3];
image_ptr[0] = &(m_imageBuf[2].at(0)); // B
image_ptr[1] = &(m_imageBuf[1].at(0)); // G
image_ptr[2] = &(m_imageBuf[0].at(0)); // R
m_image.images = (unsigned char**)image_ptr;
const char* err;
int ret = SaveEXRImageToFileHandle(&m_image, &m_header, m_fp, &err);
if (ret != TINYEXR_SUCCESS) {
throw(std::string(err));
}
}
}
//============================================================
Tiio::Reader* Tiio::makeExrReader() { return new ExrReader(); }
//------------------------------------------------------------
Tiio::Writer* Tiio::makeExrWriter() { return new ExrWriter(); }

View file

@ -0,0 +1,32 @@
#pragma once
#ifndef TTIO_EXR_INCLUDED
#define TTIO_EXR_INCLUDED
#include "tiio.h"
#include "tproperty.h"
#include <QCoreApplication>
namespace Tiio {
//===========================================================================
class ExrWriterProperties final : public TPropertyGroup {
Q_DECLARE_TR_FUNCTIONS(ExrWriterProperties)
public:
TEnumProperty m_compressionType;
TEnumProperty m_storageType;
TEnumProperty m_bitsPerPixel;
ExrWriterProperties();
void updateTranslation() override;
};
//===========================================================================
Tiio::Reader* makeExrReader();
Tiio::Writer* makeExrWriter();
} // namespace Tiio
#endif

View file

@ -0,0 +1,496 @@
#ifndef TINYEXR_OTMOD_H_
#define TINYEXR_OTMOD_H_
#define TINYEXR_IMPLEMENTATION
#include "tinyexr.h"
/*
* This source is based on TinyEXR code, enabling to use file handle
* as an argument instead of file path in order to fit usage in OpenToonz.
* TinyEXR code is licensed under the following:
*/
// Start of TinyEXR license -------------------------------------------------
/*
Copyright (c) 2014 - 2021, Syoyo Fujita and many contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Syoyo Fujita nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// End of TinyEXR license -------------------------------------------------
// TinyEXR contains some OpenEXR code, which is licensed under ------------
///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////
// End of OpenEXR license -------------------------------------------------
#ifdef __cplusplus
extern "C" {
#endif
extern int ParseEXRVersionFromFileHandle(EXRVersion *version, FILE *fp);
extern int ParseEXRHeaderFromFileHandle(EXRHeader *exr_header,
const EXRVersion *exr_version, FILE *fp,
const char **err);
extern int LoadEXRImageFromFileHandle(EXRImage *exr_image,
const EXRHeader *exr_header, FILE *fp,
const char **err);
extern int LoadEXRHeaderFromFileHandle(EXRHeader &exr_header, FILE *file,
const char **err);
extern int LoadEXRImageBufFromFileHandle(float **out_rgba,
EXRHeader &exr_header, FILE *file,
const char **err);
extern int SaveEXRImageToFileHandle(const EXRImage *exr_image,
const EXRHeader *exr_header, FILE *fp,
const char **err);
#ifdef __cplusplus
}
#endif
#endif // TINYEXR_OTMOD_H_
#ifdef TINYEXR_OTMOD_IMPLEMENTATION
#ifndef TINYEXR_OTMOD_IMPLEMENTATION_DEFINED
#define TINYEXR_OTMOD_IMPLEMENTATION_DEFINED
int ParseEXRVersionFromFileHandle(EXRVersion *version, FILE *fp) {
if (!fp) {
return TINYEXR_ERROR_CANT_OPEN_FILE;
}
size_t file_size;
// Compute size
fseek(fp, 0, SEEK_END);
file_size = static_cast<size_t>(ftell(fp));
fseek(fp, 0, SEEK_SET);
if (file_size < tinyexr::kEXRVersionSize) {
return TINYEXR_ERROR_INVALID_FILE;
}
unsigned char buf[tinyexr::kEXRVersionSize];
size_t ret = fread(&buf[0], 1, tinyexr::kEXRVersionSize, fp);
// fclose(fp);
if (ret != tinyexr::kEXRVersionSize) {
return TINYEXR_ERROR_INVALID_FILE;
}
return ParseEXRVersionFromMemory(version, buf, tinyexr::kEXRVersionSize);
}
int ParseEXRHeaderFromFileHandle(EXRHeader *exr_header,
const EXRVersion *exr_version, FILE *fp,
const char **err) {
if (exr_header == NULL || exr_version == NULL) {
tinyexr::SetErrorMessage("Invalid argument for ParseEXRHeaderFromFile",
err);
return TINYEXR_ERROR_INVALID_ARGUMENT;
}
if (!fp) {
tinyexr::SetErrorMessage("Cannot read file ", err);
return TINYEXR_ERROR_CANT_OPEN_FILE;
}
size_t filesize;
// Compute size
fseek(fp, 0, SEEK_END);
filesize = static_cast<size_t>(ftell(fp));
fseek(fp, 0, SEEK_SET);
std::vector<unsigned char> buf(filesize); // @todo { use mmap }
{
size_t ret;
ret = fread(&buf[0], 1, filesize, fp);
assert(ret == filesize);
// fclose(fp);
if (ret != filesize) {
tinyexr::SetErrorMessage("fread() error", err);
return TINYEXR_ERROR_INVALID_FILE;
}
}
return ParseEXRHeaderFromMemory(exr_header, exr_version, &buf.at(0), filesize,
err);
}
int LoadEXRImageFromFileHandle(EXRImage *exr_image, const EXRHeader *exr_header,
FILE *fp, const char **err) {
if (exr_image == NULL) {
tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromFile", err);
return TINYEXR_ERROR_INVALID_ARGUMENT;
}
if (!fp) {
tinyexr::SetErrorMessage("Cannot read file", err);
return TINYEXR_ERROR_CANT_OPEN_FILE;
}
size_t filesize;
// Compute size
fseek(fp, 0, SEEK_END);
filesize = static_cast<size_t>(ftell(fp));
fseek(fp, 0, SEEK_SET);
if (filesize < 16) {
tinyexr::SetErrorMessage("File size too short", err);
return TINYEXR_ERROR_INVALID_FILE;
}
std::vector<unsigned char> buf(filesize); // @todo { use mmap }
{
size_t ret;
ret = fread(&buf[0], 1, filesize, fp);
assert(ret == filesize);
// fclose(fp);
(void)ret;
}
return LoadEXRImageFromMemory(exr_image, exr_header, &buf.at(0), filesize,
err);
}
int LoadEXRHeaderFromFileHandle(EXRHeader &exr_header, FILE *file,
const char **err) {
EXRVersion exr_version;
InitEXRHeader(&exr_header);
{
FILE *_fp = file;
int ret = ParseEXRVersionFromFileHandle(&exr_version, _fp);
if (ret != TINYEXR_SUCCESS) {
std::stringstream ss;
ss << "Failed to open EXR file or read version info from EXR file. code("
<< ret << ")";
tinyexr::SetErrorMessage(ss.str(), err);
return ret;
}
if (exr_version.multipart || exr_version.non_image) {
tinyexr::SetErrorMessage(
"Loading multipart or DeepImage is not supported in LoadEXR() API",
err);
return TINYEXR_ERROR_INVALID_DATA; // @fixme.
}
}
{
FILE *_fp = file;
int ret = ParseEXRHeaderFromFileHandle(&exr_header, &exr_version, _fp, err);
if (ret != TINYEXR_SUCCESS) {
FreeEXRHeader(&exr_header);
return ret;
}
}
return TINYEXR_SUCCESS;
}
int LoadEXRImageBufFromFileHandle(float **out_rgba, EXRHeader &exr_header,
FILE *file, const char **err) {
if (out_rgba == NULL) {
tinyexr::SetErrorMessage("Invalid argument for LoadEXR()", err);
return TINYEXR_ERROR_INVALID_ARGUMENT;
}
EXRImage exr_image;
InitEXRImage(&exr_image);
// Read HALF channel as FLOAT.
for (int i = 0; i < exr_header.num_channels; i++) {
if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
}
}
// TODO: Probably limit loading to layers (channels) selected by layer index
{
FILE *_fp = file;
int ret = LoadEXRImageFromFileHandle(&exr_image, &exr_header, _fp, err);
if (ret != TINYEXR_SUCCESS) {
FreeEXRHeader(&exr_header);
return ret;
}
}
// RGBA
int idxR = -1;
int idxG = -1;
int idxB = -1;
int idxA = -1;
std::vector<std::string> layer_names;
tinyexr::GetLayers(exr_header, layer_names);
std::vector<tinyexr::LayerChannel> channels;
tinyexr::ChannelsInLayer(exr_header, "", channels);
if (channels.size() < 1) {
tinyexr::SetErrorMessage("Layer Not Found", err);
FreeEXRHeader(&exr_header);
FreeEXRImage(&exr_image);
return TINYEXR_ERROR_LAYER_NOT_FOUND;
}
size_t ch_count = channels.size() < 4 ? channels.size() : 4;
for (size_t c = 0; c < ch_count; c++) {
const tinyexr::LayerChannel &ch = channels[c];
if (ch.name == "R") {
idxR = int(ch.index);
} else if (ch.name == "G") {
idxG = int(ch.index);
} else if (ch.name == "B") {
idxB = int(ch.index);
} else if (ch.name == "A") {
idxA = int(ch.index);
}
}
if (channels.size() == 1) {
int chIdx = int(channels.front().index);
// Grayscale channel only.
(*out_rgba) = reinterpret_cast<float *>(
malloc(4 * sizeof(float) * static_cast<size_t>(exr_image.width) *
static_cast<size_t>(exr_image.height)));
if (exr_header.tiled) {
for (int it = 0; it < exr_image.num_tiles; it++) {
for (int j = 0; j < exr_header.tile_size_y; j++) {
for (int i = 0; i < exr_header.tile_size_x; i++) {
const int ii = exr_image.tiles[it].offset_x *
static_cast<int>(exr_header.tile_size_x) +
i;
const int jj = exr_image.tiles[it].offset_y *
static_cast<int>(exr_header.tile_size_y) +
j;
const int idx = ii + jj * static_cast<int>(exr_image.width);
// out of region check.
if (ii >= exr_image.width) {
continue;
}
if (jj >= exr_image.height) {
continue;
}
const int srcIdx = i + j * exr_header.tile_size_x;
unsigned char **src = exr_image.tiles[it].images;
(*out_rgba)[4 * idx + 0] =
reinterpret_cast<float **>(src)[chIdx][srcIdx];
(*out_rgba)[4 * idx + 1] =
reinterpret_cast<float **>(src)[chIdx][srcIdx];
(*out_rgba)[4 * idx + 2] =
reinterpret_cast<float **>(src)[chIdx][srcIdx];
(*out_rgba)[4 * idx + 3] =
reinterpret_cast<float **>(src)[chIdx][srcIdx];
}
}
}
} else {
for (int i = 0; i < exr_image.width * exr_image.height; i++) {
const float val =
reinterpret_cast<float **>(exr_image.images)[chIdx][i];
(*out_rgba)[4 * i + 0] = val;
(*out_rgba)[4 * i + 1] = val;
(*out_rgba)[4 * i + 2] = val;
(*out_rgba)[4 * i + 3] = val;
}
}
} else {
// Assume RGB(A)
if (idxR == -1) {
tinyexr::SetErrorMessage("R channel not found", err);
FreeEXRHeader(&exr_header);
FreeEXRImage(&exr_image);
return TINYEXR_ERROR_INVALID_DATA;
}
if (idxG == -1) {
tinyexr::SetErrorMessage("G channel not found", err);
FreeEXRHeader(&exr_header);
FreeEXRImage(&exr_image);
return TINYEXR_ERROR_INVALID_DATA;
}
if (idxB == -1) {
tinyexr::SetErrorMessage("B channel not found", err);
FreeEXRHeader(&exr_header);
FreeEXRImage(&exr_image);
return TINYEXR_ERROR_INVALID_DATA;
}
(*out_rgba) = reinterpret_cast<float *>(
malloc(4 * sizeof(float) * static_cast<size_t>(exr_image.width) *
static_cast<size_t>(exr_image.height)));
if (exr_header.tiled) {
for (int it = 0; it < exr_image.num_tiles; it++) {
for (int j = 0; j < exr_header.tile_size_y; j++) {
for (int i = 0; i < exr_header.tile_size_x; i++) {
const int ii =
exr_image.tiles[it].offset_x * exr_header.tile_size_x + i;
const int jj =
exr_image.tiles[it].offset_y * exr_header.tile_size_y + j;
const int idx = ii + jj * exr_image.width;
// out of region check.
if (ii >= exr_image.width) {
continue;
}
if (jj >= exr_image.height) {
continue;
}
const int srcIdx = i + j * exr_header.tile_size_x;
unsigned char **src = exr_image.tiles[it].images;
(*out_rgba)[4 * idx + 0] =
reinterpret_cast<float **>(src)[idxR][srcIdx];
(*out_rgba)[4 * idx + 1] =
reinterpret_cast<float **>(src)[idxG][srcIdx];
(*out_rgba)[4 * idx + 2] =
reinterpret_cast<float **>(src)[idxB][srcIdx];
if (idxA != -1) {
(*out_rgba)[4 * idx + 3] =
reinterpret_cast<float **>(src)[idxA][srcIdx];
} else {
(*out_rgba)[4 * idx + 3] = 1.0;
}
}
}
}
} else {
for (int i = 0; i < exr_image.width * exr_image.height; i++) {
(*out_rgba)[4 * i + 0] =
reinterpret_cast<float **>(exr_image.images)[idxR][i];
(*out_rgba)[4 * i + 1] =
reinterpret_cast<float **>(exr_image.images)[idxG][i];
(*out_rgba)[4 * i + 2] =
reinterpret_cast<float **>(exr_image.images)[idxB][i];
if (idxA != -1) {
(*out_rgba)[4 * i + 3] =
reinterpret_cast<float **>(exr_image.images)[idxA][i];
} else {
(*out_rgba)[4 * i + 3] = 1.0;
}
}
}
}
FreeEXRHeader(&exr_header);
FreeEXRImage(&exr_image);
return TINYEXR_SUCCESS;
}
int SaveEXRImageToFileHandle(const EXRImage *exr_image,
const EXRHeader *exr_header, FILE *fp,
const char **err) {
if (exr_image == NULL || exr_header->compression_type < 0) {
tinyexr::SetErrorMessage("Invalid argument for SaveEXRImageToFile", err);
return TINYEXR_ERROR_INVALID_ARGUMENT;
}
#if !TINYEXR_USE_PIZ
if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
tinyexr::SetErrorMessage("PIZ compression is not supported in this build",
err);
return TINYEXR_ERROR_UNSUPPORTED_FEATURE;
}
#endif
#if !TINYEXR_USE_ZFP
if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
tinyexr::SetErrorMessage("ZFP compression is not supported in this build",
err);
return TINYEXR_ERROR_UNSUPPORTED_FEATURE;
}
#endif
if (!fp) {
tinyexr::SetErrorMessage("Cannot write a file", err);
return TINYEXR_ERROR_CANT_WRITE_FILE;
}
unsigned char *mem = NULL;
size_t mem_size = SaveEXRImageToMemory(exr_image, exr_header, &mem, err);
if (mem_size == 0) {
return TINYEXR_ERROR_SERIALZATION_FAILED;
}
size_t written_size = 0;
if ((mem_size > 0) && mem) {
written_size = fwrite(mem, 1, mem_size, fp);
}
free(mem);
// fclose(fp);
if (written_size != mem_size) {
tinyexr::SetErrorMessage("Cannot write a file", err);
return TINYEXR_ERROR_CANT_WRITE_FILE;
}
return TINYEXR_SUCCESS;
}
#endif // TINYEXR_OTMOD_IMPLEMENTATION_DEFINED
#endif // TINYEXR_OTMOD_IMPLEMENTATION

View file

@ -60,6 +60,7 @@
#include "./ffmpeg/tiio_mov.h"
#include "./mesh/tiio_mesh.h"
#include "./sprite/tiio_sprite.h"
#include "./exr/tiio_exr.h"
//-------------------------------------------------------------------
@ -152,6 +153,11 @@ void initImageIo(bool lightVersion) {
Tiio::defineWriterProperties("spritesheet",
new Tiio::SpriteWriterProperties());
Tiio::defineReaderMaker("exr", Tiio::makeExrReader);
Tiio::defineWriterMaker("exr", Tiio::makeExrWriter, true);
TFileType::declare("exr", TFileType::RASTER_IMAGE);
Tiio::defineWriterProperties("exr", new Tiio::ExrWriterProperties());
// ffmpeg
#if !defined(_WIN32) || defined(x64) || (defined(_WIN32) && defined(__GNUC__))
if (Ffmpeg::checkFfmpeg()) {