PNGJ
| Language | Java 8 or later (also Android) |
|---|---|
| License | Apache 2.0 |
| Repository | github.com/leonbloy/pngj |
| Documentation | wiki, Javadoc |
| Depends on | nothing (no java.awt or javax.imageio) |
| Read / write | yes / 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:
<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:
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:
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#
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#
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#
ChunkCopyBehaviour.COPY_ALL_SAFEcopies chunks that are safe to keep after pixel changes;COPY_ALLcopies everything.- Pass an
InputStreamorOutputStreaminstead of aFileto read from or write to the network. - PNGJ reads interlaced PNG files but doesn’t write them.