Hello everyone,

I'm working on a program that reads a PNG and extracts a custom chunk from it (using libpng). But now I've got problems to actually register my callback function to handle unknown chunks. According to the official documentation I have to declare which chunk is "unknown" (by their name) and I need to use png_set_read_user_chunk_fn to register my callback. I've used a bit part of their code but it doesn't work, my callback isn't actually called and nothing happens except the width and heigh being printed.

Here is an entire working code which reproduces the problem :

#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

FILE *fp;
png_structp png_ptr;
png_infop info_ptr, end_info;
png_byte magic[8];
png_byte custom_chunk[5] = { 116, 73, 77, 69, (png_byte)'\0' }; // tIME
png_byte unused_chunks[]=
{
	104,  73,  83,  84, (png_byte) '\0',   /* hIST */
	105,  84,  88, 116, (png_byte) '\0',   /* iTXt */
	112,  67,  65,  76, (png_byte) '\0',   /* pCAL */
	115,  67,  65,  76, (png_byte) '\0',   /* sCAL */
	115,  80,  76,  84, (png_byte) '\0',   /* sPLT */
	'I', 'D', 'A', 'T', (png_byte) '\0' /* IDAT */
};

void readPNG(char *file);
void read_chunk_custom(png_structp ptr, png_unknown_chunkp chunk);
int main(int argc, char **argv);

void read_chunk_custom(png_structp ptr, png_unknown_chunkp chunk) {
	printf("Reading custom chunk @ %p\n", chunk->data);
	
	FILE *out = fopen("./out.jpg", "wb");
	if (!out) {
		fprintf(stderr, "Cannot write the chunk to ouput.\n");
		return;
	}
	
	fwrite(chunk->data, 1, sizeof(chunk->data), out);
	fclose(out);
	
	return;
}

void readPNG(char *file) {
	printf("read: %s\n", file);
	
	fp = fopen(file, "rb");
	if (!fp) {
		fprintf(stderr, "Cannot open %s.\n", file);
		return;
	}
	
	fread(magic, 1, sizeof(magic), fp);
	if (!png_check_sig(magic, sizeof(magic))) {
		fprintf(stderr, "error: %s isn't a valid PNG file.\n", file);
		return;
	}
	//fseek(fp, 0, SEEK_SET);
	
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png_ptr) {
		fprintf(stderr, "Cannot create read struct.\n");
		return;
	}
	
	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
		fprintf(stderr, "Cannot create info struct.\n");
		return;
	}
	
	end_info = png_create_info_struct(png_ptr);
	if (!end_info)
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
		fprintf(stderr, "Cannot create end info struct.\n");
		return;
	}
	
	if (setjmp(png_jmpbuf(png_ptr))) {
		fprintf(stderr, "Error: fatal.\n");
		return;
	}
	
	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, sizeof(magic));
	
	png_read_info(png_ptr, info_ptr);
	png_read_update_info (png_ptr, info_ptr);
	
	int width = png_get_image_width(png_ptr, info_ptr),
		height = png_get_image_height(png_ptr, info_ptr);
	printf("Image %i x %i\n", width, height);
	
	/* ignore all unknown chunks: */
	png_set_keep_unknown_chunks(png_ptr, 1, NULL, 0);
	/* except for our custom chunk: */
	png_set_keep_unknown_chunks(png_ptr, 2, custom_chunk, sizeof(custom_chunk)/5);
	/* also ignore unused known chunks: */
	png_set_keep_unknown_chunks(png_ptr, 1, unused_chunks, sizeof(unused_chunks)/5);
	
	png_set_read_user_chunk_fn(png_ptr, NULL, (png_user_chunk_ptr)read_chunk_custom);
	
	// Throw an CRC error when commented out !
	//png_read_end(png_ptr, end_info);
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
	
	fclose(fp);
}

int main(int argc, char **argv) {
	if (strcmp(argv[1], "-r") || strcmp(argv[1], "--read")) {
		readPNG(argv[2]);
	}
	return 0;
}

Output:

$ ./png-embedder -r file.png
read: file.png
Image 400 x 400

So the library is working, the file is correct and there is a tIME chunk in the file. I think this problem occurs because png_read_end is commented in my code. When I remove the comment signs the program gives me a CRC error and exit ! I don't get why it is happening ? Since the file is read without this line it shouldn't throw an error.

Can you give me a hand please ? Thank you in advance !

Your problem may be due to declaring IDAT an unknown chunk. Libpng needs to see at
least one IDAT chunk before it can do png_read_end() which reads any chunks after IDAT, including the IEND chunk.

Glenn

Unfortunately it isn't as easy as it seems to be. I've changed (diff) :

18c18
< 	'I', 'D', 'A', 'T', (png_byte) '\0' /* IDAT */
---
> 	//'I', 'D', 'A', 'T', (png_byte) '\0' 			/* IDAT */
102c102
< 	//png_read_end(png_ptr, end_info);
---
> 	png_read_end(png_ptr, end_info);

But :

$ ./png-embedder -r file2.png
read: file2.png
Image 1440 x 900
libpng error: IDAT: CRC error
Error: fatal.

I've tried other files but it remains the same. Could you try this code ? I'm using libpng 1.4.3 (latest). Note that the tIME chunk is before the IDAT ! My image viewer doesn't complain about any CRC error except if I modify the CRC by hand. So there must be a problem with my code and libpng...

