/* * #%L * OME Bio-Formats package for reading and converting biological file formats. * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * 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, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ package loci.formats.in; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import loci.common.ByteArrayHandle; import loci.common.Constants; import loci.common.DataTools; import loci.common.RandomAccessInputStream; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.ImageTools; import loci.formats.MetadataTools; import loci.formats.codec.NikonCodec; import loci.formats.codec.NikonCodecOptions; import loci.formats.meta.MetadataStore; import loci.formats.tiff.IFD; import loci.formats.tiff.IFDList; import loci.formats.tiff.PhotoInterp; import loci.formats.tiff.TiffCompression; import loci.formats.tiff.TiffParser; import loci.formats.tiff.TiffRational; /** * DNGReader is the file format reader for Canon DNG (TIFF) files. * * @author Melissa Linkert melissa at glencoesoftware.com */ public class DNGReader extends BaseTiffReader { // -- Constants -- /** Logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(DNGReader.class); private static final int CANON_TAG = 34665; private static final int TIFF_EPS_STANDARD = 37398; private static final int COLOR_MAP = 33422; private static final int WHITE_BALANCE_RGB_COEFFS = 16385; // -- Fields -- /** The original IFD. */ protected IFD original; private double[] whiteBalance; private Object cfaPattern; private byte[] lastPlane = null; private int lastIndex = -1; // -- Constructor -- /** Constructs a new DNG reader. */ public DNGReader() { super("DNG", new String[] {"cr2", "crw", "jpg", "thm", "wav", "tif", "tiff"}); suffixSufficient = false; domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ @Override public boolean isThisType(RandomAccessInputStream stream) throws IOException { TiffParser tp = new TiffParser(stream); IFD ifd = tp.getFirstIFD(); if (ifd == null) return false; boolean hasEPSTag = ifd.containsKey(TIFF_EPS_STANDARD); if (!hasEPSTag) { hasEPSTag = ifd.containsKey(CANON_TAG); } String make = ifd.getIFDTextValue(IFD.MAKE); String model = ifd.getIFDTextValue(IFD.MODEL); String software = ifd.getIFDTextValue(IFD.SOFTWARE); return make != null && make.indexOf("Canon") != -1 && hasEPSTag && (model == null || !model.endsWith("S1 IS")) && (software == null || software.indexOf("Canon") != -1); } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ @Override public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); IFD ifd = ifds.get(no); int[] bps = ifd.getBitsPerSample(); int dataSize = bps[0]; long[] byteCounts = ifd.getStripByteCounts(); long totalBytes = 0; for (long b : byteCounts) { totalBytes += b; } if (totalBytes == FormatTools.getPlaneSize(this) || bps.length > 1) { // don't call super.openBytes here // the pixel type of the image as stored in the TIFF is UINT8, // but we need to expand it out to UINT16 (based upon the white balance) if (tiffParser == null) { initTiffParser(); } byte[] b = new byte[buf.length / 2]; tiffParser.getSamples(ifds.get(0), b, x, y, w, h); for (int i=0; i<b.length; i++) { int c = isInterleaved() ? i % 3 : i / (b.length / 3); short v = (short) (b[i] & 0xff); v = adjustForWhiteBalance(v, c); DataTools.unpackBytes(v, buf, i * 2, 2, isLittleEndian()); } return buf; } if (lastPlane == null || lastIndex != no) { long[] offsets = ifd.getStripOffsets(); ByteArrayOutputStream src = new ByteArrayOutputStream(); for (int i=0; i<byteCounts.length; i++) { byte[] t = new byte[(int) byteCounts[i]]; in.seek(offsets[i]); in.read(t); src.write(t); } int[] colorMap = {1, 0, 2, 1}; // default color map short[] ifdColors = (short[]) ifd.get(COLOR_MAP); if (ifdColors != null && ifdColors.length >= colorMap.length) { boolean colorsValid = true; for (int q=0; q<colorMap.length; q++) { if (ifdColors[q] < 0 || ifdColors[q] > 2) { // found invalid channel index, use default color map instead colorsValid = false; break; } } if (colorsValid) { for (int q=0; q<colorMap.length; q++) { colorMap[q] = ifdColors[q]; } } } lastPlane = new byte[FormatTools.getPlaneSize(this)]; RandomAccessInputStream bb = new RandomAccessInputStream(new ByteArrayHandle(src.toByteArray())); src.close(); short[] pix = new short[getSizeX() * getSizeY() * 3]; for (int row=0; row<getSizeY(); row++) { int realRow = row; for (int col=0; col<getSizeX(); col++) { short val = (short) (bb.readBits(dataSize) & 0xffff); int mapIndex = (realRow % 2) * 2 + (col % 2); int redOffset = realRow * getSizeX() + col; int greenOffset = (getSizeY() + realRow) * getSizeX() + col; int blueOffset = (2 * getSizeY() + realRow) * getSizeX() + col; if (colorMap[mapIndex] == 0) { pix[redOffset] = adjustForWhiteBalance(val, 0); } else if (colorMap[mapIndex] == 1) { pix[greenOffset] = adjustForWhiteBalance(val, 1); } else if (colorMap[mapIndex] == 2) { pix[blueOffset] = adjustForWhiteBalance(val, 2); } } } bb.close(); ImageTools.interpolate(pix, buf, colorMap, getSizeX(), getSizeY(), isLittleEndian()); lastIndex = no; } int bpp = FormatTools.getBytesPerPixel(getPixelType()) * 3; int rowLen = w * bpp; int width = getSizeX() * bpp; for (int row=0; row<h; row++) { System.arraycopy( lastPlane, (row + y) * width + x * bpp, buf, row * rowLen, rowLen); } return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { original = null; whiteBalance = null; cfaPattern = null; lastPlane = null; lastIndex = -1; } } // -- Internal BaseTiffReader API methods -- /* @see BaseTiffReader#initStandardMetadata() */ @Override protected void initStandardMetadata() throws FormatException, IOException { super.initStandardMetadata(); // reset image dimensions // the actual image data is stored in IFDs referenced by the SubIFD tag // in the 'real' IFD CoreMetadata m = core.get(0); m.imageCount = ifds.size(); IFD firstIFD = ifds.get(0); PhotoInterp photo = firstIFD.getPhotometricInterpretation(); int samples = firstIFD.getSamplesPerPixel(); m.rgb = samples > 1 || photo == PhotoInterp.RGB || photo == PhotoInterp.CFA_ARRAY; if (photo == PhotoInterp.CFA_ARRAY) samples = 3; m.sizeX = (int) firstIFD.getImageWidth(); m.sizeY = (int) firstIFD.getImageLength(); m.sizeZ = 1; m.sizeC = isRGB() ? samples : 1; m.sizeT = ifds.size(); m.pixelType = FormatTools.UINT16; m.indexed = false; // now look for the EXIF IFD pointer IFDList exifIFDs = tiffParser.getExifIFDs(); if (exifIFDs.size() > 0) { IFD exifIFD = exifIFDs.get(0); tiffParser.fillInIFD(exifIFD); // put all the EXIF data in the metadata hashtable for (Integer key : exifIFD.keySet()) { int tag = key.intValue(); String name = IFD.getIFDTagName(tag); if (tag == IFD.CFA_PATTERN) { byte[] cfa = (byte[]) exifIFD.get(key); int[] colorMap = new int[cfa.length]; for (int i=0; i<cfa.length; i++) colorMap[i] = (int) cfa[i]; addGlobalMeta(name, colorMap); cfaPattern = colorMap; } else { addGlobalMeta(name, exifIFD.get(key)); if (name.equals("MAKER_NOTE")) { byte[] b = (byte[]) exifIFD.get(key); int offset = DataTools.bytesToInt(b, b.length - 4, isLittleEndian()); byte[] buf = new byte[b.length + offset - 8]; System.arraycopy(b, b.length - 8, buf, 0, 8); System.arraycopy(b, 0, buf, offset, b.length - 8); RandomAccessInputStream makerNote = new RandomAccessInputStream(buf); TiffParser tp = new TiffParser(makerNote); IFD note = null; try { note = tp.getFirstIFD(); } catch (Exception e) { LOGGER.debug("Failed to parse first IFD", e); } if (note != null) { for (Integer nextKey : note.keySet()) { int nextTag = nextKey.intValue(); addGlobalMeta(name, note.get(nextKey)); if (nextTag == WHITE_BALANCE_RGB_COEFFS) { if (note.get(nextTag) instanceof TiffRational[]) { TiffRational[] wb = (TiffRational[]) note.get(nextTag); whiteBalance = new double[wb.length]; for (int i=0; i<wb.length; i++) { whiteBalance[i] = wb[i].doubleValue(); } } else { // use a default white balance table whiteBalance = new double[3]; whiteBalance[0] = 2.391381; whiteBalance[1] = 0.929156; whiteBalance[2] = 1.298254; } } } } makerNote.close(); } } } } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ @Override protected void initFile(String id) throws FormatException, IOException { super.initFile(id); original = ifds.get(0); if (cfaPattern != null) { original.putIFDValue(IFD.COLOR_MAP, (int[]) cfaPattern); } ifds.set(0, original); CoreMetadata m = core.get(0); m.imageCount = 1; m.sizeT = 1; if (ifds.get(0).getSamplesPerPixel() == 1) { m.interleaved = true; } MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this); } // -- Helper methods -- private short adjustForWhiteBalance(short val, int index) { if (whiteBalance != null && whiteBalance.length == 3) { return (short) (val * whiteBalance[index]); } return val; } }