// // APNGReader.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats.in; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.IOException; import java.util.Vector; import java.util.zip.CRC32; import javax.imageio.ImageIO; import loci.common.DataTools; import loci.common.RandomAccessInputStream; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.gui.AWTImageTools; import loci.formats.meta.MetadataStore; /** * APNGReader is the file format reader for * Animated Portable Network Graphics (APNG) images. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/APNGReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/APNGReader.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Melissa Linkert melissa at glencoesoftware.com */ public class APNGReader extends BIFormatReader { // -- Constants -- private static final byte[] PNG_SIGNATURE = new byte[] { (byte) 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; // -- Fields -- private Vector<PNGBlock> blocks; private Vector<int[]> frameCoordinates; private byte[][] lut; private BufferedImage lastImage; private int lastImageIndex = -1; // -- Constructor -- /** Constructs a new APNGReader. */ public APNGReader() { super("Animated PNG", "png"); domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; suffixNecessary = false; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { final int blockLen = 8; if (!FormatTools.validStream(stream, blockLen, false)) return false; byte[] signature = new byte[blockLen]; stream.read(signature); if (signature[0] != (byte) 0x89 || signature[1] != 0x50 || signature[2] != 0x4e || signature[3] != 0x47 || signature[4] != 0x0d || signature[5] != 0x0a || signature[6] != 0x1a || signature[7] != 0x0a) { return false; } return true; } /* @see loci.formats.IFormatReader#get8BitLookupTable() */ public byte[][] get8BitLookupTable() { FormatTools.assertId(currentId, true, 1); return lut; } /* @see loci.formats.IFormatReader#openPlane(int, int, int, int, int int) */ public Object openPlane(int no, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.checkPlaneParameters(this, no, -1, x, y, w, h); if (no == lastImageIndex && lastImage != null) { return AWTImageTools.getSubimage(lastImage, isLittleEndian(), x, y, w, h); } if (no == 0) { in.seek(0); DataInputStream dis = new DataInputStream(new BufferedInputStream(in, 4096)); lastImage = ImageIO.read(dis); lastImageIndex = 0; if (x == 0 && y == 0 && w == getSizeX() && h == getSizeY()) { return lastImage; } return AWTImageTools.getSubimage(lastImage, isLittleEndian(), x, y, w, h); } ByteArrayOutputStream stream = new ByteArrayOutputStream(); stream.write(PNG_SIGNATURE); boolean fdatValid = false; int fctlCount = 0; int[] coords = frameCoordinates.get(no); for (PNGBlock block : blocks) { if (!block.type.equals("IDAT") && !block.type.equals("fdAT") && !block.type.equals("acTL") && !block.type.equals("fcTL") && block.length > 0) { byte[] b = new byte[block.length + 12]; DataTools.unpackBytes(block.length, b, 0, 4, isLittleEndian()); byte[] typeBytes = block.type.getBytes(); System.arraycopy(typeBytes, 0, b, 4, 4); in.seek(block.offset); in.read(b, 8, b.length - 12); if (block.type.equals("IHDR")) { DataTools.unpackBytes(coords[2], b, 8, 4, isLittleEndian()); DataTools.unpackBytes(coords[3], b, 12, 4, isLittleEndian()); } int crc = (int) computeCRC(b, b.length - 4); DataTools.unpackBytes(crc, b, b.length - 4, 4, isLittleEndian()); stream.write(b); b = null; } else if (block.type.equals("fcTL")) { fdatValid = fctlCount == no; fctlCount++; } else if (block.type.equals("fdAT")) { in.seek(block.offset + 4); if (fdatValid) { byte[] b = new byte[block.length + 8]; DataTools.unpackBytes(block.length - 4, b, 0, 4, isLittleEndian()); b[4] = 'I'; b[5] = 'D'; b[6] = 'A'; b[7] = 'T'; in.read(b, 8, b.length - 12); int crc = (int) computeCRC(b, b.length - 4); DataTools.unpackBytes(crc, b, b.length - 4, 4, isLittleEndian()); stream.write(b); b = null; } } } RandomAccessInputStream s = new RandomAccessInputStream(stream.toByteArray()); DataInputStream dis = new DataInputStream(new BufferedInputStream(s, 4096)); BufferedImage b = ImageIO.read(dis); dis.close(); lastImage = null; openPlane(0, 0, 0, getSizeX(), getSizeY()); // paste current image onto first image WritableRaster firstRaster = lastImage.getRaster(); WritableRaster currentRaster = b.getRaster(); firstRaster.setDataElements(coords[0], coords[1], currentRaster); lastImage = new BufferedImage(lastImage.getColorModel(), firstRaster, false, null); lastImageIndex = no; return lastImage; } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { lut = null; frameCoordinates = null; blocks = null; lastImage = null; lastImageIndex = -1; } } // -- Internal FormatReader methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); in = new RandomAccessInputStream(id); // check that this is a valid PNG file byte[] signature = new byte[8]; in.read(signature); if (signature[0] != (byte) 0x89 || signature[1] != 0x50 || signature[2] != 0x4e || signature[3] != 0x47 || signature[4] != 0x0d || signature[5] != 0x0a || signature[6] != 0x1a || signature[7] != 0x0a) { throw new FormatException("Invalid PNG signature."); } // read data chunks - each chunk consists of the following: // 1) 32 bit length // 2) 4 char type // 3) 'length' bytes of data // 4) 32 bit CRC blocks = new Vector<PNGBlock>(); frameCoordinates = new Vector<int[]>(); while (in.getFilePointer() < in.length()) { int length = in.readInt(); String type = in.readString(4); PNGBlock block = new PNGBlock(); block.length = length; block.type = type; block.offset = in.getFilePointer(); blocks.add(block); if (type.equals("acTL")) { // APNG-specific chunk core[0].imageCount = in.readInt(); int loop = in.readInt(); addGlobalMeta("Loop count", loop); } else if (type.equals("fcTL")) { in.skipBytes(4); int w = in.readInt(); int h = in.readInt(); int x = in.readInt(); int y = in.readInt(); frameCoordinates.add(new int[] {x, y, w, h}); in.skipBytes(length - 20); } else in.skipBytes(length); if (in.getFilePointer() < in.length() - 4) { in.skipBytes(4); // skip the CRC } } if (core[0].imageCount == 0) core[0].imageCount = 1; core[0].sizeZ = 1; core[0].sizeT = getImageCount(); core[0].dimensionOrder = "XYCTZ"; core[0].interleaved = false; RandomAccessInputStream ras = new RandomAccessInputStream(currentId); DataInputStream dis = new DataInputStream(ras); BufferedImage img = ImageIO.read(dis); dis.close(); core[0].sizeX = img.getWidth(); core[0].sizeY = img.getHeight(); core[0].rgb = img.getRaster().getNumBands() > 1; core[0].sizeC = img.getRaster().getNumBands(); core[0].pixelType = AWTImageTools.getPixelType(img); core[0].indexed = img.getColorModel() instanceof IndexColorModel; core[0].falseColor = false; if (isIndexed()) { lut = new byte[3][256]; IndexColorModel model = (IndexColorModel) img.getColorModel(); model.getReds(lut[0]); model.getGreens(lut[1]); model.getBlues(lut[2]); } MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this); MetadataTools.setDefaultCreationDate(store, id, 0); } // -- Helper methods -- private long computeCRC(byte[] buf, int len) { CRC32 crc = new CRC32(); crc.update(buf, 0, len); return crc.getValue(); } // -- Helper class -- class PNGBlock { public long offset; public int length; public String type; } }