libpng
| Language | C (usable from C++) |
|---|---|
| License | PNG Reference Library License v2 (permissive) |
| Repository | github.com/pnggroup/libpng |
| Documentation | libpng manual |
| Depends on | zlib |
| Read / write | yes / yes, all color types and bit depths, APNG in recent versions |
libpng is the reference implementation of PNG, maintained by the PNG Development Group. It’s used by most image programs, browsers’ older code paths, ImageMagick, GIMP and many others. It supports every chunk and every pixel format.
libpng has two APIs:
- the simplified API (
png_image, since 1.6): reads a whole image into a buffer in the pixel format you ask for, and writes a buffer to a file. Use it unless you need chunk-level control. - the classic API (
png_structp,png_infop): full control over transformations, chunks and row-by-row I/O. Errors are reported withsetjmp/longjmp.
Installation#
Shell
# Debian / Ubuntu
sudo apt install libpng-dev
# Fedora
sudo dnf install libpng-devel
# macOS
brew install libpng
# Windows (vcpkg)
vcpkg install libpng
With CMake:
cmake
find_package(PNG REQUIRED)
target_link_libraries(myapp PRIVATE PNG::PNG)
Basic usage: read and write a PNG#
The simplified API converts any PNG to the format you request (here 8-bit RGBA) and writes it back:
C
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
png_image image;
memset(&image, 0, sizeof(image));
image.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_file(&image, "in.png")) {
fprintf(stderr, "read error: %s\n", image.message);
return 1;
}
image.format = PNG_FORMAT_RGBA; // 4 bytes per pixel
png_bytep buffer = malloc(PNG_IMAGE_SIZE(image));
if (!buffer) {
png_image_free(&image);
return 1;
}
if (!png_image_finish_read(&image, NULL /*background*/, buffer, 0 /*row_stride*/, NULL /*colormap*/)) {
fprintf(stderr, "decode error: %s\n", image.message);
free(buffer);
return 1;
}
printf("%u x %u\n", image.width, image.height);
// buffer holds width*height RGBA pixels, top row first
if (!png_image_write_to_file(&image, "out.png", 0 /*convert_to_8bit*/, buffer, 0, NULL)) {
fprintf(stderr, "write error: %s\n", image.message);
}
free(buffer);
return 0;
}
How to get PNG width and height without decoding#
png_image_begin_read_from_file reads only the header, so the size is known before any pixels are decoded:
C
png_image image;
memset(&image, 0, sizeof(image));
image.version = PNG_IMAGE_VERSION;
if (png_image_begin_read_from_file(&image, "in.png")) {
printf("%u x %u, %s\n", image.width, image.height,
(image.format & PNG_FORMAT_FLAG_ALPHA) ? "has alpha" : "no alpha");
png_image_free(&image);
}
How to write a PNG from a pixel buffer#
C
#include <png.h>
#include <string.h>
int save_rgba(const char *path, const unsigned char *pixels, int width, int height) {
png_image image;
memset(&image, 0, sizeof(image));
image.version = PNG_IMAGE_VERSION;
image.width = width;
image.height = height;
image.format = PNG_FORMAT_RGBA; // or PNG_FORMAT_RGB, PNG_FORMAT_GRAY
return png_image_write_to_file(&image, path, 0, pixels, 0 /*stride: width*4*/, NULL);
}
How to read text chunks (tEXt, zTXt, iTXt)#
Text metadata needs the classic API. Text chunks that appear after the image data are only available after png_read_end:
C
#include <png.h>
#include <stdio.h>
int print_text(const char *path) {
FILE *fp = fopen(path, "rb");
if (!fp) return 1;
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
if (setjmp(png_jmpbuf(png))) { // libpng jumps here on error
png_destroy_read_struct(&png, &info, NULL);
fclose(fp);
return 1;
}
png_init_io(png, fp);
png_read_info(png, info);
png_textp text = NULL;
int num_text = 0;
png_get_text(png, info, &text, &num_text);
for (int i = 0; i < num_text; i++) {
printf("%s: %s\n", text[i].key, text[i].text);
}
png_destroy_read_struct(&png, &info, NULL);
fclose(fp);
return 0;
}
How to write a PNG with a compression level and text#
C
#include <png.h>
#include <stdio.h>
int write_png(const char *path, png_bytep *rows, int width, int height) {
FILE *fp = fopen(path, "wb");
if (!fp) return 1;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(fp);
return 1;
}
png_init_io(png, fp);
png_set_compression_level(png, 9); // 0 (none) .. 9 (smallest)
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_text text;
text.compression = PNG_TEXT_COMPRESSION_NONE;
text.key = "Software";
text.text = "my app";
png_set_text(png, info, &text, 1);
png_write_info(png, info);
png_write_image(png, rows); // rows[y] points to width*4 bytes
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(fp);
return 0;
}
Tips#
- With the classic API, call
png_set_expand,png_set_strip_16,png_set_gray_to_rgborpng_set_palette_to_rgbbeforepng_read_update_infoto get a uniform pixel format. The simplified API does this for you. png_set_sig_bytesandpng_sig_cmpcheck the 8-byte signature when you read the start of a file yourself.- Limit memory for untrusted input with
png_set_user_limits(png, max_width, max_height).