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