/* * #%L * OME library for reading the JPEG XR file format. * %% * Copyright (C) 2013 - 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 ome.jxr.parser; import java.io.IOException; import loci.common.RandomAccessInputStream; import ome.jxr.JXRException; import ome.jxr.constants.Image; import ome.jxr.image.BitDepth; import ome.jxr.image.ComponentMode; import ome.jxr.image.FrequencyBand; import ome.jxr.image.InternalColorFormat; import ome.jxr.image.OutputColorFormat; /** * Parses the initial elements (image header, image plane headers(-s)) of the * image codestream. Conducts validation of values according to the file format * specification. * * <dl> * * @author Blazej Pindelski bpindelski at dundee.ac.uk */ public final class DatastreamParser extends Parser { // Image header fields private int reservedB; private boolean hardTilingFlag; private boolean tilingFlag; private boolean frequencyModeCodestreamFlag; private int spatialXfrmSubordinate; private boolean indexTablePresentFlag; private int overlapMode; private boolean shortHeaderFlag; private boolean longWordFlag; private boolean windowingFlag; private boolean trimFlexbitsFlag; private int reservedD; private boolean redBlueNotSwappedFlag; private boolean premultipliedAlphaFlag; private boolean alphaImagePlaneFlag; private OutputColorFormat outputClrFmt; private BitDepth outputBitdepth; private int widthMinus1, heightMinus1; private int numVerTilesMinus1 = 1, numHorTilesMinus1 = 1; private short[] tileWidthInMB, tileHeightInMB; private int topMargin, leftMargin, bottomMargin, rightMargin; // Image plane header fields private InternalColorFormat internalClrFmt; private boolean scaledFlag; private FrequencyBand bandsPresent; private int chromaCenteringX = 0, chromaCenteringY = 0; private int numComponentsMinus1, numComponentsExtendedMinus16; private int numComponents; private int shiftBits; private int lenMantissa, expBias; private boolean lpImagePlaneUniformFlag, hpImagePlaneUniformFlag; public DatastreamParser(Parser parentParser, RandomAccessInputStream stream) throws JXRException { super(parentParser, stream); } @Override public void parse() throws JXRException { super.parse(parsingOffset); IFDParser ifdParser = (IFDParser) getParentParser(); parsingOffset = ifdParser.getIFDMetadata().getImageOffset(); try { checkGDISignaturePresence(); parseImageHeader(); verifyImageHeaderConformance(); parsePrimaryImagePlaneHeader(); verifyPrimaryImagePlaneHeaderConformance(); } catch (IOException ioe) { throw new JXRException(ioe); } } private void checkGDISignaturePresence() throws IOException, JXRException { stream.seek(parsingOffset); String signature = stream.readString(Image.GDI_SIGNATURE.length()); if (!Image.GDI_SIGNATURE.equals(signature)) { throw new JXRException("Missing required image signature."); } } private void parseImageHeader() throws IOException { reservedB = stream.readBits(4); hardTilingFlag = (stream.readBits(1) == 1); // Skip RESERVED_C in this version of the decoder stream.skipBits(3); tilingFlag = (stream.readBits(1) == 1); frequencyModeCodestreamFlag = (stream.readBits(1) == 1); spatialXfrmSubordinate = stream.readBits(3); indexTablePresentFlag = (stream.readBits(1) == 1); overlapMode = stream.readBits(2); shortHeaderFlag = (stream.readBits(1) == 1); longWordFlag = (stream.readBits(1) == 1); windowingFlag = (stream.readBits(1) == 1); trimFlexbitsFlag = (stream.readBits(1) == 1); // Skip RESERVED_D in this version of the decoder stream.skipBits(1); redBlueNotSwappedFlag = (stream.readBits(1) == 1); premultipliedAlphaFlag = (stream.readBits(1) == 1); alphaImagePlaneFlag = (stream.readBits(1) == 1); outputClrFmt = OutputColorFormat.findById(stream.readBits(4)); outputBitdepth = BitDepth.findById(stream.readBits(4)); if (shortHeaderFlag) { widthMinus1 = stream.readUnsignedShort(); heightMinus1 = stream.readUnsignedShort(); } else { widthMinus1 = stream.readInt(); heightMinus1 = stream.readInt(); } if (tilingFlag) { numVerTilesMinus1 = stream.readBits(12); numHorTilesMinus1 = stream.readBits(12); tileWidthInMB = new short[numVerTilesMinus1]; tileHeightInMB = new short[numHorTilesMinus1]; if (shortHeaderFlag) { for (int i = 0; i < numVerTilesMinus1; i++) { tileWidthInMB[i] = stream.readByte(); } for (int i = 0; i < numHorTilesMinus1; i++) { tileHeightInMB[i] = stream.readByte(); } } else { for (int i = 0; i < numVerTilesMinus1; i++) { tileWidthInMB[i] = stream.readShort(); } for (int i = 0; i < numHorTilesMinus1; i++) { tileHeightInMB[i] = stream.readShort(); } } } if (windowingFlag) { topMargin = stream.readBits(6); leftMargin = stream.readBits(6); bottomMargin = stream.readBits(6); rightMargin = stream.readBits(6); } } private void verifyImageHeaderConformance() throws JXRException { if (reservedB != Image.RESERVED_B) { throw new JXRException("Wrong value of RESERVED_B. " + "Expected: " + Image.RESERVED_B + ", found: " + reservedB); } if (spatialXfrmSubordinate > Image.SPATIAL_XFRM_SUBORDINATE_MAX) { throw new JXRException("Wrong value of SPATIAL_XFRM_SUBORDINATE. " + "Expected [0.." + Image.SPATIAL_XFRM_SUBORDINATE_MAX + "], " + "found: " + spatialXfrmSubordinate); } if ((frequencyModeCodestreamFlag || numVerTilesMinus1 > 0 || numHorTilesMinus1 > 0) && !indexTablePresentFlag) { throw new JXRException( "FREQUENCY_MODE_CODESTREAM_FLAG missing required values."); } if (overlapMode == Image.OVERLAP_MODE_RESERVED) { throw new JXRException("Reserved value of OVERLAP_MODE: " + overlapMode); } if (!OutputColorFormat.RGB.equals(outputClrFmt) && redBlueNotSwappedFlag) { throw new JXRException("Wrong value of RED_BLUE_NOT_SWAPPED_FLAG."); } if ((OutputColorFormat.YUV420.equals(outputClrFmt) || OutputColorFormat.YUV422 .equals(outputClrFmt)) && (widthMinus1 + 1) % 2 != 0) { throw new JXRException("Wrong value of WIDTH_MINUS1."); } } private void parsePrimaryImagePlaneHeader() throws IOException { internalClrFmt = InternalColorFormat.findById(stream.readBits(3)); scaledFlag = (stream.readBits(1) == 1); bandsPresent = FrequencyBand.findById(stream.readBits(4)); if (InternalColorFormat.YUV444.equals(internalClrFmt) || InternalColorFormat.YUV420.equals(internalClrFmt) || InternalColorFormat.YUV422.equals(internalClrFmt)) { if (InternalColorFormat.YUV420.equals(internalClrFmt) || InternalColorFormat.YUV422.equals(internalClrFmt)) { // Skip RESERVED_E_BIT in this version of the decoder stream.skipBits(1); chromaCenteringX = stream.readBits(3); } else { // Skip RESERVED_F in this version of the decoder stream.skipBits(4); } if (InternalColorFormat.YUV420.equals(internalClrFmt)) { // Skip RESERVED_G_BIT in this version of the decoder stream.skipBits(1); chromaCenteringY = stream.readBits(3); } else { // Skip RESERVED_H in this version of the decoder stream.skipBits(4); } } else if (InternalColorFormat.NCOMPONENT.equals(internalClrFmt)) { numComponentsMinus1 = stream.readBits(4); if (numComponentsMinus1 == 0xf) { numComponentsExtendedMinus16 = stream.readBits(12); } else { // Skip RESERVED_H in this version of the decoder stream.skipBits(4); stream.seek(stream.getFilePointer() - 1); } } if (InternalColorFormat.NCOMPONENT.equals(internalClrFmt)) { if (numComponentsMinus1 == 0xf) { numComponents = numComponentsExtendedMinus16 + 16; } else { numComponents = numComponentsMinus1 + 1; } } else if (InternalColorFormat.YONLY.equals(internalClrFmt)) { numComponents = 1; } else if (InternalColorFormat.YUV420.equals(internalClrFmt) || InternalColorFormat.YUV422.equals(internalClrFmt) || InternalColorFormat.YUV444.equals(internalClrFmt)) { numComponents = 3; } else if (InternalColorFormat.YUVK.equals(internalClrFmt)) { numComponents = 4; } if (BitDepth.BD16.equals(outputBitdepth) || BitDepth.BD16S.equals(outputBitdepth) || BitDepth.BD32S.equals(outputBitdepth)) { int shiftBits = stream.read(); } if (BitDepth.BD32F.equals(outputBitdepth)) { lenMantissa = stream.read(); expBias = stream.read(); } // TODO: This needs refactoring with a class allowing // for reading bits and bytes. How to skip a bit using // a byte pointer? boolean dcImagePlaneUniformFlag = (stream.readBits(1) == 1); if (dcImagePlaneUniformFlag) { stream.seek(stream.getFilePointer() - 1); stream.skipBits(2); ComponentMode componentMode = ComponentMode.UNIFORM; if (numComponents != 1) { componentMode = ComponentMode.findById(stream.readBits(2)); } if (ComponentMode.UNIFORM.equals(componentMode)) { int dcQuant = stream.readBits(8); } else if (ComponentMode.SEPARATE.equals(componentMode)) { int dcQuantLuma = stream.readBits(8); int dcQuantChroma = stream.readBits(8); } else if (ComponentMode.INDEPENDENT.equals(componentMode)) { int[] dcQuantCh = new int[numComponents]; for (int i = 0; i < numComponents; i++) { dcQuantCh[i] = stream.readBits(8); } } } if (!FrequencyBand.DCONLY.equals(bandsPresent)) { // Skip RESERVED_I_BIT in this version of the decoder stream.skipBits(1); lpImagePlaneUniformFlag = (stream.readBits(1) == 1); if (lpImagePlaneUniformFlag) { // NumLPQPs = 1 // LP_QP() } if (!FrequencyBand.NOHIGHPASS.equals(bandsPresent)) { // Skip RESERVED_J_BIT in this version of the decoder stream.skipBits(1); hpImagePlaneUniformFlag = (stream.readBits(1) == 1); if (hpImagePlaneUniformFlag) { // NumHPQPs = 1 // HP_QP() } } } while (!stream.isBitOnByteBoundary()) { stream.skipBits(1); } } private void verifyPrimaryImagePlaneHeaderConformance() throws JXRException { if (chromaCenteringX == 5 || chromaCenteringX == 6) { chromaCenteringX = 7; } if (chromaCenteringY == 5 || chromaCenteringY == 6) { chromaCenteringX = 7; } if (FrequencyBand.RESERVED.equals(bandsPresent)) { throw new JXRException("Reserved value of BANDS_PRESENT."); } } public void close() throws IOException { super.close(); } }