You aren't setting your chunk handling stuff until after png_read_update_info(), so all the chunks before IDAT have already been read. Try moving line 85 and 86 down to just before the png_read_end() call.

Also your custom chunk reader should return an int, positive for success, negative for error, 0 for did not recognize the chunk. See example in pngtest.c that comes with libpng.

That example is slightly different from what you want to do, namely to handle an existing known chunk (tIME) as if it were unknown. In pngtest.c, the unknown chunks (vpAg and sTER) really are unknown to libpng.

commented: Really clear and made my problem go forward +0

You aren't setting your chunk handling stuff until after png_read_update_info(), so all the chunks before IDAT have already been read. Try moving line 85 and 86 down to just before the png_read_end() call.

Also your custom chunk reader should return an int, positive for success, negative for error, 0 for did not recognize the chunk. See example in pngtest.c that comes with libpng.

That example is slightly different from what you want to do, namely to handle an existing known chunk (tIME) as if it were unknown. In pngtest.c, the unknown chunks (vpAg and sTER) really are unknown to libpng.

Thank you, it's working (partially) now ! I didn't found pngtest.c relevant beforeward because it does't contain png_set_read_user_chunk_fn so I though it wasn't handling custom chunks. I've changed the return value of my function, used chunk->size instead of sizeof(chunk->data) to make it works.

So mustn't I use png_read_end at the end of the readPNG (because the CRC error is still present) ? The problem is now when the data is before the IDAT all is working but when the data is after IDAT it doesn't work. I think it results of this CRC error when it is present and because libpng doesn't read the rest of the file when it is commented. Does that mean the custom chunks MUST be before the IDAT one ? My file: [IHDR][sRGB][pHYs][tIME][tESt][IDAT][tEST][IEND]. The first "tESt" is handled but not the one after the IDAT (tried an other name too). What should I do now ?

Thank you for your great help !

Yes thank you, I didn't know you were part of the official ML. I will post the result here when I've finished reading all the mails.

The solution is to not ignore the IDAT and critial chunks of the file and to read the IDAT even if it's in a NULL pointer. The libpng will read the rest of the file only if the IDAT were read :

#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

FILE *fp;
png_structp png_ptr;
png_infop info_ptr, end_info;
png_byte magic[8];
png_byte custom_chunk[] = {
	't', 'I', 'M', 'E', (png_byte)'\0', 			/* tIME */
	't', 'E', 'X', 't', (png_byte)'\0', 			/* tEXt */
	't', 'E', 'S', 't', (png_byte)'\0', 			/* tESt */
};

void readPNG(char *file);
int read_chunk_custom(png_structp ptr, png_unknown_chunkp chunk);
int main(int argc, char **argv);

int read_chunk_custom(png_structp ptr, png_unknown_chunkp chunk) {
	static int unknown_numb = 0;
	char fileout[255];
	
	printf("Reading custom chunk [%s] @%p (%i octets)\n", chunk->name, chunk->data, chunk->size);
	sprintf(fileout, "./out-%i-%s.bin", unknown_numb, chunk->name);
	
	FILE *out = fopen(fileout, "wb");
	if (!out) {
		fprintf(stderr, "Cannot write the chunk to ouput.\n");
		return -1;
	}
	
	fwrite(chunk->data, 1, chunk->size, out);
	fclose(out);
	
	unknown_numb++;
	return 1;
}

void readPNG(char *file) {
	printf("read: %s\n", file);
	
	fp = fopen(file, "rb");
	if (!fp) {
		fprintf(stderr, "Cannot open %s.\n", file);
		return;
	}
	
	fread(magic, 1, sizeof(magic), fp);
	if (!png_check_sig(magic, sizeof(magic))) {
		fprintf(stderr, "error: %s isn't a valid PNG file.\n", file);
		return;
	}
	
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png_ptr) {
		fprintf(stderr, "Cannot create read struct.\n");
		return;
	}
	
	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
		fprintf(stderr, "Cannot create info struct.\n");
		return;
	}
	
	end_info = png_create_info_struct(png_ptr);
	if (!end_info)
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
		fprintf(stderr, "Cannot create end info struct.\n");
		return;
	}
	
	if (setjmp(png_jmpbuf(png_ptr))) {
		fprintf(stderr, "Error: fatal.\n");
		return;
	}
	
	// Setting unknown-chunk callback
	png_set_read_user_chunk_fn(png_ptr, NULL, (png_user_chunk_ptr)read_chunk_custom);
	
	/* except for our custom chunk: */
	png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_IF_SAFE, custom_chunk, sizeof(custom_chunk)/5);
	
	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, sizeof(magic));
	
	png_read_info(png_ptr, info_ptr);
	png_read_update_info (png_ptr, info_ptr);
	
	int width = png_get_image_width(png_ptr, info_ptr),
		height = png_get_image_height(png_ptr, info_ptr);
	printf("Image %i x %i\n", width, height);
	
	// Junking the IDAT
	unsigned int i;
	for (i = 0; i < height; i++)
		png_read_row(png_ptr, NULL, NULL);
	
	png_read_end(png_ptr, end_info);
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
	
	fclose(fp);
}

int main(int argc, char **argv) {
	if (strcmp(argv[1], "-r") || strcmp(argv[1], "--read")) {
		readPNG(argv[2]);
	}
	return 0;
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.