Skip to content

Instantly share code, notes, and snippets.

@niw
Last active November 12, 2023 19:54
Show Gist options
  • Save niw/5963798 to your computer and use it in GitHub Desktop.
Save niw/5963798 to your computer and use it in GitHub Desktop.
How to read and write PNG file using libpng. Covers trivial method calls like png_set_filler.
/*
* A simple libpng example program
* http://zarb.org/~gc/html/libpng.html
*
* Modified by Yoshimasa Niwa to make it much simpler
* and support all defined color_type.
*
* To build, use the next instruction on OS X.
* $ brew install libpng
* $ clang -lz -lpng16 libpng_test.c
*
* Copyright 2002-2010 Guillaume Cottenceau.
*
* This software may be freely redistributed under the terms
* of the X11 license.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <png.h>
int width, height;
png_byte color_type;
png_byte bit_depth;
png_bytep *row_pointers = NULL;
void read_png_file(char *filename) {
FILE *fp = fopen(filename, "rb");
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if(!png) abort();
png_infop info = png_create_info_struct(png);
if(!info) abort();
if(setjmp(png_jmpbuf(png))) abort();
png_init_io(png, fp);
png_read_info(png, info);
width = png_get_image_width(png, info);
height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);
// Read any color_type into 8bit depth, RGBA format.
// See http://www.libpng.org/pub/png/libpng-manual.txt
if(bit_depth == 16)
png_set_strip_16(png);
if(color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png);
if(png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
// These color_type don't have an alpha channel then fill it with 0xff.
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
if (row_pointers) abort();
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
for(int y = 0; y < height; y++) {
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png,info));
}
png_read_image(png, row_pointers);
fclose(fp);
png_destroy_read_struct(&png, &info, NULL);
}
void write_png_file(char *filename) {
int y;
FILE *fp = fopen(filename, "wb");
if(!fp) abort();
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) abort();
png_infop info = png_create_info_struct(png);
if (!info) abort();
if (setjmp(png_jmpbuf(png))) abort();
png_init_io(png, fp);
// Output is 8bit depth, RGBA format.
png_set_IHDR(
png,
info,
width, height,
8,
PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
png_write_info(png, info);
// To remove the alpha channel for PNG_COLOR_TYPE_RGB format,
// Use png_set_filler().
//png_set_filler(png, 0, PNG_FILLER_AFTER);
if (!row_pointers) abort();
png_write_image(png, row_pointers);
png_write_end(png, NULL);
for(int y = 0; y < height; y++) {
free(row_pointers[y]);
}
free(row_pointers);
fclose(fp);
png_destroy_write_struct(&png, &info);
}
void process_png_file() {
for(int y = 0; y < height; y++) {
png_bytep row = row_pointers[y];
for(int x = 0; x < width; x++) {
png_bytep px = &(row[x * 4]);
// Do something awesome for each pixel here...
//printf("%4d, %4d = RGBA(%3d, %3d, %3d, %3d)\n", x, y, px[0], px[1], px[2], px[3]);
}
}
}
int main(int argc, char *argv[]) {
if(argc != 3) abort();
read_png_file(argv[1]);
process_png_file();
write_png_file(argv[2]);
return 0;
}
@hooddanielc
Copy link

Thanks :3

@Rufflewind
Copy link

It might be a good idea to call png_destroy_read_struct to avoid leaking memory.

@xerrni
Copy link

xerrni commented Dec 27, 2015

TO avoid memory leaks just add:
in function:

write_png_file()

at the end add:

if (png && info)
        png_destroy_write_struct(&png, &info);

in function

 read_png_file()

at the end add:

png_destroy_read_struct(&png, &info, NULL);
    png=NULL;
    info=NULL;

@coderkan
Copy link

Thanks for your sample.
I want to ask something about row data. How can I get only row data in png image. No header no info only image data. I want to get a pointer to (char *) type?
Has any idea about it ?

@xerrni
Copy link

xerrni commented Jan 14, 2016

You have image data in row_pointers and you can access it directelly in process_png_file() function

@parada2016
Copy link

Great code. I am using with xlib and it is working well.

@emmeowzing
Copy link

The most useful part of this was process_png_file(); more specifically the lines png_bytep row = row_pointers[y]; and png_bytep px = &(row[x * 4]);. Thanks!

@carterMgj
Copy link

hello,I want to use libpng to parse png file. I have downloaded the libpng, and build it successfully. Could you tell me how to put your c file into the libpng project?

Copy link

ghost commented Nov 21, 2018

Great when i use clang.
But then i need to compile it using g++-8 and the error is: fatal error: png.h: No such file or directory

I'm on mac and I did:
$ brew install libpng

Please help to solve it.

@Soraiko
Copy link

Soraiko commented Jan 26, 2019

Cool, but the png.h is impossible to compile, which makes your code useless.
I'm tired of idiots unable to upload a working library. Yes, idiots. When my codes don't compile for my friends, I remove them.
Because there's nothing more unbearable than a programmer sharing the code of its super cool program when he's the only one on the earth that can compile it. Screw it.

@Soraiko
Copy link

Soraiko commented Jan 26, 2019

hello,I want to use libpng to parse png file. I have downloaded the libpng, and build it successfully. Could you tell me how to put your c file into the libpng project?

Allow me to call you a liar. You can NOT successfully build libpng and ignore how to include one single C file in a project.

@Erjona1
Copy link

Erjona1 commented Jan 30, 2019

Hello.
I have used a picture.png to write in a file, and then saved that file successfully as another image (picture1.png). But I dont want to save to a file, i want to send it directly to a usb thermal printer which is connected to my raspberry pi (/dev/usb/lp0). When i do that it doesn't print as an image but some weird Chinese letters.
If anyone can help i would be very grateful.

@jackw1111
Copy link

jackw1111 commented Feb 28, 2019

Very nice example usage. Thank you.

@Aardvajk
Copy link

Aardvajk commented Apr 4, 2019

This is awesome. Thank you so much.

@liamlangli
Copy link

png_get_rowbytes was the same, cache that would be better.

@niw
Copy link
Author

niw commented Jun 22, 2019

I realized my old gist has many comments... and so I decided to update it to cover next things.

  • Destroy read and write structs.
  • Update comments to use -lpng16, which is current Homebrew provided libpng version.
  • Check row_pointers for read and write.

@nurettin
Copy link

*> Cool, but the png.h is impossible to compile, which makes your code useless.

@Soraiko png.h is impossible to compile, because it is just a header. If you install cmake, you can download and build libpng library from sources.

  • download libpng from sourceforge,
  • download cmake and install,
  • go to libpng sources
  • make a build directory
  • run cmake in that directory, point at parent directory
  • select your host environment (visual studio, mingw, linux gcc) and target environment

that's it, I've done it a million times. On linux it is even easier.

  • wget sourceforgeurl.targ.z
  • tar zvxf package.tar.gz
  • cd package
  • mkdir build
  • cd build
  • cmake ..
  • make -j 4
  • done

@sensationTI
Copy link

sensationTI commented Mar 13, 2020

Somehow for me, the script won't execute this line png_read_info(png, info); and png_write_info(png, info);
Can anyone explain what's happening?

@perandersson
Copy link

Thanks for this snippet! It helped me a lot :)

@randomMesh
Copy link

randomMesh commented Feb 5, 2021

The original code checks for a valid PNG signature before trying to read the file. You claim to have simplified the code. Omitting sanity checks is a bad way to simplify.

You should add

	//check header magic
	{
		unsigned char header[8];
		fread(header, 1, 8, fp);

		if (png_sig_cmp(header, 0, 8))
		{
			std::cerr << "File " << filename << " is not recognized as a PNG file" << std::endl;
			fclose(fp);
			return;
		}
	}

right before png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); in read_png_file

and

png_set_sig_bytes(png, 8); // we already read the 8 signature bytes right before png_read_info(png, info);

(FYI: I have #include <iostream> that's why i can use std::cerr)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment