// // PhotoshopTiffReader.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.io.IOException; import java.util.ArrayList; import loci.common.ByteArrayHandle; import loci.common.RandomAccessInputStream; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.codec.Codec; import loci.formats.codec.CodecOptions; import loci.formats.codec.PackbitsCodec; import loci.formats.codec.ZlibCodec; import loci.formats.meta.MetadataStore; import loci.formats.tiff.IFD; import loci.formats.tiff.TiffParser; /** * PhotoshopTiffReader is the file format reader for * Adobe Photoshop TIFF files. * * <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/PhotoshopTiffReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/PhotoshopTiffReader.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Melissa Linkert melissa at glencoesoftware.com */ public class PhotoshopTiffReader extends BaseTiffReader { // -- Constants -- public static final int IMAGE_SOURCE_DATA = 37724; public static final int PACKBITS = 1; public static final int ZIP = 3; // -- Fields -- private RandomAccessInputStream tag; private long[] layerOffset; private int[] compression; private int[][] channelOrder; private String[] layerNames; // -- Constructor -- /** Constructs a new Photoshop TIFF reader. */ public PhotoshopTiffReader() { super("Adobe Photoshop TIFF", new String[] {"tif", "tiff"}); suffixSufficient = false; domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { TiffParser tp = new TiffParser(stream); tp.setDoCaching(false); IFD ifd = tp.getFirstIFD(); if (ifd == null) return false; return ifd.containsKey(IMAGE_SOURCE_DATA); } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { if (getSeries() == 0) return super.openBytes(no, buf, x, y, w, h); FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); int offsetIndex = 0; for (int i=1; i<getSeries(); i++) { offsetIndex += core[i].sizeC; } tag.seek(layerOffset[offsetIndex]); int bpp = FormatTools.getBytesPerPixel(getPixelType()); if (compression[getSeries() - 1] == PACKBITS || compression[getSeries() - 1] == ZIP) { Codec codec = compression[getSeries() - 1] == ZIP ? new ZlibCodec() : new PackbitsCodec(); CodecOptions options = new CodecOptions(); options.maxBytes = FormatTools.getPlaneSize(this) / getSizeC(); ByteArrayHandle pix = new ByteArrayHandle(); for (int c=0; c<getSizeC(); c++) { int index = channelOrder[getSeries() - 1][c]; tag.seek(layerOffset[offsetIndex + index]); pix.write(codec.decompress(tag, options)); } RandomAccessInputStream plane = new RandomAccessInputStream(pix); plane.seek(0); readPlane(plane, x, y, w, h, buf); plane.close(); pix = null; } else readPlane(tag, x, y, w, h, buf); return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { if (tag != null) tag.close(); tag = null; layerOffset = null; compression = null; layerNames = null; } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); CoreMetadata firstSeries = core[0]; byte[] b = (byte[]) ifds.get(0).getIFDValue(IMAGE_SOURCE_DATA); if (b == null) return; tag = new RandomAccessInputStream(b); tag.order(isLittleEndian()); String checkString = tag.readCString(); String signature, type; int length; while (tag.getFilePointer() < tag.length() - 12 && tag.getFilePointer() > 0) { signature = tag.readString(4); type = tag.readString(4); length = tag.readInt(); int skip = length % 4; if (skip != 0) skip = 4 - skip; if (type.equals("ryaL")) { int nLayers = (int) Math.abs(tag.readShort()); compression = new int[nLayers]; layerNames = new String[nLayers]; ArrayList<CoreMetadata> layerMetadata = new ArrayList<CoreMetadata>(); channelOrder = new int[nLayers][]; int[][] dataSize = new int[nLayers][]; int offsetCount = 0; for (int layer=0; layer<nLayers; layer++) { int top = tag.readInt(); int left = tag.readInt(); int bottom = tag.readInt(); int right = tag.readInt(); CoreMetadata layerCore = new CoreMetadata(); layerCore = new CoreMetadata(); layerCore.sizeX = right - left; layerCore.sizeY = bottom - top; layerCore.pixelType = getPixelType(); layerCore.sizeC = tag.readShort(); layerCore.sizeZ = 1; layerCore.sizeT = 1; layerCore.imageCount = 1; layerCore.rgb = isRGB(); layerCore.interleaved = isInterleaved(); layerCore.littleEndian = isLittleEndian(); layerCore.dimensionOrder = getDimensionOrder(); if (layerCore.sizeX == 0 || layerCore.sizeY == 0) { layerMetadata.clear(); break; } offsetCount += layerCore.sizeC; channelOrder[layer] = new int[layerCore.sizeC]; dataSize[layer] = new int[layerCore.sizeC]; for (int c=0; c<layerCore.sizeC; c++) { int channelID = tag.readShort(); if (channelID < 0) channelID = layerCore.sizeC - 1; channelOrder[layer][channelID] = c; dataSize[layer][c] = tag.readInt(); } tag.skipBytes(12); int len = tag.readInt(); long fp = tag.getFilePointer(); int mask = tag.readInt(); if (mask != 0) tag.skipBytes(mask); int blending = tag.readInt(); tag.skipBytes(blending); int nameLength = tag.read(); int pad = nameLength % 4; if (pad != 0) pad = 4 - pad; layerNames[layer] = tag.readString(nameLength + pad); addGlobalMeta("Layer name #" + layer, layerNames[layer]); tag.skipBytes((int) (fp + len - tag.getFilePointer())); layerMetadata.add(layerCore); } core = new CoreMetadata[layerMetadata.size() + 1]; core[0] = firstSeries; for (int i=0; i<layerMetadata.size(); i++) { core[i + 1] = layerMetadata.get(i); } nLayers = layerMetadata.size(); layerOffset = new long[offsetCount]; int nextOffset = 0; for (int layer=0; layer<nLayers; layer++) { for (int c=0; c<core[layer + 1].sizeC; c++) { long startFP = tag.getFilePointer(); compression[layer] = tag.readShort(); layerOffset[nextOffset] = tag.getFilePointer(); if (compression[layer] == ZIP) { layerOffset[nextOffset] = tag.getFilePointer(); ZlibCodec codec = new ZlibCodec(); codec.decompress(tag, null); } else if (compression[layer] == PACKBITS) { if (layer == 0) tag.skipBytes(256); else tag.skipBytes(192); layerOffset[nextOffset] = tag.getFilePointer(); PackbitsCodec codec = new PackbitsCodec(); CodecOptions options = new CodecOptions(); options.maxBytes = core[layer + 1].sizeX * core[layer + 1].sizeY; codec.decompress(tag, options); } tag.seek(startFP + dataSize[layer][c]); nextOffset++; } } } else tag.skipBytes(length + skip); } MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this); store.setImageName("Merged", 0); if (layerNames != null) { int end = (int) Math.min(getSeriesCount() - 1, layerNames.length); for (int layer=0; layer<end; layer++) { store.setImageName(layerNames[layer], layer + 1); } } } }