001/*
002 * $RCSfile: J2KImageReader.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.7 $
042 * $Date: 2006/10/05 01:08:30 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.jpeg2000.impl;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.image.BufferedImage;
050import java.awt.image.Raster;
051import java.awt.image.RenderedImage;
052import java.io.IOException;
053import java.util.ArrayList;
054import java.util.Iterator;
055
056import javax.imageio.ImageReadParam;
057import javax.imageio.ImageReader;
058import javax.imageio.ImageTypeSpecifier;
059import javax.imageio.metadata.IIOMetadata;
060import javax.imageio.spi.ImageReaderSpi;
061import javax.imageio.stream.ImageInputStream;
062
063import jj2000.j2k.codestream.reader.HeaderDecoder;
064import jj2000.j2k.util.FacilityManager;
065import jj2000.j2k.util.MsgLogger;
066
067import com.github.jaiimageio.jpeg2000.J2KImageReadParam;
068
069/** This class is the Java Image IO plugin reader for JPEG 2000 JP2 image file
070 *  format.  It has the capability to load the compressed bilevel images,
071 *  color-indexed byte images, or multi-band images in byte/ushort/short/int
072 *  data type.  It may subsample the image, select bands, clip the image,
073 *  and shift the decoded image origin if the proper decoding parameter
074 *  are set in the provided <code>J2KImageReadParam</code>.
075 */
076public class J2KImageReader extends ImageReader implements MsgLogger {
077    /** The input stream where reads from */
078    private ImageInputStream iis = null;
079
080    /** Stream position when setInput() was called. */
081    private long streamPosition0;
082
083    /** Indicates whether the header is read. */
084    private boolean gotHeader = false;
085
086    /** The image width. */
087    private int width;
088
089    /** The image height. */
090    private int height;
091
092    /** Image metadata, valid for the imageMetadataIndex only. */
093    private J2KMetadata imageMetadata = null;
094
095    /** The image index for the cached metadata. */
096    private int imageMetadataIndex = -1;
097
098    /** The J2K HeaderDecoder defined in jj2000 packages.  Used to extract image
099     *  header information.
100     */
101    private HeaderDecoder hd;
102
103    /** The J2KReadState for this reading session based on the current input
104     *  and J2KImageReadParam.
105     */
106    private J2KReadState readState = null;
107
108    /**
109     * Whether to log JJ2000 messages.
110     */
111    private boolean logJJ2000Msg = false;
112
113    /** Wrapper for the protected method <code>computeRegions</code>.  So it
114     *  can be access from the classes which are not in <code>ImageReader</code>
115     *  hierarchy.
116     */
117    public static void computeRegionsWrapper(ImageReadParam param,
118                                             boolean allowZeroDestOffset,
119                                             int srcWidth,
120                                             int srcHeight,
121                                             BufferedImage image,
122                                             Rectangle srcRegion,
123                                             Rectangle destRegion) {
124        if (srcRegion == null) {
125            throw new IllegalArgumentException(I18N.getString("J2KImageReader0"));
126        }
127        if (destRegion == null) {
128            throw new IllegalArgumentException(I18N.getString("J2KImageReader1"));
129        }
130
131        // Clip that to the param region, if there is one
132        int periodX = 1;
133        int periodY = 1;
134        int gridX = 0;
135        int gridY = 0;
136        if (param != null) {
137            Rectangle paramSrcRegion = param.getSourceRegion();
138            if (paramSrcRegion != null) {
139                srcRegion.setBounds(srcRegion.intersection(paramSrcRegion));
140            }
141            periodX = param.getSourceXSubsampling();
142            periodY = param.getSourceYSubsampling();
143            gridX = param.getSubsamplingXOffset();
144            gridY = param.getSubsamplingYOffset();
145            srcRegion.translate(gridX, gridY);
146            srcRegion.width -= gridX;
147            srcRegion.height -= gridY;
148            if(allowZeroDestOffset) {
149                destRegion.setLocation(param.getDestinationOffset());
150            } else {
151                Point destOffset = param.getDestinationOffset();
152                if(destOffset.x != 0 || destOffset.y != 0) {
153                    destRegion.setLocation(param.getDestinationOffset());
154                }
155            }
156        }
157
158        // Now clip any negative destination offsets, i.e. clip
159        // to the top and left of the destination image
160        if (destRegion.x < 0) {
161            int delta = -destRegion.x*periodX;
162            srcRegion.x += delta;
163            srcRegion.width -= delta;
164            destRegion.x = 0;
165        }
166        if (destRegion.y < 0) {
167            int delta = -destRegion.y*periodY;
168            srcRegion.y += delta;
169            srcRegion.height -= delta;
170            destRegion.y = 0;
171        }
172
173        // Now clip the destination Region to the subsampled width and height
174        int subsampledWidth = (srcRegion.width + periodX - 1)/periodX;
175        int subsampledHeight = (srcRegion.height + periodY - 1)/periodY;
176        destRegion.width = subsampledWidth;
177        destRegion.height = subsampledHeight;
178
179        // Now clip that to right and bottom of the destination image,
180        // if there is one, taking subsampling into account
181        if (image != null) {
182            Rectangle destImageRect = new Rectangle(0, 0,
183                                                    image.getWidth(),
184                                                    image.getHeight());
185            destRegion.setBounds(destRegion.intersection(destImageRect));
186            if (destRegion.isEmpty()) {
187                throw new IllegalArgumentException
188                    (I18N.getString("J2KImageReader2"));
189            }
190
191            int deltaX = destRegion.x + subsampledWidth - image.getWidth();
192            if (deltaX > 0) {
193                srcRegion.width -= deltaX*periodX;
194            }
195            int deltaY =  destRegion.y + subsampledHeight - image.getHeight();
196            if (deltaY > 0) {
197                srcRegion.height -= deltaY*periodY;
198            }
199        }
200        if (srcRegion.isEmpty() || destRegion.isEmpty()) {
201            throw new IllegalArgumentException(I18N.getString("J2KImageReader3"));
202        }
203    }
204
205    /** Wrapper for the protected method <code>checkReadParamBandSettings</code>.
206     *  So it can be access from the classes which are not in
207     *  <code>ImageReader</code> hierarchy.
208     */
209    public static void checkReadParamBandSettingsWrapper(ImageReadParam param,
210                                                  int numSrcBands,
211                                                  int numDstBands) {
212        checkReadParamBandSettings(param, numSrcBands, numDstBands);
213    }
214
215    /**
216     * Convert a rectangle provided in the coordinate system of the JPEG2000
217     * reference grid to coordinates at a lower resolution level where zero
218     * denotes the lowest resolution level.
219     *
220     * @param r A rectangle in references grid coordinates.
221     * @param maxLevel The highest resolution level in the image.
222     * @param level The resolution level of the returned rectangle.
223     * @param subX The horizontal subsampling step size.
224     * @param subY The vertical subsampling step size.
225     * @return The parameter rectangle converted to a lower resolution level.
226     * @throws IllegalArgumentException if <core>r</code> is <code>null</code>,
227     * <code>maxLevel</code> or <code>level</code> is negative, or
228     * <code>level</code> is greater than <code>maxLevel</code>.
229     */
230    static Rectangle getReducedRect(Rectangle r, int maxLevel, int level,
231                                    int subX, int subY) {
232        if(r == null) {
233            throw new IllegalArgumentException("r == null!");
234        } else if(maxLevel < 0 || level < 0) {
235            throw new IllegalArgumentException("maxLevel < 0 || level < 0!");
236        } else if(level > maxLevel) {
237            throw new IllegalArgumentException("level > maxLevel");
238        }
239
240        // At the highest level; return the parameter.
241        if(level == maxLevel && subX == 1 && subY == 1) {
242            return r;
243        }
244
245        // Resolution divisor is step*2^(maxLevel - level).
246        int divisor = 1 << (maxLevel - level);
247        int divX = divisor*subX;
248        int divY = divisor*subY;
249
250        // Convert upper left and lower right corners.
251        int x1 = (r.x + divX - 1)/divX;
252        int x2 = (r.x + r.width + divX - 1)/divX;
253        int y1 = (r.y + divY - 1)/divY;
254        int y2 = (r.y + r.height + divY - 1)/divY;
255
256        // Create lower resolution rectangle and return.
257        return new Rectangle(x1, y1, x2 - x1, y2 - y1);
258    }
259
260    /** Wrapper for the protected method <code>processImageUpdate</code>
261     *  So it can be access from the classes which are not in
262     *  <code>ImageReader</code> hierarchy.
263     */
264    public void processImageUpdateWrapper(BufferedImage theImage,
265                                      int minX, int minY,
266                                      int width, int height,
267                                      int periodX, int periodY,
268                                      int[] bands) {
269        processImageUpdate(theImage,
270                                  minX, minY,
271                                  width, height,
272                                  periodX, periodY,
273                                  bands);
274    }
275
276    /** Wrapper for the protected method <code>processImageProgress</code>
277     *  So it can be access from the classes which are not in
278     *  <code>ImageReader</code> hierarchy.
279     */
280    public void processImageProgressWrapper(float percentageDone) {
281        processImageProgress(percentageDone);
282    }
283
284    /** Constructs <code>J2KImageReader</code> from the provided
285     *  <code>ImageReaderSpi</code>.
286     */
287    public J2KImageReader(ImageReaderSpi originator) {
288        super(originator);
289
290        this.logJJ2000Msg = Boolean.getBoolean("jj2000.j2k.decoder.log");
291
292        FacilityManager.registerMsgLogger(null, this);
293    }
294
295    /** Overrides the method defined in the superclass. */
296    public void setInput(Object input,
297                         boolean seekForwardOnly,
298                         boolean ignoreMetadata) {
299        super.setInput(input, seekForwardOnly, ignoreMetadata);
300        this.ignoreMetadata = ignoreMetadata;
301        iis = (ImageInputStream) input; // Always works
302        imageMetadata = null;
303        try {
304            this.streamPosition0 = iis.getStreamPosition();
305        } catch(IOException e) {
306            // XXX ignore
307        }
308    }
309
310    /** Overrides the method defined in the superclass. */
311    public int getNumImages(boolean allowSearch) throws IOException {
312        return 1;
313    }
314
315    public int getWidth(int imageIndex) throws IOException {
316        checkIndex(imageIndex);
317        readHeader();
318        return width;
319    }
320
321    public int getHeight(int imageIndex) throws IOException {
322        checkIndex(imageIndex);
323        readHeader();
324        return height;
325    }
326
327    public int getTileGridXOffset(int imageIndex) throws IOException {
328        checkIndex(imageIndex);
329        readHeader();
330        return hd.getTilingOrigin(null).x;
331    }
332
333    public int getTileGridYOffset(int imageIndex) throws IOException {
334        checkIndex(imageIndex);
335        readHeader();
336        return hd.getTilingOrigin(null).y;
337    }
338
339    public int getTileWidth(int imageIndex) throws IOException {
340        checkIndex(imageIndex);
341        readHeader();
342        return hd.getNomTileWidth();
343    }
344
345    public int getTileHeight(int imageIndex) throws IOException {
346        checkIndex(imageIndex);
347        readHeader();
348        return hd.getNomTileHeight();
349    }
350
351    private void checkIndex(int imageIndex) {
352        if (imageIndex != 0) {
353            throw new IndexOutOfBoundsException(I18N.getString("J2KImageReader4"));
354        }
355    }
356
357    public void readHeader() {
358        if (gotHeader)
359            return;
360
361        if (readState == null) {
362            try {
363                iis.seek(streamPosition0);
364            } catch(IOException e) {
365                // XXX ignore
366            }
367
368            readState =
369                new J2KReadState(iis,
370                                 new J2KImageReadParamJava(getDefaultReadParam()),
371                                 this);
372        }
373
374        hd = readState.getHeader();
375        gotHeader = true;
376
377        this.width = hd.getImgWidth();
378        this.height = hd.getImgHeight();
379    }
380
381    public Iterator getImageTypes(int imageIndex)
382        throws IOException {
383        checkIndex(imageIndex);
384        readHeader();
385        if (readState != null) {
386            ArrayList list = new ArrayList();
387            list.add(new ImageTypeSpecifier(readState.getColorModel(),
388                                            readState.getSampleModel()));
389            return list.iterator();
390        }
391        return null;
392    }
393
394    public ImageReadParam getDefaultReadParam() {
395        return new J2KImageReadParam();
396    }
397
398    public IIOMetadata getImageMetadata(int imageIndex)
399        throws IOException {
400        checkIndex(imageIndex);
401        if (ignoreMetadata)
402            return null;
403
404        if (imageMetadata == null) {
405            iis.mark();
406            imageMetadata = new J2KMetadata(iis, this);
407            iis.reset();
408        }
409        return imageMetadata;
410    }
411
412    public IIOMetadata getStreamMetadata() throws IOException {
413        return null;
414    }
415
416    public BufferedImage read(int imageIndex, ImageReadParam param)
417        throws IOException {
418        checkIndex(imageIndex);
419        clearAbortRequest();
420        processImageStarted(imageIndex);
421
422        if (param == null)
423            param = getDefaultReadParam();
424
425        param = new J2KImageReadParamJava(param);
426
427        if (!ignoreMetadata) {
428            imageMetadata = new J2KMetadata();
429            iis.seek(streamPosition0);
430            readState = new J2KReadState(iis,
431                                         (J2KImageReadParamJava)param,
432                                         imageMetadata,
433                                         this);
434        } else {
435            iis.seek(streamPosition0);
436            readState = new J2KReadState(iis,
437                                         (J2KImageReadParamJava)param,
438                                         this);
439        }
440
441        BufferedImage bi = readState.readBufferedImage();
442        if (abortRequested())
443            processReadAborted();
444        else
445            processImageComplete();
446        return bi;
447    }
448
449    public RenderedImage readAsRenderedImage(int imageIndex,
450                                             ImageReadParam param)
451                                             throws IOException {
452        checkIndex(imageIndex);
453        RenderedImage ri = null;
454        clearAbortRequest();
455        processImageStarted(imageIndex);
456
457        if (param == null)
458            param = getDefaultReadParam();
459
460        param = new J2KImageReadParamJava(param);
461        if (!ignoreMetadata) {
462            if (imageMetadata == null)
463                imageMetadata = new J2KMetadata();
464            ri = new J2KRenderedImage(iis,
465                                        (J2KImageReadParamJava)param,
466                                        imageMetadata,
467                                        this);
468        }
469        else
470            ri = new J2KRenderedImage(iis, (J2KImageReadParamJava)param, this);
471        if (abortRequested())
472            processReadAborted();
473        else
474            processImageComplete();
475        return ri;
476    }
477
478    public boolean canReadRaster() {
479        return true;
480    }
481
482    public boolean isRandomAccessEasy(int imageIndex) throws IOException {
483        checkIndex(imageIndex);
484        return false;
485    }
486
487    public Raster readRaster(int imageIndex,
488                             ImageReadParam param) throws IOException {
489        checkIndex(imageIndex);
490        processImageStarted(imageIndex);
491        param = new J2KImageReadParamJava(param);
492
493        if (!ignoreMetadata) {
494            imageMetadata = new J2KMetadata();
495            iis.seek(streamPosition0);
496            readState = new J2KReadState(iis,
497                                         (J2KImageReadParamJava)param,
498                                         imageMetadata,
499                                         this);
500        } else {
501            iis.seek(streamPosition0);
502            readState = new J2KReadState(iis,
503                                         (J2KImageReadParamJava)param,
504                                         this);
505        }
506
507        Raster ras = readState.readAsRaster();
508        if (abortRequested())
509            processReadAborted();
510        else
511            processImageComplete();
512        return ras;
513    }
514
515    public boolean isImageTiled(int imageIndex) {
516        checkIndex(imageIndex);
517        readHeader();
518        if (readState != null) {
519            RenderedImage image = new J2KRenderedImage(readState);
520            if (image.getNumXTiles() * image.getNumYTiles() > 0)
521                return true;
522            return false;
523        }
524        return false;
525    }
526
527    public void reset() {
528        // reset local Java structures
529        super.reset();
530
531        iis = null;
532        gotHeader = false;
533        imageMetadata = null;
534        readState = null;
535        System.gc();
536    }
537
538    /** This method wraps the protected method <code>abortRequested</code>
539     *  to allow the abortions be monitored by <code>J2KReadState</code>.
540     */
541    public boolean getAbortRequest() {
542        return abortRequested();
543    }
544
545    private ImageTypeSpecifier getImageType(int imageIndex)
546        throws IOException {
547        checkIndex(imageIndex);
548        readHeader();
549        if (readState != null) {
550            return new ImageTypeSpecifier(readState.getColorModel(),
551                                          readState.getSampleModel());
552        }
553        return null;
554    }
555
556    // --- Begin jj2000.j2k.util.MsgLogger implementation ---
557    public void flush() {
558        // Do nothing.
559    }
560
561    public void println(String str, int flind, int ind) {
562        printmsg(INFO, str);
563    }
564
565    public void printmsg(int sev, String msg) {
566        if(logJJ2000Msg) {
567            String msgSev;
568            switch(sev) {
569            case ERROR:
570                msgSev = "ERROR";
571                break;
572            case INFO:
573                msgSev = "INFO";
574                break;
575            case LOG:
576                msgSev = "LOG";
577                break;
578            case WARNING:
579            default:
580                msgSev = "WARNING";
581                break;
582            }
583
584            processWarningOccurred("[JJ2000 "+msgSev+"] "+msg);
585        }
586    }
587    // --- End jj2000.j2k.util.MsgLogger implementation ---
588}