Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Custom Information in PNG Files (Source Code)  (Read 6437 times)

0 Members and 1 Guest are viewing this topic.

Tenry

  • Full Member
  • ***
  • Posts: 120
  • Experienced Programmer
    • View Profile
    • Simon-Burchert.com
Custom Information in PNG Files (Source Code)
« on: October 16, 2012, 05:39:08 pm »
Games usually don't have static sprites. Usually, they are animated, and some other information are also linked with the sprites such as the animation speed or the texture origin. I'm sure most of you have hard coded those extra values or put them in extra files. If you are using PNG images, you don't need to!

The PNG file is chunk based and designed to have so called private chunks besides the public chunks which contain the actual image information. So how to use your own chunks?
PNG chunks always have a 4-letter-identifer, some of the public ones are IHDR with header information or IDAT with the actual image. Those are both always needed, hence all letters are capital. Some ancillary are for example bKGD or pHYs.
The case of each letter significates something:
  • critical (X) or not (x): those chunks are needed to read the image
  • public (X) or not (x): we must use lower case letters for out private chunks
  • always capital (X): might be used in the future of PNG
  • save to copy (x) or not (X): indicates whether the chunk depends on other information (such as the image itself) or not

Typical image editors don't allow you to write your own custom chunks, neither there is a way to read the custom chunks with the SFML library itself. We need some library so read and write such chunks, and whick library should be better than the original PNG library libpng? It's quite complicated and the documentation poor (in my opinion), but I tried it and finally have a solution. To spare you the same trouble I had I tell you how to use private chunks. This assumes you having installed libpng already, I'm not going to help with that because I had my own trouble there too and I'm unsure about the "correct" installation.


Writing Private Chunks
I assume you already having the original PNG files which you want to "tag" with your custom chunk(s). First of all you should have planned which information you want to store. I created my own struct for that:
struct isPR_struct
{
  sf::Uint16 nFrames; // only positive values are allowed. 16-bit can store a number up to 65535
  sf::Int16 xOrigin; // might be positive or negative. values from -32768 - 32767 are allowed
  sf::Int16 yOrigin;
} __attribute__((packed));
// isPR: i = not critical; s = private; P must be capital; R = depends on image data
Most systems pad variables so that they fit to 16 or 32 bit, no matter what the actual type size is. If one system writes 32 bit for even just a byte value (char) and another one pad it to 16 bit, there will be incompatibilities if you are reading your chunks from a different system. Hence you should add __attribute__(packed)). But I'm not sure whether it works for any compiler or not, I'm using gcc.

Now I'm going to write a program that reads the necessary data from the source file and copies it to the destination along with our custom chunk.
#include <png.h>

...

png_bytepp loadPng(const char *fname, unsigned &width, unsigned &height)
{
  png_structp png_ptr = 0;
  png_infop info_ptr = 0;
  png_bytepp data = 0; // arrays of rows containing the image data

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                               0, 0, 0);

  if(!png_ptr) return 0; // return NULL-pointer: error
  FILE *fp = fopen(fname, "rb");

  try
  {
    info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr) throw 1; // just throw anything, you might use your own exception class

    png_init_io(png_ptr, fp); // connect libpng with our fp

    png_read_info(png_ptr, info_ptr); // fill info_ptr

    width = png_get_image_width(png_ptr, info_ptr);
    height = png_get_image_height(png_ptr, info_ptr);

    png_uint_32 bitdepth = png_get_bit_depth(png_ptr, info_ptr); // depth per channel, not pixel!
    png_uint_32 channels = png_get_channels(png_ptr, info_ptr);
    //png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);

    printf("Image: %dx%d\n", width, height); // you can discard this if it works
    printf("Bitdepth: %d\nChannels: %d\n", bitdepth, channels); // this too

    // allocate the data
    data = new png_bytep[height];
    for(size_t i=0;i<height;i++) data[i] = new png_byte[width*4];

    // fill the data
    png_read_image(png_ptr, data);

    png_destroy_read_struct(&png_ptr, &info_ptr, 0); // don't forget to free the pointers

    fclose(fp); // and to close the file
  }
  catch(int &e)
  {
    fclose(fp);

    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    return 0; // return NULL-pointer: error
  }

  return data; // finally return the filled data
}
The code assumes 32-bit RGBA images. If you are going to use any format or are unsure about the actual format of your images, do some checking with the given values (bitdepth, etc.).
Note that we have to care about deleting the returned data later with delete.
loadPng() is only needed where you are going to tag your png. You might have a separated tool for that, you don't need it in your actual game later.

