/*
* #%L
* Fork of JAI Image I/O Tools.
* %%
* Copyright (C) 2008 - 2014 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of any organization.
* #L%
*/
/*
* $RCSfile: J2KImageReader.java,v $
*
*
* Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for
* use in the design, construction, operation or maintenance of any
* nuclear facility.
*
* $Revision: 1.7 $
* $Date: 2006/10/05 01:08:30 $
* $State: Exp $
*/
package com.sun.media.imageioimpl.plugins.jpeg2000;
import java.awt.Rectangle;
import java.awt.Point;
import javax.imageio.IIOException;
import javax.imageio.ImageReader;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import com.sun.media.imageio.plugins.jpeg2000.J2KImageReadParam;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.*;
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
import jj2000.j2k.quantization.dequantizer.*;
import jj2000.j2k.wavelet.synthesis.*;
import jj2000.j2k.image.invcomptransf.*;
import jj2000.j2k.fileformat.reader.*;
import jj2000.j2k.codestream.reader.*;
import jj2000.j2k.entropy.decoder.*;
import jj2000.j2k.codestream.*;
import jj2000.j2k.decoder.*;
import jj2000.j2k.image.*;
import jj2000.j2k.util.*;
import jj2000.j2k.roi.*;
import jj2000.j2k.io.*;
import jj2000.j2k.*;
/** This class is the Java Image IO plugin reader for JPEG 2000 JP2 image file
* format. It has the capability to load the compressed bilevel images,
* color-indexed byte images, or multi-band images in byte/ushort/short/int
* data type. It may subsample the image, select bands, clip the image,
* and shift the decoded image origin if the proper decoding parameter
* are set in the provided <code>J2KImageReadParam</code>.
*/
public class J2KImageReader extends ImageReader implements MsgLogger {
/** The input stream where reads from */
private ImageInputStream iis = null;
/** Stream position when setInput() was called. */
private long streamPosition0;
/** Indicates whether the header is read. */
private boolean gotHeader = false;
/** The image width. */
private int width;
/** The image height. */
private int height;
/** Image metadata, valid for the imageMetadataIndex only. */
private J2KMetadata imageMetadata = null;
/** The image index for the cached metadata. */
private int imageMetadataIndex = -1;
/** The J2K HeaderDecoder defined in jj2000 packages. Used to extract image
* header information.
*/
private HeaderDecoder hd;
/** The J2KReadState for this reading session based on the current input
* and J2KImageReadParam.
*/
private J2KReadState readState = null;
/**
* Whether to log JJ2000 messages.
*/
private boolean logJJ2000Msg = false;
/** Wrapper for the protected method <code>computeRegions</code>. So it
* can be access from the classes which are not in <code>ImageReader</code>
* hierarchy.
*/
public static void computeRegionsWrapper(ImageReadParam param,
boolean allowZeroDestOffset,
int srcWidth,
int srcHeight,
BufferedImage image,
Rectangle srcRegion,
Rectangle destRegion) {
if (srcRegion == null) {
throw new IllegalArgumentException(I18N.getString("J2KImageReader0"));
}
if (destRegion == null) {
throw new IllegalArgumentException(I18N.getString("J2KImageReader1"));
}
// Clip that to the param region, if there is one
int periodX = 1;
int periodY = 1;
int gridX = 0;
int gridY = 0;
if (param != null) {
Rectangle paramSrcRegion = param.getSourceRegion();
if (paramSrcRegion != null) {
srcRegion.setBounds(srcRegion.intersection(paramSrcRegion));
}
periodX = param.getSourceXSubsampling();
periodY = param.getSourceYSubsampling();
gridX = param.getSubsamplingXOffset();
gridY = param.getSubsamplingYOffset();
srcRegion.translate(gridX, gridY);
srcRegion.width -= gridX;
srcRegion.height -= gridY;
if(allowZeroDestOffset) {
destRegion.setLocation(param.getDestinationOffset());
} else {
Point destOffset = param.getDestinationOffset();
if(destOffset.x != 0 || destOffset.y != 0) {
destRegion.setLocation(param.getDestinationOffset());
}
}
}
// Now clip any negative destination offsets, i.e. clip
// to the top and left of the destination image
if (destRegion.x < 0) {
int delta = -destRegion.x*periodX;
srcRegion.x += delta;
srcRegion.width -= delta;
destRegion.x = 0;
}
if (destRegion.y < 0) {
int delta = -destRegion.y*periodY;
srcRegion.y += delta;
srcRegion.height -= delta;
destRegion.y = 0;
}
// Now clip the destination Region to the subsampled width and height
int subsampledWidth = (srcRegion.width + periodX - 1)/periodX;
int subsampledHeight = (srcRegion.height + periodY - 1)/periodY;
destRegion.width = subsampledWidth;
destRegion.height = subsampledHeight;
// Now clip that to right and bottom of the destination image,
// if there is one, taking subsampling into account
if (image != null) {
Rectangle destImageRect = new Rectangle(0, 0,
image.getWidth(),
image.getHeight());
destRegion.setBounds(destRegion.intersection(destImageRect));
if (destRegion.isEmpty()) {
throw new IllegalArgumentException
(I18N.getString("J2KImageReader2"));
}
int deltaX = destRegion.x + subsampledWidth - image.getWidth();
if (deltaX > 0) {
srcRegion.width -= deltaX*periodX;
}
int deltaY = destRegion.y + subsampledHeight - image.getHeight();
if (deltaY > 0) {
srcRegion.height -= deltaY*periodY;
}
}
if (srcRegion.isEmpty() || destRegion.isEmpty()) {
throw new IllegalArgumentException(I18N.getString("J2KImageReader3"));
}
}
/** Wrapper for the protected method <code>checkReadParamBandSettings</code>.
* So it can be access from the classes which are not in
* <code>ImageReader</code> hierarchy.
*/
public static void checkReadParamBandSettingsWrapper(ImageReadParam param,
int numSrcBands,
int numDstBands) {
checkReadParamBandSettings(param, numSrcBands, numDstBands);
}
/**
* Convert a rectangle provided in the coordinate system of the JPEG2000
* reference grid to coordinates at a lower resolution level where zero
* denotes the lowest resolution level.
*
* @param r A rectangle in references grid coordinates.
* @param maxLevel The highest resolution level in the image.
* @param level The resolution level of the returned rectangle.
* @param subX The horizontal subsampling step size.
* @param subY The vertical subsampling step size.
* @return The parameter rectangle converted to a lower resolution level.
* @throws IllegalArgumentException if <core>r</code> is <code>null</code>,
* <code>maxLevel</code> or <code>level</code> is negative, or
* <code>level</code> is greater than <code>maxLevel</code>.
*/
static Rectangle getReducedRect(Rectangle r, int maxLevel, int level,
int subX, int subY) {
if(r == null) {
throw new IllegalArgumentException("r == null!");
} else if(maxLevel < 0 || level < 0) {
throw new IllegalArgumentException("maxLevel < 0 || level < 0!");
} else if(level > maxLevel) {
throw new IllegalArgumentException("level > maxLevel");
}
// At the highest level; return the parameter.
if(level == maxLevel && subX == 1 && subY == 1) {
return r;
}
// Resolution divisor is step*2^(maxLevel - level).
int divisor = 1 << (maxLevel - level);
int divX = divisor*subX;
int divY = divisor*subY;
// Convert upper left and lower right corners.
int x1 = (r.x + divX - 1)/divX;
int x2 = (r.x + r.width + divX - 1)/divX;
int y1 = (r.y + divY - 1)/divY;
int y2 = (r.y + r.height + divY - 1)/divY;
// Create lower resolution rectangle and return.
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/** Wrapper for the protected method <code>processImageUpdate</code>
* So it can be access from the classes which are not in
* <code>ImageReader</code> hierarchy.
*/
public void processImageUpdateWrapper(BufferedImage theImage,
int minX, int minY,
int width, int height,
int periodX, int periodY,
int[] bands) {
processImageUpdate(theImage,
minX, minY,
width, height,
periodX, periodY,
bands);
}
/** Wrapper for the protected method <code>processImageProgress</code>
* So it can be access from the classes which are not in
* <code>ImageReader</code> hierarchy.
*/
public void processImageProgressWrapper(float percentageDone) {
processImageProgress(percentageDone);
}
/** Constructs <code>J2KImageReader</code> from the provided
* <code>ImageReaderSpi</code>.
*/
public J2KImageReader(ImageReaderSpi originator) {
super(originator);
this.logJJ2000Msg = Boolean.getBoolean("jj2000.j2k.decoder.log");
FacilityManager.registerMsgLogger(null, this);
}
/** Overrides the method defined in the superclass. */
public void setInput(Object input,
boolean seekForwardOnly,
boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata);
this.ignoreMetadata = ignoreMetadata;
iis = (ImageInputStream) input; // Always works
imageMetadata = null;
try {
this.streamPosition0 = iis.getStreamPosition();
} catch(IOException e) {
// XXX ignore
}
}
/** Overrides the method defined in the superclass. */
public int getNumImages(boolean allowSearch) throws IOException {
return 1;
}
public int getWidth(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return width;
}
public int getHeight(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return height;
}
public int getTileGridXOffset(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return hd.getTilingOrigin(null).x;
}
public int getTileGridYOffset(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return hd.getTilingOrigin(null).y;
}
public int getTileWidth(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return hd.getNomTileWidth();
}
public int getTileHeight(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return hd.getNomTileHeight();
}
private void checkIndex(int imageIndex) {
if (imageIndex != 0) {
throw new IndexOutOfBoundsException(I18N.getString("J2KImageReader4"));
}
}
public void readHeader() {
if (gotHeader)
return;
if (readState == null) {
try {
iis.seek(streamPosition0);
} catch(IOException e) {
// XXX ignore
}
readState =
new J2KReadState(iis,
new J2KImageReadParamJava(getDefaultReadParam()),
this);
}
hd = readState.getHeader();
gotHeader = true;
this.width = hd.getImgWidth();
this.height = hd.getImgHeight();
}
public Iterator getImageTypes(int imageIndex)
throws IOException {
checkIndex(imageIndex);
readHeader();
if (readState != null) {
ArrayList list = new ArrayList();
list.add(new ImageTypeSpecifier(readState.getColorModel(),
readState.getSampleModel()));
return list.iterator();
}
return null;
}
public ImageReadParam getDefaultReadParam() {
return new J2KImageReadParam();
}
public IIOMetadata getImageMetadata(int imageIndex)
throws IOException {
checkIndex(imageIndex);
if (ignoreMetadata)
return null;
if (imageMetadata == null) {
iis.mark();
imageMetadata = new J2KMetadata(iis, this);
iis.reset();
}
return imageMetadata;
}
public IIOMetadata getStreamMetadata() throws IOException {
return null;
}
public BufferedImage read(int imageIndex, ImageReadParam param)
throws IOException {
checkIndex(imageIndex);
clearAbortRequest();
processImageStarted(imageIndex);
if (param == null)
param = getDefaultReadParam();
param = new J2KImageReadParamJava(param);
if (!ignoreMetadata) {
imageMetadata = new J2KMetadata();
iis.seek(streamPosition0);
readState = new J2KReadState(iis,
(J2KImageReadParamJava)param,
imageMetadata,
this);
} else {
iis.seek(streamPosition0);
readState = new J2KReadState(iis,
(J2KImageReadParamJava)param,
this);
}
BufferedImage bi = readState.readBufferedImage();
if (abortRequested())
processReadAborted();
else
processImageComplete();
return bi;
}
public RenderedImage readAsRenderedImage(int imageIndex,
ImageReadParam param)
throws IOException {
checkIndex(imageIndex);
RenderedImage ri = null;
clearAbortRequest();
processImageStarted(imageIndex);
if (param == null)
param = getDefaultReadParam();
param = new J2KImageReadParamJava(param);
if (!ignoreMetadata) {
if (imageMetadata == null)
imageMetadata = new J2KMetadata();
ri = new J2KRenderedImage(iis,
(J2KImageReadParamJava)param,
imageMetadata,
this);
}
else
ri = new J2KRenderedImage(iis, (J2KImageReadParamJava)param, this);
if (abortRequested())
processReadAborted();
else
processImageComplete();
return ri;
}
public boolean canReadRaster() {
return true;
}
public boolean isRandomAccessEasy(int imageIndex) throws IOException {
checkIndex(imageIndex);
return false;
}
public Raster readRaster(int imageIndex,
ImageReadParam param) throws IOException {
checkIndex(imageIndex);
processImageStarted(imageIndex);
if (param == null) {
param = getDefaultReadParam();
}
param = new J2KImageReadParamJava(param);
if (!ignoreMetadata) {
imageMetadata = new J2KMetadata();
iis.seek(streamPosition0);
readState = new J2KReadState(iis,
(J2KImageReadParamJava)param,
imageMetadata,
this);
} else {
iis.seek(streamPosition0);
readState = new J2KReadState(iis,
(J2KImageReadParamJava)param,
this);
}
Raster ras = readState.readAsRaster();
if (abortRequested())
processReadAborted();
else
processImageComplete();
return ras;
}
public boolean isImageTiled(int imageIndex) {
checkIndex(imageIndex);
readHeader();
if (readState != null) {
RenderedImage image = new J2KRenderedImage(readState);
if (image.getNumXTiles() * image.getNumYTiles() > 0)
return true;
return false;
}
return false;
}
public void reset() {
// reset local Java structures
super.reset();
iis = null;
gotHeader = false;
imageMetadata = null;
readState = null;
System.gc();
}
/** This method wraps the protected method <code>abortRequested</code>
* to allow the abortions be monitored by <code>J2KReadState</code>.
*/
public boolean getAbortRequest() {
return abortRequested();
}
private ImageTypeSpecifier getImageType(int imageIndex)
throws IOException {
checkIndex(imageIndex);
readHeader();
if (readState != null) {
return new ImageTypeSpecifier(readState.getColorModel(),
readState.getSampleModel());
}
return null;
}
// --- Begin jj2000.j2k.util.MsgLogger implementation ---
public void flush() {
// Do nothing.
}
public void println(String str, int flind, int ind) {
printmsg(INFO, str);
}
public void printmsg(int sev, String msg) {
if(logJJ2000Msg) {
String msgSev;
switch(sev) {
case ERROR:
msgSev = "ERROR";
break;
case INFO:
msgSev = "INFO";
break;
case LOG:
msgSev = "LOG";
break;
case WARNING:
default:
msgSev = "WARNING";
break;
}
processWarningOccurred("[JJ2000 "+msgSev+"] "+msg);
}
}
// --- End jj2000.j2k.util.MsgLogger implementation ---
}