ImageData (and Images loaded from them) now support different data formats. Resolves issue #1048.

Currently exposed formats are rgba8 and rgba16 (normalized), and rgba16f and rgba32f (floating-point). Some systems, especially mobile ones, won't support every format when creating a love.graphics Image. Use love.graphics.getRawImageFormats to check for support.

love.image.newImageData now takes an optional format parameter as its third argument when creating an empty sized ImageData. It defaults to rgba8.

16-bit PNGs, .hdr images, and floating-point OpenEXR images can now be loaded via love.image.newImageData and love.graphics.newImage.

--HG--
branch : minor
This commit is contained in:
Alex Szpakowski
2016-05-22 15:28:20 -03:00
parent 3e6d01748e
commit e71f95595c
31 changed files with 12018 additions and 241 deletions
+202
View File
@@ -0,0 +1,202 @@
/**
* Copyright (c) 2006-2016 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "EXRHandler.h"
// tinyexr
#define TINYEXR_IMPLEMENTATION
#include "libraries/tinyexr/tinyexr.h"
// C
#include <cstdlib>
namespace love
{
namespace image
{
namespace magpie
{
bool EXRHandler::canDecode(love::filesystem::FileData *data)
{
const char *err;
EXRImage exrImage;
InitEXRImage(&exrImage);
if (ParseMultiChannelEXRHeaderFromMemory(&exrImage, (const unsigned char *) data->getData(), &err) != 0)
return false;
FreeEXRImage(&exrImage);
return exrImage.width > 0 && exrImage.height > 0;
}
bool EXRHandler::canEncode(ImageData::Format /*rawFormat*/, ImageData::EncodedFormat /*encodedFormat*/)
{
return false;
}
template <typename T>
static void getEXRChannels(const EXRImage &exrImage, T *rgba[4])
{
for (int i = 0; i < exrImage.num_channels; i++)
{
if (exrImage.channel_names[i] == nullptr)
continue;
switch (*exrImage.channel_names[i])
{
case 'R':
rgba[0] = (T *) exrImage.images[i];
break;
case 'G':
rgba[1] = (T *) exrImage.images[i];
break;
case 'B':
rgba[2] = (T *) exrImage.images[i];
break;
case 'A':
rgba[3] = (T *) exrImage.images[i];
break;
}
}
}
template <typename T>
static T *loadEXRChannels(int width, int height, T *rgba[4], T one)
{
T *data = nullptr;
try
{
data = new T[width * height * 4];
}
catch (std::exception &)
{
throw love::Exception("Out of memory.");
}
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
size_t offset = y * width + x;
data[offset * 4 + 0] = rgba[0] != nullptr ? rgba[0][offset] : 0;
data[offset * 4 + 1] = rgba[1] != nullptr ? rgba[1][offset] : 0;
data[offset * 4 + 2] = rgba[2] != nullptr ? rgba[2][offset] : 0;
data[offset * 4 + 3] = rgba[3] != nullptr ? rgba[3][offset] : one;
}
}
return data;
}
FormatHandler::DecodedImage EXRHandler::decode(love::filesystem::FileData *data)
{
const char *err;
auto mem = (const unsigned char *) data->getData();
DecodedImage img;
EXRImage exrImage;
InitEXRImage(&exrImage);
if (ParseMultiChannelEXRHeaderFromMemory(&exrImage, mem, &err) != 0)
throw love::Exception("Could not parse EXR image: %s", err);
if (LoadMultiChannelEXRFromMemory(&exrImage, mem, &err) != 0)
throw love::Exception("Could not decode EXR image: %s", err);
int pixelType = exrImage.pixel_types[0];
for (int i = 1; i < exrImage.num_channels; i++)
{
if (pixelType != exrImage.pixel_types[i])
{
FreeEXRImage(&exrImage);
throw love::Exception("Could not decode EXR image: all channels must have the same data type.");
}
}
img.width = exrImage.width;
img.height = exrImage.height;
if (pixelType == TINYEXR_PIXELTYPE_HALF)
{
img.format = ImageData::FORMAT_RGBA16F;
half *rgba[4] = {nullptr};
getEXRChannels(exrImage, rgba);
try
{
img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, floatToHalf(1.0f));
}
catch (love::Exception &)
{
FreeEXRImage(&exrImage);
throw;
}
}
else if (pixelType == TINYEXR_PIXELTYPE_FLOAT)
{
img.format = ImageData::FORMAT_RGBA32F;
float *rgba[4] = {nullptr};
getEXRChannels(exrImage, rgba);
try
{
img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, 1.0f);
}
catch (love::Exception &)
{
FreeEXRImage(&exrImage);
throw;
}
}
else
{
FreeEXRImage(&exrImage);
throw love::Exception("Could not decode EXR image: unknown pixel format.");
}
img.size = img.width * img.height * ImageData::getPixelSize(img.format);
FreeEXRImage(&exrImage);
return img;
}
FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage & /*img*/, ImageData::EncodedFormat /*encodedFormat*/)
{
throw love::Exception("Invalid format.");
}
void EXRHandler::free(unsigned char *mem)
{
delete[] mem;
}
} // magpie
} // image
} // love
+56
View File
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2006-2016 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_IMAGE_MAGPIE_EXR_HANDLER_H
#define LOVE_IMAGE_MAGPIE_EXR_HANDLER_H
#include "FormatHandler.h"
namespace love
{
namespace image
{
namespace magpie
{
/**
* Interface between ImageData and TinyEXR library, for decoding exr files.
**/
class EXRHandler : public FormatHandler
{
public:
// Implements FormatHandler.
virtual bool canDecode(love::filesystem::FileData *data);
virtual bool canEncode(ImageData::Format rawFormat, ImageData::EncodedFormat encodedFormat);
virtual DecodedImage decode(love::filesystem::FileData *data);
virtual EncodedImage encode(const DecodedImage &img, ImageData::EncodedFormat format);
virtual void free(unsigned char *mem);
}; // EXRHandler
} // magpie
} // image
} // love
#endif // LOVE_IMAGE_MAGPIE_EXR_HANDLER_H
+1 -1
View File
@@ -42,7 +42,7 @@ bool FormatHandler::canDecode(love::filesystem::FileData* /*data*/)
return false;
}
bool FormatHandler::canEncode(ImageData::EncodedFormat /*format*/)
bool FormatHandler::canEncode(ImageData::Format /*rawFormat*/, ImageData::EncodedFormat /*encodedFormat*/)
{
return false;
}
+2 -1
View File
@@ -44,6 +44,7 @@ public:
// Raw RGBA pixel data.
struct DecodedImage
{
ImageData::Format format = ImageData::FORMAT_RGBA8;
int width = 0;
int height = 0;
size_t size = 0;
@@ -75,7 +76,7 @@ public:
/**
* Whether this format handler can encode to a particular format.
**/
virtual bool canEncode(ImageData::EncodedFormat format);
virtual bool canEncode(ImageData::Format rawFormat, ImageData::EncodedFormat encodedFormat);
/**
* Decodes an image from its encoded form into raw pixel data.
+8 -4
View File
@@ -27,6 +27,7 @@
#include "PNGHandler.h"
#include "STBHandler.h"
#include "EXRHandler.h"
#include "ddsHandler.h"
#include "PVRHandler.h"
@@ -43,9 +44,12 @@ namespace magpie
Image::Image()
{
halfInit(); // Makes sure half-float conversions can be used.
formatHandlers = {
new PNGHandler,
new STBHandler,
new EXRHandler,
};
compressedFormatHandlers = {
@@ -78,14 +82,14 @@ love::image::ImageData *Image::newImageData(love::filesystem::FileData *data)
return new ImageData(formatHandlers, data);
}
love::image::ImageData *Image::newImageData(int width, int height)
love::image::ImageData *Image::newImageData(int width, int height, ImageData::Format format)
{
return new ImageData(formatHandlers, width, height);
return new ImageData(formatHandlers, width, height, format);
}
love::image::ImageData *Image::newImageData(int width, int height, void *data, bool own)
love::image::ImageData *Image::newImageData(int width, int height, ImageData::Format format, void *data, bool own)
{
return new ImageData(formatHandlers, width, height, data, own);
return new ImageData(formatHandlers, width, height, format, data, own);
}
love::image::CompressedImageData *Image::newCompressedData(love::filesystem::FileData *data)
+2 -2
View File
@@ -52,8 +52,8 @@ public:
const char *getName() const;
love::image::ImageData *newImageData(love::filesystem::FileData *data);
love::image::ImageData *newImageData(int width, int height);
love::image::ImageData *newImageData(int width, int height, void *data, bool own = false);
love::image::ImageData *newImageData(int width, int height, ImageData::Format format = ImageData::FORMAT_RGBA8);
love::image::ImageData *newImageData(int width, int height, ImageData::Format format, void *data, bool own = false);
love::image::CompressedImageData *newCompressedData(love::filesystem::FileData *data);
+25 -20
View File
@@ -28,8 +28,8 @@ namespace image
namespace magpie
{
ImageData::ImageData(std::list<FormatHandler *> formats, love::filesystem::FileData *data)
: formatHandlers(formats)
ImageData::ImageData(std::list<FormatHandler *> formatHandlers, love::filesystem::FileData *data)
: formatHandlers(formatHandlers)
, decodeHandler(nullptr)
{
for (FormatHandler *handler : formatHandlers)
@@ -38,8 +38,8 @@ ImageData::ImageData(std::list<FormatHandler *> formats, love::filesystem::FileD
decode(data);
}
ImageData::ImageData(std::list<FormatHandler *> formats, int width, int height)
: formatHandlers(formats)
ImageData::ImageData(std::list<FormatHandler *> formatHandlers, int width, int height, Format format)
: formatHandlers(formatHandlers)
, decodeHandler(nullptr)
{
for (FormatHandler *handler : formatHandlers)
@@ -47,15 +47,16 @@ ImageData::ImageData(std::list<FormatHandler *> formats, int width, int height)
this->width = width;
this->height = height;
this->format = format;
create(width, height);
create(width, height, format);
// Set to black/transparency.
memset(data, 0, width*height*sizeof(pixel));
memset(data, 0, getSize());
}
ImageData::ImageData(std::list<FormatHandler *> formats, int width, int height, void *data, bool own)
: formatHandlers(formats)
ImageData::ImageData(std::list<FormatHandler *> formatHandlers, int width, int height, Format format, void *data, bool own)
: formatHandlers(formatHandlers)
, decodeHandler(nullptr)
{
for (FormatHandler *handler : formatHandlers)
@@ -63,11 +64,12 @@ ImageData::ImageData(std::list<FormatHandler *> formats, int width, int height,
this->width = width;
this->height = height;
this->format = format;
if (own)
this->data = (unsigned char *) data;
else
create(width, height, data);
create(width, height, format, data);
}
ImageData::~ImageData()
@@ -81,11 +83,13 @@ ImageData::~ImageData()
handler->release();
}
void ImageData::create(int width, int height, void *data)
void ImageData::create(int width, int height, Format format, void *data)
{
size_t datasize = width * height * getPixelSize(format);
try
{
this->data = new unsigned char[width*height*sizeof(pixel)];
this->data = new unsigned char[datasize];
}
catch(std::bad_alloc &)
{
@@ -93,9 +97,10 @@ void ImageData::create(int width, int height, void *data)
}
if (data)
memcpy(this->data, data, width*height*sizeof(pixel));
memcpy(this->data, data, datasize);
decodeHandler = nullptr;
this->format = format;
}
void ImageData::decode(love::filesystem::FileData *data)
@@ -121,8 +126,7 @@ void ImageData::decode(love::filesystem::FileData *data)
throw love::Exception("Could not decode file '%s' to ImageData: unsupported file format", name.c_str());
}
// The decoder *must* output a 32 bits-per-pixel image.
if (decodedimage.size != decodedimage.width*decodedimage.height*sizeof(pixel))
if (decodedimage.size != decodedimage.width * decodedimage.height * getPixelSize(decodedimage.format))
{
decoder->free(decodedimage.data);
throw love::Exception("Could not convert image!");
@@ -134,14 +138,15 @@ void ImageData::decode(love::filesystem::FileData *data)
else
delete[] this->data;
this->width = decodedimage.width;
this->width = decodedimage.width;
this->height = decodedimage.height;
this->data = decodedimage.data;
this->data = decodedimage.data;
this->format = decodedimage.format;
decodeHandler = decoder;
}
love::filesystem::FileData *ImageData::encode(EncodedFormat format, const char *filename)
love::filesystem::FileData *ImageData::encode(EncodedFormat encodedFormat, const char *filename)
{
FormatHandler *encoder = nullptr;
FormatHandler::EncodedImage encodedimage;
@@ -149,12 +154,12 @@ love::filesystem::FileData *ImageData::encode(EncodedFormat format, const char *
rawimage.width = width;
rawimage.height = height;
rawimage.size = width*height*sizeof(pixel);
rawimage.size = getSize();
rawimage.data = data;
for (FormatHandler *handler : formatHandlers)
{
if (handler->canEncode(format))
if (handler->canEncode(format, encodedFormat))
{
encoder = handler;
break;
@@ -164,7 +169,7 @@ love::filesystem::FileData *ImageData::encode(EncodedFormat format, const char *
if (encoder != nullptr)
{
thread::Lock lock(mutex);
encodedimage = encoder->encode(rawimage, format);
encodedimage = encoder->encode(rawimage, encodedFormat);
}
if (encoder == nullptr || encodedimage.data == nullptr)
+5 -5
View File
@@ -39,18 +39,18 @@ class ImageData : public love::image::ImageData
{
public:
ImageData(std::list<FormatHandler *> formats, love::filesystem::FileData *data);
ImageData(std::list<FormatHandler *> formats, int width, int height);
ImageData(std::list<FormatHandler *> formats, int width, int height, void *data, bool own);
ImageData(std::list<FormatHandler *> formatHandlers, love::filesystem::FileData *data);
ImageData(std::list<FormatHandler *> formatHandlers, int width, int height, Format format = FORMAT_RGBA8);
ImageData(std::list<FormatHandler *> formatHandlers, int width, int height, Format format, void *data, bool own);
virtual ~ImageData();
// Implements image::ImageData.
virtual love::filesystem::FileData *encode(EncodedFormat format, const char *filename);
virtual love::filesystem::FileData *encode(EncodedFormat encodedFormat, const char *filename);
private:
// Create imagedata. Initialize with data if not null.
void create(int width, int height, void *data = 0);
void create(int width, int height, Format format, void *data = nullptr);
// Decode and load an encoded format.
void decode(love::filesystem::FileData *data);
+34 -12
View File
@@ -140,9 +140,10 @@ bool PNGHandler::canDecode(love::filesystem::FileData *data)
return status == 0 && width > 0 && height > 0;
}
bool PNGHandler::canEncode(ImageData::EncodedFormat format)
bool PNGHandler::canEncode(ImageData::Format rawFormat, ImageData::EncodedFormat encodedFormat)
{
return format == ImageData::ENCODED_PNG;
return encodedFormat == ImageData::ENCODED_PNG
&& (rawFormat == ImageData::FORMAT_RGBA8 || rawFormat == ImageData::FORMAT_RGBA16);
}
PNGHandler::DecodedImage PNGHandler::decode(love::filesystem::FileData *fdata)
@@ -154,14 +155,23 @@ PNGHandler::DecodedImage PNGHandler::decode(love::filesystem::FileData *fdata)
DecodedImage img;
lodepng::State state;
unsigned status = lodepng_inspect(&width, &height, &state, indata, insize);
state.info_raw.colortype = LCT_RGBA;
state.info_raw.bitdepth = 8;
if (status != 0)
{
const char *err = lodepng_error_text(status);
throw love::Exception("Could not decode PNG image (%s)", err);
}
state.decoder.zlibsettings.custom_zlib = zlibDecompress;
state.info_raw.colortype = LCT_RGBA;
unsigned status = lodepng_decode(&img.data, &width, &height,
&state, indata, insize);
if (state.info_png.color.bitdepth == 16)
state.info_raw.bitdepth = 16;
else
state.info_raw.bitdepth = 8;
status = lodepng_decode(&img.data, &width, &height, &state, indata, insize);
if (status != 0)
{
@@ -171,14 +181,27 @@ PNGHandler::DecodedImage PNGHandler::decode(love::filesystem::FileData *fdata)
img.width = (int) width;
img.height = (int) height;
img.size = width * height * 4;
img.size = width * height * (state.info_raw.bitdepth * 4 / 8);
img.format = state.info_raw.bitdepth == 16 ? ImageData::FORMAT_RGBA16 : ImageData::FORMAT_RGBA8;
// LodePNG keeps raw 16 bit images stored as big-endian.
#ifndef LOVE_BIG_ENDIAN
if (state.info_raw.bitdepth == 16)
{
uint16 *pixeldata = (uint16 *) img.data;
uint16 numpixelcomponents = img.size / sizeof(uint16);
for (size_t i = 0; i < numpixelcomponents; i++)
pixeldata[i] = swapuint16(pixeldata[i]);
}
#endif
return img;
}
PNGHandler::EncodedImage PNGHandler::encode(const DecodedImage &img, ImageData::EncodedFormat format)
PNGHandler::EncodedImage PNGHandler::encode(const DecodedImage &img, ImageData::EncodedFormat encodedFormat)
{
if (format != ImageData::ENCODED_PNG)
if (!canEncode(img.format, encodedFormat))
throw love::Exception("PNG encoder cannot encode to non-PNG format.");
EncodedImage encimg;
@@ -186,11 +209,10 @@ PNGHandler::EncodedImage PNGHandler::encode(const DecodedImage &img, ImageData::
lodepng::State state;
state.info_raw.colortype = LCT_RGBA;
state.info_raw.bitdepth = 8;
state.info_raw.bitdepth = img.format == ImageData::FORMAT_RGBA16 ? 16 : 8;
// TODO: support plain RGB (24-bit) encoding in the future?
state.info_png.color.colortype = LCT_RGBA;
state.info_png.color.bitdepth = 8;
state.info_png.color.bitdepth = state.info_raw.bitdepth;
state.encoder.zlibsettings.custom_zlib = zlibCompress;
+1 -1
View File
@@ -41,7 +41,7 @@ public:
// Implements FormatHandler.
virtual bool canDecode(love::filesystem::FileData *data);
virtual bool canEncode(ImageData::EncodedFormat format);
virtual bool canEncode(ImageData::Format rawFormat, ImageData::EncodedFormat encodedFormat);
virtual DecodedImage decode(love::filesystem::FileData *data);
virtual EncodedImage encode(const DecodedImage &img, ImageData::EncodedFormat format);
+27 -17
View File
@@ -32,6 +32,7 @@ static void loveSTBIAssert(bool test, const char *teststr)
// #define STBI_ONLY_PNG
#define STBI_ONLY_BMP
#define STBI_ONLY_TGA
#define STBI_ONLY_HDR
#define STBI_NO_STDIO
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ASSERT(A) loveSTBIAssert((A), #A)
@@ -59,20 +60,31 @@ bool STBHandler::canDecode(love::filesystem::FileData *data)
return status == 1 && w > 0 && h > 0;
}
bool STBHandler::canEncode(ImageData::EncodedFormat format)
bool STBHandler::canEncode(ImageData::Format rawFormat, ImageData::EncodedFormat encodedFormat)
{
return format == ImageData::ENCODED_TGA;
return encodedFormat == ImageData::ENCODED_TGA && rawFormat == ImageData::FORMAT_RGBA8;
}
FormatHandler::DecodedImage STBHandler::decode(love::filesystem::FileData *data)
{
DecodedImage img;
const stbi_uc *buffer = (const stbi_uc *) data->getData();
int bufferlen = (int) data->getSize();
int comp = 0;
img.data = stbi_load_from_memory((const stbi_uc *) data->getData(),
(int) data->getSize(),
&img.width, &img.height,
&comp, 4);
if (stbi_is_hdr_from_memory(buffer, bufferlen))
{
img.data = (unsigned char *) stbi_loadf_from_memory(buffer, bufferlen, &img.width, &img.height, &comp, 4);
img.size = img.width * img.height * 4 * sizeof(float);
img.format = ImageData::FORMAT_RGBA32F;
}
else
{
img.data = stbi_load_from_memory(buffer, bufferlen, &img.width, &img.height, &comp, 4);
img.size = img.width * img.height * 4;
img.format = ImageData::FORMAT_RGBA8;
}
if (img.data == nullptr || img.width <= 0 || img.height <= 0)
{
@@ -82,14 +94,12 @@ FormatHandler::DecodedImage STBHandler::decode(love::filesystem::FileData *data)
throw love::Exception("Could not decode image with stb_image (%s).", err);
}
img.size = img.width * img.height * 4;
return img;
}
FormatHandler::EncodedImage STBHandler::encode(const DecodedImage &img, ImageData::EncodedFormat format)
FormatHandler::EncodedImage STBHandler::encode(const DecodedImage &img, ImageData::EncodedFormat encodedFormat)
{
if (!canEncode(format))
if (!canEncode(img.format, encodedFormat))
throw love::Exception("Invalid format.");
// We don't actually use stb_image for encoding, but this code is small
@@ -113,13 +123,13 @@ FormatHandler::EncodedImage STBHandler::encode(const DecodedImage &img, ImageDat
throw love::Exception("Out of memory.");
// here's the header for the Targa file format.
encimg.data[0] = 0; // ID field size
encimg.data[1] = 0; // colormap type
encimg.data[2] = 2; // image type
encimg.data[3] = encimg.data[4] = 0; // colormap start
encimg.data[5] = encimg.data[6] = 0; // colormap length
encimg.data[7] = 32; // colormap bits
encimg.data[8] = encimg.data[9] = 0; // x origin
encimg.data[0] = 0; // ID field size
encimg.data[1] = 0; // colormap type
encimg.data[2] = 2; // image type
encimg.data[3] = encimg.data[4] = 0; // colormap start
encimg.data[5] = encimg.data[6] = 0; // colormap length
encimg.data[7] = 32; // colormap bits
encimg.data[8] = encimg.data[9] = 0; // x origin
encimg.data[10] = encimg.data[11] = 0; // y origin
// Targa is little endian, so:
encimg.data[12] = img.width & 255; // least significant byte of width
+1 -1
View File
@@ -44,7 +44,7 @@ public:
// Implements FormatHandler.
virtual bool canDecode(love::filesystem::FileData *data);
virtual bool canEncode(ImageData::EncodedFormat format);
virtual bool canEncode(ImageData::Format rawFormat, ImageData::EncodedFormat encodedFormat);
virtual DecodedImage decode(love::filesystem::FileData *data);
virtual EncodedImage encode(const DecodedImage &img, ImageData::EncodedFormat format);