// overwrites your file!
bool saveAttributes(const isPR_struct &isPR, const char *filename)
{
  unsigned width, height;
  png_bytepp data = loadPng(filename, width, height); // width and height are filled

  png_structp png_ptr;
  png_infop info_ptr;

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
                                    0, 0, 0);
  if(!png_ptr) return false;

  FILE *fp = fopen(filename.c_str(), "wb");

  try
  {
    info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr) throw 1; // like in loadPng()

    png_init_io(png_ptr, fp);

    png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA,
                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_DEFAULT); // set the header information

    png_write_info(png_ptr, info_ptr);
    png_write_image(png_ptr, (png_bytepp)data); // write the actual image data

    // tell libpng about our custom chunks
    png_unknown_chunk chunks[1]; // 1 custom chunk
    strcpy((char*)chunks[0].name, "isPR"); // fill in the name
    chunks[0].data = (png_byte*)&isPR; // data points to the chunk data
    chunks[0].size = sizeof(isPR);

    // it doesn't write your chunks without the following lines
    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, 0, 0);
    png_set_unknown_chunks(png_ptr, info_ptr, chunks, 1);
    png_set_unknown_chunk_location(png_ptr, info_ptr, 0, PNG_AFTER_IDAT);

    // this might actually write your chunks
    png_write_end(png_ptr, info_ptr);

    png_destroy_write_struct(&png_ptr, &info_ptr);

    fclose(fp);

    // don't forget to delete our data which was allocated in loadPng()!
    // you might provide another function named unloadPng() instead...
    for(size_t i=0;i<height;i++) delete data[i];
    delete data;
  }
  catch(int &e)
  {
    png_destroy_write_struct(&png_ptr, 0);

    fclose(fp);

    for(size_t i=0;i<height;i++) delete data[i];
    delete data;
    return false;
  }
 
  return true;
}

Note that the following code will discard any additional information such as the author or comments. You need to change the code if you want to keep additional information.


Reading Private Chunks
I think loading your custom chunks is the easiest step of them. First some code again:
bool loadAttributes(isPR_struct &isPR, const char *filename)
{
  DataStream ds(filename); // I use my own sf::InputStream

  png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                               0, 0, 0);

  if(!png_ptr) return false;

  try
  {
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr) throw 1;

    png_infop end_ptr = png_create_info_struct(png_ptr);
    if(!end_ptr) throw 1; // don't know why it is needed...

    // set our user chunk handler and tell libpng not to discard them
    png_set_read_user_chunk_fn(png_ptr, &isPR, &chunkReader);
    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS,
                                (png_const_bytep)"isPR", 1);

    // instead of png_init_io() if using custom file stream
    png_set_read_fn(png_ptr, &ds, &readFunction);

    png_read_info(png_ptr, info_ptr);
    png_read_update_info(png_ptr, info_ptr);

    // you need to read the rows to avoid CRC error... strange
    for(unsigned i=0;i<png_get_image_height(png_ptr, info_ptr);i++)
    {
      png_read_row(png_ptr, 0, 0);
    }

    // this might actually read your chunks
    png_read_end(png_ptr, end_ptr);

    png_destroy_read_struct(&png_ptr, &info_ptr, &end_ptr);
  }
  catch(int &e)
  {
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_ptr);
    return false;
  }
 
  return true;
}

void readFunction(png_structp png_ptr, png_bytep data, png_size_t size)
{
  DataStream *ds = (DataStream*)png_get_io_ptr(png_ptr);

  // some operations on our data stream... (read size bytes into data)
  // in standard C it would be: fread(data, 1, size, fp);
}

int chunkReader(png_structp png_ptr, png_unknown_chunkp chunk)
{
  // todo: you should first check whether:
  //  - chunk->name=="isPR"
  //  - chunk->size==sizeof(isPR_struct)

  isPR_struct *isPR_input = (isPR_struct*)chunk->data;
  isPR_struct *isPR_output = (isPR_struct*)png_get_user_chunk_ptr(png_ptr);

  *isPR_output = *isPR_input;

  // return value < 0 if the chunk had an error (e.g. mismatching size),
  // return 0 if the chunk is unknown and
  // value > 0 on success
  return 1;
}


I hope this helps you. I don't guarantee that the could would really works and wouldn't crash. I had a lot of crashes myself before arriving the final code you see above.
Please give me comments and error reports. I'm going to make the code better if something doesn't work well. Maybe I will write a small library which makes the tasks much easier.

