ImageData:encode now supports saving exr image files.

Also add support for loading uint exr image files, and fix a memory leak in the exr loading code.

Resolves #1813.
This commit is contained in:
slime
2022-09-10 23:17:42 -03:00
parent 9be43d671c
commit 9c46b75138
9 changed files with 236 additions and 27 deletions
+5
View File
@@ -70,5 +70,10 @@ void FormatHandler::freeRawPixels(unsigned char *mem)
delete[] mem;
}
void FormatHandler::freeEncodedImage(unsigned char *mem)
{
delete[] mem;
}
} // image
} // love
+6
View File
@@ -43,6 +43,7 @@ public:
{
ENCODED_TGA,
ENCODED_PNG,
ENCODED_EXR,
ENCODED_MAX_ENUM
};
@@ -116,6 +117,11 @@ public:
**/
virtual void freeRawPixels(unsigned char *mem);
/**
* Frees encoded image memory allocated by the format handler.
**/
virtual void freeEncodedImage(unsigned char *mem);
}; // FormatHandler
} // image
+3 -2
View File
@@ -206,12 +206,12 @@ love::filesystem::FileData *ImageData::encode(FormatHandler::EncodedFormat encod
}
catch (love::Exception &)
{
encoder->freeRawPixels(encodedimage.data);
encoder->freeEncodedImage(encodedimage.data);
throw;
}
memcpy(filedata->getData(), encodedimage.data, encodedimage.size);
encoder->freeRawPixels(encodedimage.data);
encoder->freeEncodedImage(encodedimage.data);
if (writefile)
{
@@ -859,6 +859,7 @@ StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM>::Entry
{
{"tga", FormatHandler::ENCODED_TGA},
{"png", FormatHandler::ENCODED_PNG},
{"exr", FormatHandler::ENCODED_EXR},
};
StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM> ImageData::encodedFormats(ImageData::encodedFormatEntries, sizeof(ImageData::encodedFormatEntries));
+185 -7
View File
@@ -47,8 +47,27 @@ bool EXRHandler::canDecode(Data *data)
return ParseEXRVersionFromMemory(&version, (const unsigned char *) data->getData(), data->getSize()) == TINYEXR_SUCCESS;
}
bool EXRHandler::canEncode(PixelFormat /*rawFormat*/, EncodedFormat /*encodedFormat*/)
bool EXRHandler::canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat)
{
if (encodedFormat != ENCODED_EXR)
return false;
switch (rawFormat)
{
case PIXELFORMAT_R16_FLOAT:
case PIXELFORMAT_R32_FLOAT:
case PIXELFORMAT_R32_UINT:
case PIXELFORMAT_RG16_FLOAT:
case PIXELFORMAT_RG32_FLOAT:
case PIXELFORMAT_RG32_UINT:
case PIXELFORMAT_RGBA16_FLOAT:
case PIXELFORMAT_RGBA32_FLOAT:
case PIXELFORMAT_RGBA32_UINT:
return true;
default:
return false;
}
return false;
}
@@ -76,7 +95,7 @@ static void getEXRChannels(const EXRHeader &header, const EXRImage &image, T *rg
}
template <typename T>
static T *loadEXRChannels(int width, int height, T *rgba[4], T one)
static T *readEXRChannels(int width, int height, T *rgba[4], T one)
{
T *data = nullptr;
@@ -105,6 +124,20 @@ static T *loadEXRChannels(int width, int height, T *rgba[4], T one)
return data;
}
template <typename T>
static void writeEXRChannels(int width, int height, int components, const T *pixels, T *rgba[4])
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
size_t offset = y * width + x;
for (int c = 0; c < components; c++)
rgba[c][offset] = pixels[offset * components + c];
}
}
}
FormatHandler::DecodedImage EXRHandler::decode(Data *data)
{
const char *err = "unknown error";
@@ -131,7 +164,10 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
throw love::Exception("Could not parse EXR image header: %s", err);
if (LoadEXRImageFromMemory(&exrImage, &exrHeader, mem, memsize, &err) != TINYEXR_SUCCESS)
{
FreeEXRHeader(&exrHeader);
throw love::Exception("Could not decode EXR image: %s", err);
}
}
catch (love::Exception &)
{
@@ -145,6 +181,7 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
{
if (pixelType != exrHeader.pixel_types[i])
{
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
throw love::Exception("Could not decode EXR image: all channels must have the same data type.");
}
@@ -153,7 +190,25 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
img.width = exrImage.width;
img.height = exrImage.height;
if (pixelType == TINYEXR_PIXELTYPE_HALF)
if (pixelType == TINYEXR_PIXELTYPE_UINT)
{
img.format = PIXELFORMAT_RGBA32_UINT;
uint32 *rgba[4] = {nullptr};
getEXRChannels(exrHeader, exrImage, rgba);
try
{
img.data = (unsigned char *) readEXRChannels(img.width, img.height, rgba, 1u);
}
catch (love::Exception &)
{
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
throw;
}
}
else if (pixelType == TINYEXR_PIXELTYPE_HALF)
{
img.format = PIXELFORMAT_RGBA16_FLOAT;
@@ -162,10 +217,11 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
try
{
img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, float32to16(1.0f));
img.data = (unsigned char *) readEXRChannels(img.width, img.height, rgba, float32to16(1.0f));
}
catch (love::Exception &)
{
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
throw;
}
@@ -179,30 +235,145 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
try
{
img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, 1.0f);
img.data = (unsigned char *) readEXRChannels(img.width, img.height, rgba, 1.0f);
}
catch (love::Exception &)
{
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
throw;
}
}
else
{
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
throw love::Exception("Could not decode EXR image: unknown pixel format.");
}
img.size = getPixelFormatSliceSize(img.format, img.width, img.height);
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
return img;
}
FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage & /*img*/, EncodedFormat /*encodedFormat*/)
FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage &img, EncodedFormat encodedFormat)
{
throw love::Exception("Invalid format.");
if (!canEncode(img.format, encodedFormat))
{
if (encodedFormat != ENCODED_EXR)
throw love::Exception("EXR encoder cannot encode to non-EXR format.");
else
throw love::Exception("EXR encoder cannot encode the given pixel format.");
}
const auto &formatinfo = getPixelFormatInfo(img.format);
EXRHeader exrHeader;
InitEXRHeader(&exrHeader);
exrHeader.num_channels = formatinfo.components;
// TODO: this could be configurable.
exrHeader.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
// TinyEXR expects malloc here because FreeEXRHeader uses free().
exrHeader.channels = (EXRChannelInfo *) malloc(sizeof(EXRChannelInfo) * exrHeader.num_channels);
exrHeader.pixel_types = (int *) malloc(sizeof(int) * exrHeader.num_channels);
exrHeader.requested_pixel_types = (int *) malloc(sizeof(int) * exrHeader.num_channels);
int pixeltype = -1;
if (formatinfo.dataType == PIXELFORMATTYPE_UINT)
{
pixeltype = TINYEXR_PIXELTYPE_UINT;
}
else if (formatinfo.dataType == PIXELFORMATTYPE_SFLOAT)
{
if (formatinfo.blockSize / formatinfo.components == 2)
pixeltype = TINYEXR_PIXELTYPE_HALF;
else if (formatinfo.blockSize / formatinfo.components == 4)
pixeltype = TINYEXR_PIXELTYPE_FLOAT;
}
if (pixeltype == -1)
{
FreeEXRHeader(&exrHeader);
throw love::Exception("Cannot convert the given pixel format to an EXR pixel type.");
}
for (int i = 0; i < exrHeader.num_channels; i++)
{
exrHeader.channels[i] = EXRChannelInfo();
const char names[] = {'R', 'G', 'B', 'A'};
exrHeader.channels[i].name[0] = names[i];
exrHeader.pixel_types[i] = pixeltype;
exrHeader.requested_pixel_types[i] = pixeltype;
}
EXRImage exrImage;
InitEXRImage(&exrImage);
exrImage.width = img.width;
exrImage.height = img.height;
exrImage.num_channels = exrHeader.num_channels;
exrImage.images = (unsigned char **) malloc(sizeof(unsigned char *) * exrImage.num_channels);
for (int i = 0; i < exrImage.num_channels; i++)
{
size_t componentsize = pixeltype == TINYEXR_PIXELTYPE_HALF ? 2 : 4;
exrImage.images[i] = (unsigned char *) malloc(img.width * img.height * componentsize);
if (exrImage.images[i] == nullptr)
{
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
throw love::Exception("Out of memory.");
}
}
if (pixeltype == TINYEXR_PIXELTYPE_UINT)
{
writeEXRChannels(img.width, img.height, formatinfo.components, (const uint32 *) img.data, (uint32 **) exrImage.images);
}
else if (pixeltype == TINYEXR_PIXELTYPE_HALF)
{
writeEXRChannels(img.width, img.height, formatinfo.components, (const float16 *) img.data, (float16 **) exrImage.images);
}
else if (pixeltype == TINYEXR_PIXELTYPE_FLOAT)
{
writeEXRChannels(img.width, img.height, formatinfo.components, (const float *) img.data, (float **) exrImage.images);
}
EncodedImage encimg;
const char *err = nullptr;
encimg.size = SaveEXRImageToMemory(&exrImage, &exrHeader, &encimg.data, &err);
FreeEXRHeader(&exrHeader);
FreeEXRImage(&exrImage);
std::string errstring;
if (err != nullptr)
{
errstring = err;
FreeEXRErrorMessage(err);
}
if (encimg.size == 0)
{
if (!errstring.empty())
throw love::Exception("Could not encode EXR image: %s", errstring.c_str());
else
throw love::Exception("Could not encode EXR image");
}
return encimg;
}
void EXRHandler::freeRawPixels(unsigned char *mem)
@@ -210,6 +381,13 @@ void EXRHandler::freeRawPixels(unsigned char *mem)
delete[] mem;
}
void EXRHandler::freeEncodedImage(unsigned char *mem)
{
// SaveEXRImageToMemory uses malloc.
if (mem != nullptr)
::free(mem);
}
} // magpie
} // image
} // love
+8 -5
View File
@@ -36,15 +36,18 @@ class EXRHandler : public FormatHandler
{
public:
virtual ~EXRHandler() {}
// Implements FormatHandler.
virtual bool canDecode(Data *data);
virtual bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat);
bool canDecode(Data *data) override;
bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat) override;
virtual DecodedImage decode(Data *data);
virtual EncodedImage encode(const DecodedImage &img, EncodedFormat format);
DecodedImage decode(Data *data) override;
EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
virtual void freeRawPixels(unsigned char *mem);
void freeRawPixels(unsigned char *mem) override;
void freeEncodedImage(unsigned char *mem) override;
}; // EXRHandler
+6
View File
@@ -264,6 +264,12 @@ void PNGHandler::freeRawPixels(unsigned char *mem)
::free(mem);
}
void PNGHandler::freeEncodedImage(unsigned char *mem)
{
if (mem)
::free(mem);
}
} // magpie
} // image
} // love
+8 -5
View File
@@ -37,15 +37,18 @@ class PNGHandler : public FormatHandler
{
public:
virtual ~PNGHandler() {}
// Implements FormatHandler.
virtual bool canDecode(Data *data);
virtual bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat);
bool canDecode(Data *data) override;
bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat) override;
virtual DecodedImage decode(Data *data);
virtual EncodedImage encode(const DecodedImage &img, EncodedFormat format);
DecodedImage decode(Data *data) override;
EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
virtual void freeRawPixels(unsigned char *mem);
void freeRawPixels(unsigned char *mem) override;
void freeEncodedImage(unsigned char *mem) override;
}; // PNGHandler
+12 -8
View File
@@ -123,15 +123,14 @@ FormatHandler::EncodedImage STBHandler::encode(const DecodedImage &img, EncodedF
encimg.size = (img.width * img.height * bpp) + headerlen;
// We need to use malloc because we use stb_image_free (which uses free())
// as our custom free() function, which is called by the ImageData after
// encode() is complete.
// stb_image's source code is compiled with this source, so calling malloc()
// directly is fine.
encimg.data = (unsigned char *) malloc(encimg.size);
if (encimg.data == nullptr)
try
{
encimg.data = new unsigned char[encimg.size];
}
catch (std::exception &)
{
throw love::Exception("Out of memory.");
}
// here's the header for the Targa file format.
encimg.data[0] = 0; // ID field size
@@ -176,6 +175,11 @@ void STBHandler::freeRawPixels(unsigned char *mem)
stbi_image_free(mem);
}
void STBHandler::freeEncodedImage(unsigned char *mem)
{
delete[] mem;
}
} // magpie
} // image
} // love
+3
View File
@@ -40,6 +40,8 @@ class STBHandler final : public FormatHandler
{
public:
virtual ~STBHandler() {}
// Implements FormatHandler.
bool canDecode(Data *data) override;
@@ -49,6 +51,7 @@ public:
EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
void freeRawPixels(unsigned char *mem) override;
void freeEncodedImage(unsigned char *mem) override;
}; // STBHandler