All formats

PNGJ

LanguageJava 8 or later (also Android)
LicenseApache 2.0
Repositorygithub.com/leonbloy/pngj
Documentationwiki, Javadoc
Depends onnothing (no java.awt or javax.imageio)
Read / writeyes / yes, all color types and bit depths, all chunks; APNG read (basic)

PNGJ is a low-level PNG encoder and decoder. It reads and writes one row at a time, so it processes images of any size in constant memory. It gives direct access to every chunk and keeps unknown chunks when you copy a file.

It works with raw scanlines: the values in a row depend on the image’s color type and bit depth (e.g. RGBA8 gives 4 ints per pixel). It has no resize or color conversion; use it for PNG-specific work like re-encoding, editing metadata, tiling huge images or generating images from data. It doesn’t use java.awt, so it runs on Android too. The last release is from 2015, but the format hasn’t changed in ways that matter for it.

Installation#

Maven:

XML
<dependency>
    <groupId>ar.com.hjg</groupId>
    <artifactId>pngj</artifactId>
    <version>2.1.0</version>
</dependency>

Gradle:

implementation 'ar.com.hjg:pngj:2.1.0'

Basic usage: read, change and write a PNG row by row#

This example halves the red channel of an 8-bit RGB or RGBA image and copies all safe chunks (text, gamma, etc.) to the output:

Java
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngReaderInt;
import ar.com.hjg.pngj.PngWriter;
import ar.com.hjg.pngj.chunks.ChunkCopyBehaviour;
import java.io.File;

public class DecreaseRed {
    public static void main(String[] args) {
        PngReaderInt reader = new PngReaderInt(new File("in.png"));
        System.out.println(reader.imgInfo);  // size, channels, bit depth
        int channels = reader.imgInfo.channels;
        if (channels < 3 || reader.imgInfo.bitDepth != 8) {
            throw new IllegalArgumentException("expected RGB8 or RGBA8");
        }
        PngWriter writer = new PngWriter(new File("out.png"), reader.imgInfo, true);
        writer.copyChunksFrom(reader.getChunksList(), ChunkCopyBehaviour.COPY_ALL_SAFE);

        while (reader.hasMoreRows()) {
            ImageLineInt line = reader.readRowInt();
            int[] scanline = line.getScanline();
            for (int x = 0; x < reader.imgInfo.cols; x++) {
                scanline[x * channels] /= 2;  // red
            }
            writer.writeRow(line);
        }
        reader.end();  // reads chunks after the image data
        writer.end();
    }
}

How to get PNG size and color type#

The constructor reads the header; imgInfo is available before any row is read:

Java
import ar.com.hjg.pngj.PngReader;
import java.io.File;

PngReader reader = new PngReader(new File("in.png"));
System.out.println(reader.imgInfo.cols + " x " + reader.imgInfo.rows
        + ", " + reader.imgInfo.bitDepth + " bit, alpha: " + reader.imgInfo.alpha
        + ", palette: " + reader.imgInfo.indexed);
reader.close();

How to create a PNG from scratch#

Java
import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineHelper;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngWriter;
import ar.com.hjg.pngj.chunks.PngChunkTextVar;
import java.io.File;

int cols = 256, rows = 100;
ImageInfo info = new ImageInfo(cols, rows, 8, false);  // 8 bits per channel, no alpha
PngWriter writer = new PngWriter(new File("gradient.png"), info, true);
writer.setCompLevel(9);
writer.getMetadata().setDpi(150.0);
writer.getMetadata().setText(PngChunkTextVar.KEY_Title, "Gradient");

ImageLineInt line = new ImageLineInt(info);
for (int x = 0; x < cols; x++) {
    ImageLineHelper.setPixelRGB8(line, x, x, 128, 255 - x);
}
for (int y = 0; y < rows; y++) {
    writer.writeRow(line);
}
writer.end();

How to read PNG text chunks#

Java
import ar.com.hjg.pngj.PngReader;
import ar.com.hjg.pngj.chunks.PngChunk;
import java.io.File;

PngReader reader = new PngReader(new File("in.png"));
reader.readSkippingAllRows();  // also loads chunks after IDAT
System.out.println("Title: " + reader.getMetadata().getTxtForKey("Title"));
for (PngChunk chunk : reader.getChunksList().getChunks()) {
    System.out.println(chunk.id + " " + chunk.getLen() + " bytes");
}
reader.close();

Tips#