-
Notifications
You must be signed in to change notification settings - Fork 52
Snippets
*WARNING: Some snippets are still not adapted/optimized/tested for version 2.0 *
Since 2.1.0 the test source (not included in the main jar) has a package ar.com.hjg.pngj.cli which includes some command line utilities based on PNG.
This is basically the code for SampleMirrorImage
class in the samples. Notice that this works for every image model - that's because even for the "packed" formats (low bitdepth: one byte packs several samples of 1-2-4 bits) we always have (if we use the standard ImageLineInt
storage) one sample per array element.
void static mirrorPng(String orig,String dest) {
PngReader pngr = new PngReader(orig);
PngWriter pngw = new PngWriter(dest, pngr.imgInfo, false);
pngw.copyChunksFrom(pngr.getChunksList());
for (int row = 0; row < pngr.imgInfo.rows; row++) {
ImageLineInt line = (ImageLineInt)pngr.readRow();
mirrorLine(line, pngr.imgInfo);
pngw.writeRow(line);
}
pngr.end();
pngw.end();
}
void static mirrorLine(ImageLineInt line, ImageInfo iminfo) {
int channels = iminfo.channels;
int[imlinei = imline.getScanline();
for (int c1 = 0, c2 = iminfo.cols - 1; c1 < c2; c1++, c2--) {
for (int i = 0; i < channels; i++) {
int s1 = c1 * channels + i; // sample left
int s2 = c2 * channels + i; // sample right
int aux = imlinei[s1];
imlinei[s1] = imlinei[s2];
}
}
}
Two notes about memory use:
- The example uses
ImageLineInt
which wraps an integer array withcols x channels
elements; the memory usage is then aboutcols x channels x 4
. By usingPngReaderByte
we can load each line in anImageLineByte
, reducing the memory by a factor of 4 - but we'd loose resolution if image has 16bits depth. - The above is not true if the image is interlaced (not frequent, and not recommended), in which case the full image must actually be loaded. This is transparent to the programmer, except that the memory usage multiplies by
rows
. This is practically unavoidable. Of course, we call always callif(pngr.isInterlaced())
to detect and disallow interlaced images if we wish to do so.
This loads all chunks, skipping pixels data, and process the textual chunks. Bear in mind that there are three types of text chunks, and iTxt
includes some extra info. Also, remember that textual chunks with repeated keys are allowed.
PngReader pngr = new PngReader(file);
pngr.readSkippingAllRows(); // reads only metadata
for (PngChunk c : pngr.getChunksList().getChunks()) {
if (!ChunkHelper.isText(c)) continue;
PngChunkTextVar ct = (PngChunkTextVar) c;
String key = ct.getKey();
String val = ct.getVal();
// ...
}
pngr.end(); // not necessary here, but good practice
*TODO: in version 2.0 this could be done much more efficiently. PENDING *
PNGJ is, by design, decoupled from java.awt.*
. If you want to write or read from a BufferedImage
, you must adapt the format. For example, the following code writes a BufferedImage
of type TYPE_INT_ARGB
to a RGBA8 PNG image:
/**
-
- @param bi BufferedImage of TYPE_INT_ARGB or TYPE_INT_RGB
- @param os
**/
public static void writeARGB(BufferedImage bi, OutputStream os) {
if(bi.getType() != BufferedImage.TYPE_INT_ARGB) throw new PngjException("This method expects BufferedImage.TYPE_INT_ARGB" );
ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
PngWriter pngw = new PngWriter(os, imi);
// pngw.setCompLevel(6); // tuning
// pngw.setFilterType(FilterType.FILTER_PAETH); // tuning
DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
if(db.getNumBanks()!=1) throw new PngjException("This method expects one bank");
SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) bi.getSampleModel();
ImageLine line = new ImageLine(imi);
int[dbbuf = db.getData();
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++) {
int sample = dbbuf[elem++];
line.scanline[j++] = (sample & 0xFF0000)>>16; // R
line.scanline[j++] = (sample & 0xFF00)>>8; // G
line.scanline[j++] = (sample & 0xFF); // B
line.scanline[j++] = (((sample & 0xFF000000)>>24)&0xFF); // A
} }
pngw.writeRow(line, row);
}
pngw.end();
}
for version 2.x version the "BufferedImage to a png file" will be this :
/** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
public static void writePNGJARGB(BufferedImage bi, /*OutputStream os, */File file) { // OutputStream or File
if(bi.getType() != BufferedImage.TYPE_INT_ARGB) throw new PngjException("This method expects BufferedImage.TYPE_INT_ARGB" );
ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
PngWriter pngw = new PngWriter(file, imi, false);
// PngWriter pngw = new PngWriter(file,imginfo,overwrite); //params
pngw.setCompLevel(7); // tuning compression, not critical usually
pngw.setFilterType(FilterType.FILTER_PAETH); // tuning, see what you prefer here
System.out.println("..... PNGj metadata = "+pngw.getMetadata() );
DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
if(db.getNumBanks()!=1) {
throw new PngjException("This method expects one bank");
}
SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) bi.getSampleModel();
ImageLineInt line = new ImageLineInt(imi);
int[] dbbuf = db.getData();
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++) {
int sample = dbbuf[elem++];
line.getScanline()[j++] = (sample & 0xFF0000)>>16; // R
line.getScanline()[j++] = (sample & 0xFF00)>>8; // G
line.getScanline()[j++] = (sample & 0xFF); // B
line.getScanline()[j++] = (((sample & 0xFF000000)>>24)&0xFF); // A
}
//pngw.writeRow(line, /*imi.rows*/); // rows not needed anymore (?)
pngw.writeRow(line);
}
pngw.end();
}
To write a TYPE_4BYTE_ABGR
image (again, to PNGA8 ), you'd change the data buffer access, eg:
...
DataBufferByte db =((DataBufferByte) bi.getRaster().getDataBuffer());
if(db.getNumBanks()!=1) throw new PngjException("This method expects one bank");
ComponentSampleModel samplemodel = (ComponentSampleModel) bi.getSampleModel();
ImageLine line = new ImageLine(imi);
byte[dbbuf = db.getData();
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++,elem+=7) {
line.scanline[j++] = dbbuf[elem--]; // R
line.scanline[j++] = dbbuf[elem--]; // G
line.scanline[j++] = dbbuf[elem--]; // B
line.scanline[j++] = dbbuf[elem]; //A
}
pngw.writeRow(line, row);
}
...
/**
- Takes several tiles and join them in a single image
-
- @param tiles Filenames of PNG files to tile
- @param dest Destination PNG filename
- @param nTilesX How many tiles per row?
*/
public class SampleTileImage {
public static void doTiling(String tiles[](elem];), String dest, int nTilesX) {
int ntiles = tiles.length;
int nTilesY = (ntiles + nTilesX - 1) / nTilesX; // integer ceil
ImageInfo imi1, imi2; // 1:small tile 2:big image
PngReader pngr = new PngReader(new File(tiles[imi1 = pngr.imgInfo;
PngReader[](0]));) readers = new PngReader[imi2 = new ImageInfo(imi1.cols * nTilesX, imi1.rows * nTilesY, imi1.bitDepth, imi1.alpha, imi1.greyscale,
imi1.indexed);
PngWriter pngw = new PngWriter(new File(dest), imi2, true);
// copy palette and transparency if necessary (more chunks?)
pngw.copyChunksFrom(pngr.getChunksList(), ChunkCopyBehaviour.COPY_PALETTE
| ChunkCopyBehaviour.COPY_TRANSPARENCY);
pngr.readSkippingAllRows(); // reads only metadata
pngr.end(); // close, we'll reopen it again soon
ImageLineInt line2 = new ImageLineInt(imi2);
int row2 = 0;
for (int ty = 0; ty < nTilesY; ty++) {
int nTilesXcur = ty < nTilesY - 1 ? nTilesX : ntiles - (nTilesY - 1) * nTilesX;
Arrays.fill(line2.getScanline(), 0);
for (int tx = 0; tx < nTilesXcur; tx++) { // open serveral readers
readers[tx](nTilesX];) = new PngReader(new File(tiles[+ ty * nTilesX](tx)));
readers[if (!readers[tx](tx].setChunkLoadBehaviour(ChunkLoadBehaviour.LOAD_CHUNK_NEVER);).imgInfo.equals(imi1))
throw new RuntimeException("different tile ? " + readers[}
for (int row1 = 0; row1 < imi1.rows; row1++, row2++) {
for (int tx = 0; tx < nTilesXcur; tx++) {
ImageLineInt line1 = (ImageLineInt) readers[tx](tx].imgInfo);).readRow(row1); // read line
System.arraycopy(line1.getScanline(), 0, line2.getScanline(), line1.getScanline().length ** tx,
line1.getScanline().length);
}
pngw.writeRow(line2, row2); // write to full image
}
for (int tx = 0; tx < nTilesXcur; tx++)
readers[// close readers
}
pngw.end(); // close writer
}
public static void main(String[](tx].end();) args) {
doTiling(new String[{ "t1.png", "t2.png", "t3.png", "t4.png", "t5.png", "t6.png" }, "tiled.png", 2);
System.out.println("done");
}
}
PNGJ has basic support for APNG reading
public class ApngSplit {
private static final String PREFIX = "apngf";
/** reads a APNG file and tries to split it into its frames */
public static void process(File orig) throws Exception {
PngReaderApng pngr = new PngReaderApng(orig);
int numFrames = pngr.getApngNumFrames();
PngWriter[] dests = new PngWriter[numFrames];
ChunkPredicate copyPolicy = new ChunkPredicate(){
public boolean match(PngChunk chunk) {
if (chunk.safe) return true;
switch(chunk.id) {
case ChunkHelper.PLTE:
case ChunkHelper.tRNS:
case ChunkHelper.bKGD:
case ChunkHelper.gAMA:
case ChunkHelper.iCCP:
case ChunkHelper.cHRM:
case ChunkHelper.sBIT:
case ChunkHelper.sPLT:
case ChunkHelper.sRGB:
return true;
default:
return false;
}
}
};
for (int i = pngr.hasExtraStillImage() ? -1 : 0 ; i < numFrames; i++) {
pngr.advanceToFrame(i);
File dest = new File(orig.getParent(), PREFIX + i + "_" + orig.getName());
PngWriter pngw = new PngWriter(dest, pngr.imgInfo, true);
System.out.println("writing frame " + i);
pngw.copyChunksFrom(pngr.getChunksList(), copyPolicy);
for (int row = 0; row < pngr.imgInfo.rows; row++) {
pngw.writeRow(pngr.readRow(), row);
}
dests[i] = pngw;
}
pngr.end();
for(int i = 0; i < numFrames; i++) {
dests[i].end();
}
}
public static void main(String[] args) throws Exception {
process(new File("C:/temp/029.png"));
}
}