1
SFML projects / libgfx - Image file format (PNG) with custom data
« on: March 16, 2014, 10:08:53 pm »
A long time ago, I started a topic about custom information in the PNG file format, which can be useful to store sprite animation data and other stuff, so you don't need extra data files or your own, possibly quite poor graphics file format, just to get these extra data stored.
Finally, I am working on a library for my current project, that is able to read and even write PNG files with custom chunks, named libgfx. It can even be used for loading and writing PNG files without the need of SFML/sfml-graphics (e.g., if you write a low-overhead console application, that shall read/write PNG files), and it will also convert the pixels data to your need (e.g. to 32-bit RGBA, no matter if the file format is 24-bit RGB or palette). And if I remember well, SFML does not support PNG based on palettes (did it change already?).
Download Beta (0.1.0)
https://github.com/tenry92/libgfx
Download Alpha (obsolete)
Source Code + Linux Compiled: libgfx-source-unix.tar.gz
Source Code + Windows Compiled: libgfx-source-win32.zip
Issues
Example reading PNG file with custom chunk:
EDIT: This is, how I currently use libgfx for loading my graphics into OpenGL, might be useful for you:
Finally, I am working on a library for my current project, that is able to read and even write PNG files with custom chunks, named libgfx. It can even be used for loading and writing PNG files without the need of SFML/sfml-graphics (e.g., if you write a low-overhead console application, that shall read/write PNG files), and it will also convert the pixels data to your need (e.g. to 32-bit RGBA, no matter if the file format is 24-bit RGB or palette). And if I remember well, SFML does not support PNG based on palettes (did it change already?).
Download Beta (0.1.0)
https://github.com/tenry92/libgfx
Download Alpha (obsolete)
Source Code + Linux Compiled: libgfx-source-unix.tar.gz
Source Code + Windows Compiled: libgfx-source-win32.zip
Issues
- This is still beta and currently designed for my personal used. This means, only a selected set of color formats and bit depths are supported at the moment. This includes, that it DOES load png files with palettes, but gives you the pixels in pre-converted rgba values at the moment.
GfxImage gfx;
gfx.colorFormat = GfxFormat_RGBA;
gfx.bitDepth = 8;
gfx.width = ...; gfx.height = ...;
/* allocate the memory based on the specs given above */
libgfx_createImage(&img);
/* now you can set your data in gfx.pixels */
libgfx_createChunk(&gfx, "isPR");
GfxChunk *myChunk = gfx.userChunks[gfx.nUserChunks - 1];
int err = libgfx_writeGfxFile(&gfx, "output.png");
libgfx_destroyImage(&gfx);
gfx.colorFormat = GfxFormat_RGBA;
gfx.bitDepth = 8;
gfx.width = ...; gfx.height = ...;
/* allocate the memory based on the specs given above */
libgfx_createImage(&img);
/* now you can set your data in gfx.pixels */
libgfx_createChunk(&gfx, "isPR");
GfxChunk *myChunk = gfx.userChunks[gfx.nUserChunks - 1];
int err = libgfx_writeGfxFile(&gfx, "output.png");
libgfx_destroyImage(&gfx);
Example reading PNG file with custom chunk:
GfxImage gfx;
/* no matter what the input format is, give me 8-bit RGBA */
gfx.colorFormat = GfxFormat_RGBA;
gfx.bitDepth = 8;
int err = libgfx_loadGfxFile(&gfx, "input.png");
if(err != GfxError_None) return 1; // error occurred
if(gfx.nUserChunks > 0)
{
GfxChunk *myChunk = gfx.userChunks[0];
/* do something with the user chunk... */
}
libgfx_destroyImage(&gfx);
/* no matter what the input format is, give me 8-bit RGBA */
gfx.colorFormat = GfxFormat_RGBA;
gfx.bitDepth = 8;
int err = libgfx_loadGfxFile(&gfx, "input.png");
if(err != GfxError_None) return 1; // error occurred
if(gfx.nUserChunks > 0)
{
GfxChunk *myChunk = gfx.userChunks[0];
/* do something with the user chunk... */
}
libgfx_destroyImage(&gfx);
EDIT: This is, how I currently use libgfx for loading my graphics into OpenGL, might be useful for you:
GfxImage img;
int ret = libgfx_loadGfxFile(&img, filename);
switch(ret)
{
case GfxError_None: break;
default: throw std::runtime_error("Unknown error loading " + filename);
case GfxError_ColorFormat: case GfxError_BitDepth:
throw std::runtime_error(filename + ": Color format not supported");
case GfxError_File: throw std::runtime_error(filename + ": Not found");
case GfxError_FileFormat: throw std::runtime_error(filename + ": Unknown format");
}
if(img.bitDepth != 8)
throw std::runtime_error(filename + ": Color format not supported (8-bit rgba expected)");
/* OpenGL usually only supports dimensions like 128x64 or 256x256. */
int nearestWidth = exp2(ceil(log2(img.width)));
int nearestHeight = exp2(ceil(log2(img.height)));
tex->actualWidth = img.width;
tex->actualHeight = img.height;
tex->textureWidth = nearestWidth;
tex->textureHeight = nearestHeight;
/* We need to resize the image to fit OpenGL's requirements. */
if(img.width < nearestWidth || img.height < nearestHeight)
{
/* resize actual texture. note: pixels out of bounds may have random data */
std::vector<uint8_t> pixels;
pixels.resize(nearestWidth * nearestHeight * 4);
for(int y = 0; y < img.height; ++y)
{
memcpy(&pixels[y * nearestWidth * 4], &img.pixels[y * img.width * 4],
img.width * 4);
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nearestWidth, nearestHeight,
0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
}
/* The image's dimensions already fit the requirements, pass it already. */
else
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nearestWidth, nearestHeight,
0, GL_RGBA, GL_UNSIGNED_BYTE, img.pixels);
}
int ret = libgfx_loadGfxFile(&img, filename);
switch(ret)
{
case GfxError_None: break;
default: throw std::runtime_error("Unknown error loading " + filename);
case GfxError_ColorFormat: case GfxError_BitDepth:
throw std::runtime_error(filename + ": Color format not supported");
case GfxError_File: throw std::runtime_error(filename + ": Not found");
case GfxError_FileFormat: throw std::runtime_error(filename + ": Unknown format");
}
if(img.bitDepth != 8)
throw std::runtime_error(filename + ": Color format not supported (8-bit rgba expected)");
/* OpenGL usually only supports dimensions like 128x64 or 256x256. */
int nearestWidth = exp2(ceil(log2(img.width)));
int nearestHeight = exp2(ceil(log2(img.height)));
tex->actualWidth = img.width;
tex->actualHeight = img.height;
tex->textureWidth = nearestWidth;
tex->textureHeight = nearestHeight;
/* We need to resize the image to fit OpenGL's requirements. */
if(img.width < nearestWidth || img.height < nearestHeight)
{
/* resize actual texture. note: pixels out of bounds may have random data */
std::vector<uint8_t> pixels;
pixels.resize(nearestWidth * nearestHeight * 4);
for(int y = 0; y < img.height; ++y)
{
memcpy(&pixels[y * nearestWidth * 4], &img.pixels[y * img.width * 4],
img.width * 4);
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nearestWidth, nearestHeight,
0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
}
/* The image's dimensions already fit the requirements, pass it already. */
else
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nearestWidth, nearestHeight,
0, GL_RGBA, GL_UNSIGNED_BYTE, img.pixels);
}