Hope you like it :)
« Last Edit: October 16, 2012, 07:19:27 pm by Shy Guy »
Please note that my previous display name was "Shy Guy".

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Custom Information in PNG Files (Source Code)
« Reply #1 on: October 16, 2012, 06:16:48 pm »
You should rather put it on the wiki (unless you create a real open-source library with it, of course).

__attribute__((packed)) is specific to gcc. For VC++, you must use this code:
#pragma pack(push, 1)
struct isPR_struct
{
    ...
};
#pragma pack(pop)

But you probably don't need it, this structure should normally not be padded by the compiler.
« Last Edit: October 16, 2012, 06:19:43 pm by Laurent »
Laurent Gomila - SFML developer

Tenry

  • Full Member
  • ***
  • Posts: 120
  • Experienced Programmer
    • View Profile
    • Simon-Burchert.com
Re: Custom Information in PNG Files (Source Code)
« Reply #2 on: October 16, 2012, 06:28:10 pm »
You should rather put it on the wiki (unless you create a real open-source library with it, of course).
Yeah, I was going too, but I thought, there would be some response first if I put it in the forum, I'm not sure whether one can comment directly on wiki post or get noticed of new pages at all.
Please note that my previous display name was "Shy Guy".

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Custom Information in PNG Files (Source Code)
« Reply #3 on: October 16, 2012, 06:36:25 pm »
You can put the contents on the wiki, and a post with a link on the forum.
Laurent Gomila - SFML developer

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Custom Information in PNG Files (Source Code)
« Reply #4 on: October 16, 2012, 07:13:25 pm »
Interesting, but isn't it easier to use multiple texture rects in an image, considering that image editors usually don't provide access to private chunks?

Or what is the main advantage? That there is no need to specify position and size of the sub-image (but a tag instead)?

By the way, you should think about std::vector<char> as return value or output parameter, so that the user doesn't have to struggle with memory management. Exceptions are probably exaggerated, as they are not propagated outside the function. A if (error) { cleanup; return xy; } should suffice.
« Last Edit: October 16, 2012, 07:16:30 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Tenry

  • Full Member
  • ***
  • Posts: 120
  • Experienced Programmer
    • View Profile
    • Simon-Burchert.com
Re: Custom Information in PNG Files (Source Code)
« Reply #5 on: October 16, 2012, 07:18:15 pm »
Interesting, but isn't it easier to use multiple texture rects in an image, considering that image editors usually don't provide access to private chunks?

Or what is the main advantage? That there is no need to specify position and size of the sub-image (but a tag instead)?
I was thinking of a png image that has got a strip of multiple frames (either horizontally or vertically laid out). After reading the private chunks you know which rect to use with sub-images and you don't need to do a png file for every frame, only one png per animation/sprite.

By the way, you should think about returning std::vector<char> or writing into some output parameter buffer, so that the user doesn't have to struggle with memory management. And exceptions are probably exaggerated, as they are not propagated outside the function.
Thanks. I know my code isn't really nice yet as I did it just within a few hours of trouble. I'm going to improve the code more and more.
Please note that my previous display name was "Shy Guy".

Groogy

  • Hero Member
  • *****
  • Posts: 1469
    • MSN Messenger - groogy@groogy.se
    • View Profile
    • http://www.groogy.se
    • Email
Re: Custom Information in PNG Files (Source Code)
« Reply #6 on: October 16, 2012, 07:43:16 pm »
It looks really interesting though it's a problem that the image has to go trough another step in the content pipeline before it can be used in the application. I would recommend that you take a look at GIMP, it can save a comment to PNG images and that you get it to work with that. Would remove one step and only force the user to do an extra step to read the image instead.
Developer and Maker of rbSFML and Programmer at Paradox Development Studio

Tenry

  • Full Member
  • ***
  • Posts: 120
  • Experienced Programmer
    • View Profile
    • Simon-Burchert.com
Re: Custom Information in PNG Files (Source Code)
« Reply #7 on: October 16, 2012, 08:36:44 pm »
It looks really interesting though it's a problem that the image has to go trough another step in the content pipeline before it can be used in the application. I would recommend that you take a look at GIMP, it can save a comment to PNG images and that you get it to work with that. Would remove one step and only force the user to do an extra step to read the image instead.
Yeah, comments can be used to store information in text form, but then you have to write some code that interprets the comment text. I personally prefer the binary form, and the comment chunk might still be used for any information.
Anyway, I might also provide some code to read and interpret text comments instead later.
Please note that my previous display name was "Shy Guy".