001/*
002 * $RCSfile: J2KReadState.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.8 $
042 * $Date: 2006/10/03 23:40:14 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.jpeg2000.impl;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.Transparency;
050import java.awt.color.ColorSpace;
051import java.awt.image.BufferedImage;
052import java.awt.image.ColorModel;
053import java.awt.image.ComponentColorModel;
054import java.awt.image.DataBuffer;
055import java.awt.image.MultiPixelPackedSampleModel;
056import java.awt.image.PixelInterleavedSampleModel;
057import java.awt.image.Raster;
058import java.awt.image.SampleModel;
059import java.awt.image.WritableRaster;
060import java.io.EOFException;
061import java.io.IOException;
062import java.util.Hashtable;
063
064import javax.imageio.ImageTypeSpecifier;
065import javax.imageio.stream.ImageInputStream;
066
067import jj2000.j2k.codestream.HeaderInfo;
068import jj2000.j2k.codestream.reader.BitstreamReaderAgent;
069import jj2000.j2k.codestream.reader.HeaderDecoder;
070import jj2000.j2k.decoder.DecoderSpecs;
071import jj2000.j2k.entropy.decoder.EntropyDecoder;
072import jj2000.j2k.fileformat.reader.FileFormatReader;
073import jj2000.j2k.image.DataBlkInt;
074import jj2000.j2k.image.ImgDataConverter;
075import jj2000.j2k.image.invcomptransf.InvCompTransf;
076import jj2000.j2k.io.RandomAccessIO;
077import jj2000.j2k.quantization.dequantizer.Dequantizer;
078import jj2000.j2k.roi.ROIDeScaler;
079import jj2000.j2k.wavelet.synthesis.InverseWT;
080
081import com.github.jaiimageio.impl.common.ImageUtil;
082
083public class J2KReadState {
084    /** The input stream we read from */
085    private ImageInputStream iis = null;
086
087    private FileFormatReader ff;
088    private HeaderInfo hi;
089    private HeaderDecoder hd;
090    private RandomAccessIO in;
091    private BitstreamReaderAgent breader;
092    private EntropyDecoder entdec;
093    private ROIDeScaler roids;
094    private Dequantizer deq;
095    private InverseWT invWT;
096    private InvCompTransf ictransf;
097    private ImgDataConverter converter,converter2;
098    private DecoderSpecs decSpec = null;
099    private J2KImageReadParamJava j2krparam = null;
100    private int[] destinationBands = null;
101    private int[] sourceBands = null;
102
103    private int[] levelShift = null;        // level shift for each component
104    private int[] minValues = null;         // The min values
105    private int[] maxValues = null;         // The max values
106    private int[] fracBits = null;          // fractional bits for each component
107    private DataBlkInt[] dataBlocks = null; // data-blocks to request data from src
108
109    private int[] bandOffsets = null;
110    private int maxDepth = 0;
111    private boolean isSigned = false;
112
113    private ColorModel colorModel = null;
114    private SampleModel sampleModel = null;
115    private int nComp = 0;
116    private int tileWidth = 0;
117    private int tileHeight = 0;
118
119    /** Source to destination transform */
120    private int scaleX, scaleY, xOffset, yOffset;
121    private Rectangle destinationRegion = null;
122    private Point sourceOrigin;
123
124    /** Tile grid offsets of the source, also used for destination. */
125    private int tileXOffset, tileYOffset;
126
127    private int width;
128    private int height;
129    private int[] pixbuf = null;
130    private byte[] bytebuf = null;
131    private int[] channelMap = null;
132
133    private boolean noTransform = true;
134
135    /** The resolution level requested. */
136    private int resolution;
137
138    /** The subsampling step sizes. */
139    private int stepX, stepY;
140
141    /** Tile step sizes. */
142    private int tileStepX, tileStepY;
143
144    private J2KMetadata metadata;
145
146    private BufferedImage destImage;
147
148    /** Cache the <code>J2KImageReader</code> which creates this object.  This
149     *  variable is used to monitor the abortion.
150     */
151    private J2KImageReader reader;
152
153    /** Constructs <code>J2KReadState</code>.
154     *  @param iis The input stream.
155     *  @param param The reading parameters.
156     *  @param metadata The <code>J2KMetadata</code> to cache the metadata read
157     *                  from the input stream.
158     *  @param reader The <code>J2KImageReader</code> which holds this state.
159     *                It is necessary for processing abortion.
160     *  @throw IllegalArgumentException If the provided <code>iis</code>,
161     *          <code>param</code> or <code>metadata</code> is <code>null</code>.
162     */
163    public J2KReadState(ImageInputStream iis,
164                        J2KImageReadParamJava param,
165                        J2KMetadata metadata,
166                        J2KImageReader reader) {
167        if (iis == null || param == null || metadata == null)
168            throw new IllegalArgumentException(I18N.getString("J2KReadState0"));
169
170        this.iis = iis;
171        this.j2krparam = param;
172        this.metadata = metadata;
173        this.reader = reader;
174
175        initializeRead(0, param, metadata);
176    }
177
178    /** Constructs <code>J2KReadState</code>.
179     *  @param iis The input stream.
180     *  @param param The reading parameters.
181     *  @param reader The <code>J2KImageReader</code> which holds this state.
182     *                It is necessary for processing abortion.
183     *  @throw IllegalArgumentException If the provided <code>iis</code>,
184     *          or <code>param</code> is <code>null</code>.
185     */
186    public J2KReadState(ImageInputStream iis,
187                        J2KImageReadParamJava param,
188                        J2KImageReader reader) {
189        if (iis == null || param == null)
190            throw new IllegalArgumentException(I18N.getString("J2KReadState0"));
191
192        this.iis = iis;
193        this.j2krparam = param;
194        this.reader = reader;
195        initializeRead(0, param, null);
196    }
197
198    public int getWidth() throws IOException {
199        return width;
200    }
201
202    public int getHeight() throws IOException {
203        return height;
204    }
205
206    public HeaderDecoder getHeader() {
207        return hd;
208    }
209
210    public Raster getTile(int tileX, int tileY,
211                          WritableRaster raster) throws IOException {
212        Point nT = ictransf.getNumTiles(null);
213
214        if (noTransform) {
215            if (tileX >= nT.x || tileY >= nT.y)
216                throw new IllegalArgumentException(I18N.getString("J2KImageReader0"));
217
218            ictransf.setTile(tileX*tileStepX, tileY*tileStepY);
219
220            // The offset of the active tiles is the same for all components,
221            // since we don't support different component dimensions.
222            int tOffx;
223            int tOffy;
224            int cTileWidth;
225            int cTileHeight;
226            if(raster != null &&
227               (this.resolution < hd.getDecoderSpecs().dls.getMin()) ||
228               stepX != 1 || stepY != 1) {
229                tOffx = raster.getMinX();
230                tOffy = raster.getMinY();
231                cTileWidth = Math.min(raster.getWidth(),
232                                      ictransf.getTileWidth());
233                cTileHeight = Math.min(raster.getHeight(),
234                                       ictransf.getTileHeight());
235            } else {
236                tOffx = ictransf.getCompULX(0) -
237                    (ictransf.getImgULX() + ictransf.getCompSubsX(0) - 1) /
238                    ictransf.getCompSubsX(0) + destinationRegion.x;
239                tOffy = ictransf.getCompULY(0)-
240                    (ictransf.getImgULY() + ictransf.getCompSubsY(0) - 1) /
241                    ictransf.getCompSubsY(0) + destinationRegion.y;
242                cTileWidth = ictransf.getTileWidth();
243                cTileHeight = ictransf.getTileHeight();
244            }
245
246            if (raster == null)
247                raster = Raster.createWritableRaster(sampleModel,
248                                                     new Point(tOffx, tOffy));
249
250            int numBands = sampleModel.getNumBands();
251
252            if (tOffx + cTileWidth >=
253                destinationRegion.width + destinationRegion.x)
254                cTileWidth =
255                    destinationRegion.width + destinationRegion.x - tOffx;
256
257            if (tOffy + cTileHeight >=
258                destinationRegion.height + destinationRegion.y)
259                cTileHeight =
260                    destinationRegion.height + destinationRegion.y - tOffy;
261
262            //create the line buffer for pixel data if it is not large enough
263            // or null
264            if (pixbuf == null || pixbuf.length < cTileWidth * numBands)
265                pixbuf = new int[cTileWidth * numBands];
266            boolean prog = false;
267
268            // Deliver in lines to reduce memory usage
269            for (int l=0; l < cTileHeight;l++) {
270                if (reader.getAbortRequest())
271                    break;
272
273                // Request line data
274                for (int i = 0; i < numBands; i++) {
275                    if (reader.getAbortRequest())
276                        break;
277                    DataBlkInt db = dataBlocks[i];
278                    db.ulx = 0;
279                    db.uly = l;
280                    db.w = cTileWidth;
281                    db.h = 1;
282                    ictransf.getInternCompData(db, channelMap[sourceBands[i]]);
283                    prog = prog || db.progressive;
284
285                    int[] data = db.data;
286                    int k1 = db.offset + cTileWidth - 1;
287
288                    int fracBit = fracBits[i];
289                    int lS = levelShift[i];
290                    int min = minValues[i];
291                    int max = maxValues[i];
292
293                    if (ImageUtil.isBinary(sampleModel)) {
294                        // Force min max to 0 and 1.
295                        min = 0;
296                        max = 1;
297                        if (bytebuf == null || bytebuf.length < cTileWidth * numBands)
298                            bytebuf = new byte[cTileWidth * numBands];
299                        for (int j = cTileWidth - 1;
300                             j >= 0; j--) {
301                            int tmp = (data[k1--] >> fracBit) + lS;
302                            bytebuf[j] =
303                                (byte)((tmp < min) ? min : 
304                                       ((tmp > max) ? max : tmp));
305                        }
306
307                        ImageUtil.setUnpackedBinaryData(bytebuf,
308                                                        raster,
309                                                        new Rectangle(tOffx,
310                                                                      tOffy + l,
311                                                                      cTileWidth,
312                                                                      1));
313                    } else {
314
315                        for (int j = cTileWidth - 1;
316                             j >= 0; j--) {
317                            int tmp = (data[k1--] >> fracBit) + lS;
318                            pixbuf[j] = (tmp < min) ? min : 
319                                ((tmp > max) ? max : tmp);
320                        }
321
322                        raster.setSamples(tOffx,
323                                          tOffy + l,
324                                          cTileWidth,
325                                          1,
326                                          destinationBands[i],
327                                          pixbuf);
328                    }
329                }
330            }
331        } else {
332            readSubsampledRaster(raster);
333        }
334
335        return raster;
336    }
337
338    public Rectangle getDestinationRegion() {
339        return destinationRegion;
340    }
341
342    public BufferedImage readBufferedImage() throws IOException {
343        colorModel = getColorModel();
344        sampleModel = getSampleModel();
345        WritableRaster raster = null;
346        BufferedImage image = j2krparam.getDestination();
347
348        int x = destinationRegion.x;
349        int y = destinationRegion.y;
350        destinationRegion.setLocation(j2krparam.getDestinationOffset());
351        if (image == null) {
352            // If the destination type is specified, use the color model of it.
353            ImageTypeSpecifier type = j2krparam.getDestinationType();
354            if (type != null)
355                colorModel = type.getColorModel();
356
357            raster = Raster.createWritableRaster(
358                sampleModel.createCompatibleSampleModel(destinationRegion.x +
359                                                        destinationRegion.width,
360                                                        destinationRegion.y +
361                                                        destinationRegion.height),
362                new Point(0, 0));
363            image = new BufferedImage(colorModel, raster,
364                                      colorModel.isAlphaPremultiplied(),
365                                      new Hashtable());
366        } else
367            raster = image.getWritableTile(0, 0);
368
369        destImage = image;
370        readSubsampledRaster(raster);
371        destinationRegion.setLocation(x, y);
372        destImage = null;
373        return image;
374    }
375
376    public Raster readAsRaster() throws IOException {
377        BufferedImage image = j2krparam.getDestination();
378        WritableRaster raster = null;
379
380        if (image == null) {
381            sampleModel = getSampleModel();
382            raster = Raster.createWritableRaster(
383                sampleModel.createCompatibleSampleModel(destinationRegion.x +
384                                                        destinationRegion.width,
385                                                        destinationRegion.y +
386                                                        destinationRegion.height),
387                new Point(0, 0));
388        } else
389            raster = image.getWritableTile(0, 0);
390
391        readSubsampledRaster(raster);
392        return raster;
393    }
394
395    private void initializeRead(int imageIndex, J2KImageReadParamJava param,
396                                J2KMetadata metadata) {
397        try {
398            iis.mark();
399            in = new IISRandomAccessIO(iis);
400
401            // **** File Format ****
402            // If the codestream is wrapped in the jp2 fileformat, Read the
403            // file format wrapper
404            ff = new FileFormatReader(in, metadata);
405            ff.readFileFormat();
406            in.seek(ff.getFirstCodeStreamPos());
407
408            hi = new HeaderInfo();
409            try{
410                hd = new HeaderDecoder(in, j2krparam, hi);
411            } catch(EOFException e){
412                throw new RuntimeException(I18N.getString("J2KReadState2"));
413            } catch (IOException ioe) {
414                throw new RuntimeException(ioe);
415            }
416
417            this.width = hd.getImgWidth();
418            this.height = hd.getImgHeight();
419
420            Rectangle sourceRegion = param.getSourceRegion();
421            sourceOrigin = new Point();
422            sourceRegion =
423                new Rectangle(hd.getImgULX(), hd.getImgULY(),
424                              this.width, this.height);
425
426            // if the subsample rate for components are not consistent
427            boolean compConsistent = true;
428            stepX = hd.getCompSubsX(0);
429            stepY = hd.getCompSubsY(0);
430            for (int i = 1; i < nComp; i++) {
431                if (stepX != hd.getCompSubsX(i) || stepY != hd.getCompSubsY(i))
432                    throw new RuntimeException(I18N.getString("J2KReadState12"));
433            }
434
435            // Get minimum number of resolution levels available across
436            // all tile-components.
437            int minResLevels = hd.getDecoderSpecs().dls.getMin();
438
439            // Set current resolution level.
440            this.resolution = param != null ?
441                param.getResolution() : minResLevels;
442            if(resolution < 0 || resolution > minResLevels) {
443                resolution = minResLevels;
444            }
445
446            // Convert source region to lower resolution level.
447            if(resolution != minResLevels || stepX != 1 || stepY != 1) {
448                sourceRegion =
449                    J2KImageReader.getReducedRect(sourceRegion, minResLevels,
450                                                  resolution, stepX, stepY);
451            }
452
453            destinationRegion = (Rectangle)sourceRegion.clone();
454
455            J2KImageReader.computeRegionsWrapper(param,
456                                                 false,
457                                                 this.width,
458                                                 this.height,
459                                                 param.getDestination(),
460                                                 sourceRegion,
461                                                 destinationRegion);
462
463            sourceOrigin = new Point(sourceRegion.x, sourceRegion.y);
464            scaleX = param.getSourceXSubsampling();
465            scaleY = param.getSourceYSubsampling();
466            xOffset = param.getSubsamplingXOffset();
467            yOffset = param.getSubsamplingYOffset();
468
469            this.width = destinationRegion.width;
470            this.height = destinationRegion.height;
471
472            Point tileOffset = hd.getTilingOrigin(null);
473
474            this.tileWidth = hd.getNomTileWidth();
475            this.tileHeight = hd.getNomTileHeight();
476
477            // Convert tile 0 to lower resolution level.
478            if(resolution != minResLevels || stepX != 1 || stepY != 1) {
479                Rectangle tileRect = new Rectangle(tileOffset);
480                tileRect.width = tileWidth;
481                tileRect.height = tileHeight;
482                tileRect =
483                    J2KImageReader.getReducedRect(tileRect, minResLevels,
484                                                  resolution, stepX, stepY);
485                tileOffset = tileRect.getLocation();
486                tileWidth = tileRect.width;
487                tileHeight = tileRect.height;
488            }
489
490            tileXOffset = tileOffset.x;
491            tileYOffset = tileOffset.y;
492
493
494            // Set the tile step sizes. These values are used because it
495            // is possible that tiles will be empty. In particular at lower
496            // resolution levels when subsampling is used this may be the
497            // case. This method of calculation will work at least for
498            // Profile-0 images.
499            if(tileWidth*(1 << (minResLevels - resolution))*stepX >
500               hd.getNomTileWidth()) {
501                tileStepX =
502                    (tileWidth*(1 << (minResLevels - resolution))*stepX +
503                     hd.getNomTileWidth() - 1)/hd.getNomTileWidth();
504            } else {
505                tileStepX = 1;
506            }
507
508            if(tileHeight*(1 << (minResLevels - resolution))*stepY >
509               hd.getNomTileHeight()) {
510                tileStepY =
511                    (tileHeight*(1 << (minResLevels - resolution))*stepY +
512                     hd.getNomTileHeight() - 1)/hd.getNomTileHeight();
513            } else {
514                tileStepY = 1;
515            }
516
517            if (!destinationRegion.equals(sourceRegion))
518                noTransform = false;
519
520            // **** Header decoder ****
521            // Instantiate header decoder and read main header
522            decSpec = hd.getDecoderSpecs();
523
524            // **** Instantiate decoding chain ****
525            // Get demixed bitdepths
526            nComp = hd.getNumComps();
527
528            int[] depth = new int[nComp];
529            for (int i=0; i<nComp;i++)
530                depth[i] = hd.getOriginalBitDepth(i);
531
532            //Get channel mapping
533            ChannelDefinitionBox cdb = null;
534            if (metadata != null)
535                cdb = (ChannelDefinitionBox)metadata.getElement("JPEG2000ChannelDefinitionBox");
536
537            channelMap = new int[nComp];
538            if (cdb != null &&
539                metadata.getElement("JPEG2000PaletteBox") == null) {
540                short[] assoc = cdb.getAssociation();
541                short[] types = cdb.getTypes();
542                short[] channels = cdb.getChannel();
543
544                for (int i = 0; i < types.length; i++)
545                    if (types[i] == 0)
546                        channelMap[channels[i]] = assoc[i] - 1;
547                    else if (types[i] == 1 || types[i] == 2)
548                        channelMap[channels[i]] = channels[i];
549            } else {
550                for (int i = 0; i < nComp; i++)
551                    channelMap[i] = i;
552            }
553
554            // **** Bitstream reader ****
555            try {
556                boolean logJJ2000Messages =
557                    Boolean.getBoolean("jj2000.j2k.decoder.log");
558                breader =
559                    BitstreamReaderAgent.createInstance(in, hd,
560                                                        j2krparam, decSpec,
561                                                        logJJ2000Messages, hi);
562            } catch (IOException e) {
563                throw new RuntimeException(I18N.getString("J2KReadState3") + " " +
564                          ((e.getMessage() != null) ?
565                            (":\n"+e.getMessage()) : ""));
566            } catch (IllegalArgumentException e) {
567                throw new RuntimeException(I18N.getString("J2KReadState4") + " " +
568                               ((e.getMessage() != null) ?
569                               (":\n"+e.getMessage()) : ""));
570            }
571
572            // **** Entropy decoder ****
573            try {
574                entdec = hd.createEntropyDecoder(breader, j2krparam);
575            } catch (IllegalArgumentException e) {
576                throw new RuntimeException(I18N.getString("J2KReadState5") + " " +
577                              ((e.getMessage() != null) ?
578                               (":\n"+e.getMessage()) : ""));
579            }
580
581            // **** ROI de-scaler ****
582            try {
583                roids = hd.createROIDeScaler(entdec, j2krparam, decSpec);
584            } catch (IllegalArgumentException e) {
585                throw new RuntimeException(I18N.getString("J2KReadState6") + " " +
586                              ((e.getMessage() != null) ?
587                               (":\n"+e.getMessage()) : ""));
588            }
589
590
591            // **** Dequantizer ****
592            try {
593                deq = hd.createDequantizer(roids, depth, decSpec);
594            } catch (IllegalArgumentException e) {
595                throw new RuntimeException(I18N.getString("J2KReadState7") + " " +
596                              ((e.getMessage() != null) ?
597                               (":\n"+e.getMessage()) : ""));
598            }
599
600            // **** Inverse wavelet transform ***
601            try {
602                // full page inverse wavelet transform
603                invWT = InverseWT.createInstance(deq,decSpec);
604            } catch (IllegalArgumentException e) {
605                throw new RuntimeException(I18N.getString("J2KReadState8") + " " +
606                              ((e.getMessage() != null) ?
607                               (":\n"+e.getMessage()) : ""));
608            }
609
610            int res = breader.getImgRes();
611            int mrl = decSpec.dls.getMin();
612            invWT.setImgResLevel(res);
613
614            // **** Data converter **** (after inverse transform module)
615            converter = new ImgDataConverter(invWT,0);
616
617            // **** Inverse component transformation ****
618            ictransf = new InvCompTransf(converter, decSpec, depth);
619
620            // If the destination band is set used it
621            sourceBands = j2krparam.getSourceBands();
622
623            if (sourceBands == null) {
624                sourceBands = new int[nComp];
625                for (int i = 0; i < nComp; i++)
626                    sourceBands[i] = i;
627            }
628
629            nComp = sourceBands.length;
630
631            destinationBands = j2krparam.getDestinationBands();
632            if (destinationBands == null) {
633                destinationBands = new int[nComp];
634                for (int i = 0; i < nComp; i++)
635                    destinationBands[i] = i;
636            }
637
638            J2KImageReader.checkReadParamBandSettingsWrapper(param,
639                                                             hd.getNumComps(),
640                                                             destinationBands.length);
641
642            levelShift = new int[nComp];
643            minValues = new int[nComp];
644            maxValues = new int[nComp];
645            fracBits = new int[nComp];
646            dataBlocks = new DataBlkInt[nComp];
647
648            depth = new int[nComp];
649            bandOffsets = new int[nComp];
650            maxDepth = 0;
651            isSigned = false;
652            for (int i=0; i<nComp;i++) {
653                depth[i] = hd.getOriginalBitDepth(sourceBands[i]);
654                if (depth[i] > maxDepth)
655                    maxDepth = depth[i];
656                dataBlocks[i] = new DataBlkInt();
657
658                //XXX: may need to change if ChannelDefinition is used to
659                // define the color channels, such as BGR order
660                bandOffsets[i] = i;
661                if (hd.isOriginalSigned(sourceBands[i]))
662                    isSigned = true;
663                else {
664                    levelShift[i] =
665                        1<<(ictransf.getNomRangeBits(sourceBands[i])-1);
666                }
667
668                // Get the number of bits in the image, and decide what the max
669                // value should be, depending on whether it is signed or not
670                int nomRangeBits = ictransf.getNomRangeBits(sourceBands[i]);
671                maxValues[i] = (1 << (isSigned == true ? (nomRangeBits-1) :
672                                                          nomRangeBits)) - 1;
673                minValues[i] = isSigned ? -(maxValues[i]+1) : 0;
674
675                fracBits[i] = ictransf.getFixedPoint(sourceBands[i]);
676            }
677
678            iis.reset();
679        } catch (IllegalArgumentException e){
680            throw new RuntimeException(e.getMessage(), e);
681        } catch (Error e) {
682            if(e.getMessage()!=null)
683                throw new RuntimeException(e.getMessage(), e);
684            else {
685                throw new RuntimeException(I18N.getString("J2KReadState9"), e);
686            }
687        } catch (RuntimeException e) {
688            if(e.getMessage()!=null)
689                throw new RuntimeException(I18N.getString("J2KReadState10") + " " +
690                      e.getMessage(), e);
691            else {
692                throw new RuntimeException(I18N.getString("J2KReadState10"), e);
693            }
694        } catch (Throwable e) {
695            throw new RuntimeException(I18N.getString("J2KReadState10"), e);
696        }
697    }
698
699    private Raster readSubsampledRaster(WritableRaster raster) throws IOException {
700        if (raster == null)
701            raster = Raster.createWritableRaster(
702                sampleModel.createCompatibleSampleModel(destinationRegion.x +
703                                                        destinationRegion.width,
704                                                        destinationRegion.y +
705                                                        destinationRegion.height),
706                new Point(destinationRegion.x, destinationRegion.y));
707
708        int pixbuf[] = null;                  // line buffer for pixel data
709        boolean prog = false;                  // Flag for progressive data
710        Point nT = ictransf.getNumTiles(null);
711        int numBands = sourceBands.length;
712
713        Rectangle destRect = raster.getBounds().intersection(destinationRegion);
714
715        int offx = destinationRegion.x;
716        int offy = destinationRegion.y;
717
718        int sourceSX = (destRect.x - offx) * scaleX + sourceOrigin.x;
719        int sourceSY = (destRect.y - offy) * scaleY + sourceOrigin.y;
720        int sourceEX = (destRect.width - 1)* scaleX + sourceSX;
721        int sourceEY = (destRect.height - 1) * scaleY + sourceSY;
722
723        int startXTile = (sourceSX - tileXOffset) / tileWidth;
724        int startYTile = (sourceSY - tileYOffset) / tileHeight;
725        int endXTile = (sourceEX - tileXOffset) / tileWidth;
726        int endYTile = (sourceEY - tileYOffset) / tileHeight;
727
728        startXTile = clip(startXTile, 0, nT.x - 1);
729        startYTile = clip(startYTile, 0, nT.y - 1);
730        endXTile = clip(endXTile, 0, nT.x - 1);
731        endYTile = clip(endYTile, 0, nT.y - 1);
732
733        int totalXTiles = endXTile - startXTile + 1;
734        int totalYTiles = endYTile - startYTile + 1;
735        int totalTiles = totalXTiles * totalYTiles;
736
737        // Start the data delivery to the cached consumers tile by tile
738        for(int y=startYTile; y <= endYTile; y++){
739            if (reader.getAbortRequest())
740                break;
741
742            // Loop on horizontal tiles
743            for(int x=startXTile; x <= endXTile; x++){
744                if (reader.getAbortRequest())
745                    break;
746
747                float initialFraction =
748                    (x - startXTile + (y - startYTile)*totalXTiles)/totalTiles;
749
750                ictransf.setTile(x*tileStepX,y*tileStepY);
751
752                int sx = hd.getCompSubsX(0);
753                int cTileWidth = (ictransf.getTileWidth() + sx - 1)/sx;
754                int sy = hd.getCompSubsY(0);
755                int cTileHeight = (ictransf.getTileHeight() + sy - 1)/sy;
756
757                // Offsets within the tile.
758                int tx = 0;
759                int ty = 0;
760
761                // The region for this tile
762                int startX = tileXOffset + x * tileWidth;
763                int startY = tileYOffset + y * tileHeight;
764
765                // sourceSX is guaranteed to be >= startX
766                if (sourceSX > startX) {
767                    if(startX >= hd.getImgULX()) {
768                        tx = sourceSX - startX; // Intra-tile offset.
769                        cTileWidth -= tx;       // Reduce effective width.
770                    }
771                    startX = sourceSX;      // Absolute position.
772                }
773
774                // sourceSY is guaranteed to be >= startY
775                if (sourceSY > startY) {
776                    if(startY >= hd.getImgULY()) {
777                        ty = sourceSY - startY; // Intra-tile offset.
778                        cTileHeight -= ty;      // Reduce effective width.
779                    }
780                    startY = sourceSY;      // Absolute position.
781                }
782
783                // Decrement dimensions if end position is within tile.
784                if (sourceEX < startX + cTileWidth - 1) {
785                    cTileWidth += sourceEX - startX - cTileWidth + 1;
786                }
787                if (sourceEY < startY + cTileHeight - 1) {
788                    cTileHeight += sourceEY - startY - cTileHeight + 1;
789                }
790
791                // The start X in the destination
792                int x1 = (startX + scaleX - 1 - sourceOrigin.x) / scaleX;
793                int x2 = (startX + scaleX -1 + cTileWidth - sourceOrigin.x) /
794                         scaleX;
795                int lineLength = x2 - x1;
796                if (pixbuf == null || pixbuf.length < lineLength)
797                    pixbuf = new int[lineLength]; // line buffer for pixel data
798                x2 = (x2 - 1) * scaleX + sourceOrigin.x - startX;
799
800                int y1 = (startY + scaleY -1 - sourceOrigin.y) /scaleY;
801
802                x1 += offx;
803                y1 += offy;
804
805                // check to see if we have YCbCr data
806                boolean ycbcr = false;
807
808                for (int i=0; i<numBands; i++) {
809                  DataBlkInt db = dataBlocks[i];
810                  db.ulx = tx;
811                  db.uly = ty + cTileHeight - 1;
812                  db.w = cTileWidth;
813                  db.h = 1;
814
815                  try {
816                    ictransf.getInternCompData(db, channelMap[sourceBands[i]]);
817                  }
818                  catch (ArrayIndexOutOfBoundsException e) {
819                    ycbcr = true;
820                    break;
821                  }
822                }
823
824                // Deliver in lines to reduce memory usage
825                for (int l = ty, m = y1;
826                     l < ty + cTileHeight;
827                     l += scaleY, m++) {
828                    if (reader.getAbortRequest())
829                        break;
830
831
832                    if (ycbcr) {
833                      DataBlkInt lum = dataBlocks[0];
834                      DataBlkInt cb = dataBlocks[1];
835                      DataBlkInt cr = dataBlocks[2];
836
837                      lum.ulx = tx;
838                      lum.uly = l;
839                      lum.w = cTileWidth;
840                      lum.h = 1;
841                      ictransf.getInternCompData(lum, channelMap[sourceBands[0]]);
842                      prog = prog || lum.progressive;
843
844                      cb.ulx = tx;
845                      cb.uly = l;
846                      cb.w = cTileWidth / 2;
847                      cb.h = 1;
848                      ictransf.getInternCompData(cb, channelMap[sourceBands[1]]);
849                      prog = prog || cb.progressive;
850
851                      cr.ulx = tx;
852                      cr.uly = l;
853                      cr.w = cTileWidth / 2;
854                      cr.h = 1;
855                      ictransf.getInternCompData(cr, channelMap[sourceBands[2]]);
856                      prog = prog || cr.progressive;
857
858                      int[] lumdata = lum.data;
859                      int[] cbdata = cb.data;
860                      int[] crdata = cr.data;
861
862                      int k1 = lum.offset + x2;
863
864                      int fracBit = fracBits[0];
865                      int lS = levelShift[0];
866                      int min = minValues[0];
867                      int max = maxValues[0];
868
869                      int[][] pix = new int[3][lineLength];
870
871                      for (int j = lineLength - 1; j >= 0; j--, k1-=scaleX) {
872                        int red = (lumdata[k1] >> fracBit) + lS;
873                        red = (red < min) ? min : ((red > max) ? max : red);
874
875                        int cIndex = k1 / 2;
876
877                        int chrom1 = cbdata[cIndex];
878                        int chrom2 = crdata[cIndex];
879                        int lumval = red;
880
881                        red = (int) (chrom2 * 1.542 + lumval);
882                        int blue = (int) (lumval + 1.772 * chrom1 - 0.886);
883                        int green = (int) (lumval - 0.34413 * chrom1 - 0.71414 *
884                          chrom2 - 0.1228785);
885
886                        if (red > 255) red = 255;
887                        if (green > 255) green = 255;
888                        if (blue > 255) blue = 255;
889
890                        if (red < 0) red = 0;
891                        if (green < 0) green = 0;
892                        if (blue < 0) blue = 0;
893
894                        pix[0][j] = red;
895                        pix[1][j] = green;
896                        pix[2][j] = blue;
897                      }
898
899
900                      raster.setSamples(x1, m, lineLength, 1,
901                        destinationBands[0], pix[0]);
902                      raster.setSamples(x1, m, lineLength, 1,
903                        destinationBands[1], pix[1]);
904                      raster.setSamples(x1, m, lineLength, 1,
905                        destinationBands[2], pix[2]);
906
907                      continue;
908                    }
909
910                    // Request line data
911                    for (int i = 0; i < numBands; i++) {
912                        DataBlkInt db = dataBlocks[i];
913                        db.ulx = tx;
914                        db.uly = l;
915                        db.w = ycbcr && i > 0 ? cTileWidth / 2 : cTileWidth;
916                        db.h = 1;
917                        ictransf.getInternCompData(db, channelMap[sourceBands[i]]);
918                        prog = prog || db.progressive;
919
920                        int[] data = db.data;
921                        int k1 = db.offset + x2;
922
923                        int fracBit = fracBits[i];
924                        int lS = levelShift[i];
925                        int min = minValues[i];
926                        int max = maxValues[i];
927
928                        if (ImageUtil.isBinary(sampleModel)) {
929                            // Force min max to 0 and 1.
930                            min = 0;
931                            max = 1;
932                            if (bytebuf == null || bytebuf.length < cTileWidth * numBands)
933                                bytebuf = new byte[cTileWidth * numBands];
934                            for (int j = lineLength - 1; j >= 0; j--, k1-=scaleX) {
935                                int tmp = (data[k1] >> fracBit) + lS;
936                                bytebuf[j] =
937                                    (byte)((tmp < min) ? min : 
938                                           ((tmp > max) ? max : tmp));
939                            }
940
941                            ImageUtil.setUnpackedBinaryData(bytebuf,
942                                                            raster,
943                                                            new Rectangle(x1,
944                                                                          m,
945                                                                          lineLength,
946                                                                          1));
947                        } else {
948                            for (int j = lineLength - 1; j >= 0; j--, k1-=scaleX) {
949                                int tmp = (data[k1] >> fracBit) + lS;
950                                pixbuf[j] = (tmp < min) ? min : 
951                                    ((tmp > max) ? max : tmp);
952                            }
953
954                            // Send the line data to the BufferedImage
955                            raster.setSamples(x1,
956                                              m,
957                                              lineLength,
958                                              1,
959                                              destinationBands[i],
960                                              pixbuf);
961                        }
962                    }
963
964                    if (destImage != null)
965                        reader.processImageUpdateWrapper(destImage, x1, m,
966                                                         cTileWidth, 1, 1, 1,
967                                                         destinationBands);
968
969                    float fraction = initialFraction +
970                        (l - ty + 1.0F)/cTileHeight/totalTiles;
971                    reader.processImageProgressWrapper(100.0f*fraction);
972                }
973            } // End loop on horizontal tiles
974        } // End loop on vertical tiles
975
976        return raster;
977    }
978
979    public ImageTypeSpecifier getImageType()
980        throws IOException {
981
982        getSampleModel();
983        getColorModel();
984
985        return new ImageTypeSpecifier(colorModel, sampleModel);
986    }
987
988    public SampleModel getSampleModel() {
989        if (sampleModel != null)
990            return sampleModel;
991
992        int realWidth = (int) Math.min(tileWidth, width);
993        int realHeight = (int) Math.min(tileHeight, height);
994
995        if (nComp == 1 && (maxDepth == 1 || maxDepth == 2 || maxDepth == 4))
996            sampleModel =
997                new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
998                                                realWidth,
999                                                realHeight,
1000                                                maxDepth);
1001        else if (maxDepth <= 8)
1002            sampleModel =
1003                new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
1004                                                realWidth,
1005                                                realHeight,
1006                                                nComp,
1007                                                realWidth * nComp,
1008                                                bandOffsets);
1009        else if (maxDepth <=16)
1010            sampleModel =
1011                new PixelInterleavedSampleModel(isSigned ?
1012                                                DataBuffer.TYPE_SHORT :
1013                                                DataBuffer.TYPE_USHORT,
1014                                                realWidth, realHeight,
1015                                                nComp,
1016                                                realWidth * nComp,
1017                                                bandOffsets);
1018        else if (maxDepth <= 32)
1019            sampleModel =
1020                new PixelInterleavedSampleModel(DataBuffer.TYPE_INT,
1021                                                realWidth,
1022                                                realHeight,
1023                                                nComp,
1024                                                realWidth * nComp,
1025                                                bandOffsets);
1026        else
1027            throw new IllegalArgumentException(I18N.getString("J2KReadState11") + " " +
1028                                                + maxDepth);
1029        return sampleModel;
1030    }
1031
1032    public ColorModel getColorModel() {
1033
1034        if (colorModel != null)
1035            return colorModel;
1036
1037        // Attempt to get the ColorModel from the JP2 boxes.
1038        colorModel = ff.getColorModel();
1039        if (colorModel != null)
1040            return colorModel;
1041
1042        if(hi.siz.csiz <= 4) {
1043            // XXX: Code essentially duplicated from FileFormatReader.getColorModel().
1044            // Create the ColorModel from the SIZ marker segment parameters.
1045            ColorSpace cs;
1046            if(hi.siz.csiz > 2) {
1047                cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1048            } else {
1049                cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
1050            }
1051
1052            int[] bitsPerComponent = new int[hi.siz.csiz];
1053            boolean isSigned = false;
1054            int maxBitDepth = -1;
1055            for(int i = 0; i < hi.siz.csiz; i++) {
1056                bitsPerComponent[i] = hi.siz.getOrigBitDepth(i);
1057                if(maxBitDepth < bitsPerComponent[i]) {
1058                    maxBitDepth = bitsPerComponent[i];
1059                }
1060                isSigned |= hi.siz.isOrigSigned(i);
1061            }
1062
1063            boolean hasAlpha = hi.siz.csiz % 2 == 0;
1064
1065            int type = -1;
1066
1067            if (maxBitDepth <= 8) {
1068                type = DataBuffer.TYPE_BYTE;
1069            } else if (maxBitDepth <= 16) {
1070                type = isSigned ? DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
1071            } else if (maxBitDepth <= 32) {
1072                type = DataBuffer.TYPE_INT;
1073            }
1074
1075            if (type != -1) {
1076                if(hi.siz.csiz == 1 &&
1077                   (maxBitDepth == 1 || maxBitDepth == 2 || maxBitDepth == 4)) {
1078                    colorModel = ImageUtil.createColorModel(getSampleModel());
1079                } else {
1080                    colorModel = new ComponentColorModel(cs,
1081                                                         bitsPerComponent,
1082                                                         hasAlpha,
1083                                                         false,
1084                                                         hasAlpha ?
1085                                                         Transparency.TRANSLUCENT :
1086                                                         Transparency.OPAQUE ,
1087                                                         type);
1088                }
1089
1090                return colorModel;
1091            }
1092        }
1093
1094        if(sampleModel == null) {
1095            sampleModel = getSampleModel();
1096        }
1097
1098        if (sampleModel == null)
1099            return null;
1100
1101        return ImageUtil.createColorModel(null, sampleModel);
1102    }
1103
1104    /**
1105     * Returns the bounding rectangle of the upper left tile at
1106     * the current resolution level.
1107     */
1108    Rectangle getTile0Rect() {
1109        return new Rectangle(tileXOffset, tileYOffset, tileWidth, tileHeight);
1110    }
1111
1112    private int clip(int value, int min, int max) {
1113        if (value < min)
1114            value = min;
1115        if (value > max)
1116            value = max;
1117        return value;
1118    }
1119
1120    private void clipDestination(Rectangle dest) {
1121        Point offset = j2krparam.getDestinationOffset();
1122        if (dest.x < offset.x) {
1123            dest.width += dest.x - offset.x;
1124            dest.x = offset.x ;
1125        }
1126        if (dest.y < offset.y) {
1127            dest.height += dest.y - offset.y;
1128            dest.y = offset.y ;
1129        }
1130    }
1131}