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            raster = Raster.createWritableRaster(
382                sampleModel.createCompatibleSampleModel(destinationRegion.x +
383                                                        destinationRegion.width,
384                                                        destinationRegion.y +
385                                                        destinationRegion.height),
386                new Point(0, 0));
387        } else
388            raster = image.getWritableTile(0, 0);
389
390        readSubsampledRaster(raster);
391        return raster;
392    }
393
394    private void initializeRead(int imageIndex, J2KImageReadParamJava param,
395                                J2KMetadata metadata) {
396        try {
397            iis.mark();
398            in = new IISRandomAccessIO(iis);
399
400            // **** File Format ****
401            // If the codestream is wrapped in the jp2 fileformat, Read the
402            // file format wrapper
403            ff = new FileFormatReader(in, metadata);
404            ff.readFileFormat();
405            in.seek(ff.getFirstCodeStreamPos());
406
407            hi = new HeaderInfo();
408            try{
409                hd = new HeaderDecoder(in, j2krparam, hi);
410            } catch(EOFException e){
411                throw new RuntimeException(I18N.getString("J2KReadState2"));
412            } catch (IOException ioe) {
413                throw new RuntimeException(ioe);
414            }
415
416            this.width = hd.getImgWidth();
417            this.height = hd.getImgHeight();
418
419            Rectangle sourceRegion = param.getSourceRegion();
420            sourceOrigin = new Point();
421            sourceRegion =
422                new Rectangle(hd.getImgULX(), hd.getImgULY(),
423                              this.width, this.height);
424
425            // if the subsample rate for components are not consistent
426            boolean compConsistent = true;
427            stepX = hd.getCompSubsX(0);
428            stepY = hd.getCompSubsY(0);
429            for (int i = 1; i < nComp; i++) {
430                if (stepX != hd.getCompSubsX(i) || stepY != hd.getCompSubsY(i))
431                    throw new RuntimeException(I18N.getString("J2KReadState12"));
432            }
433
434            // Get minimum number of resolution levels available across
435            // all tile-components.
436            int minResLevels = hd.getDecoderSpecs().dls.getMin();
437
438            // Set current resolution level.
439            this.resolution = param != null ?
440                param.getResolution() : minResLevels;
441            if(resolution < 0 || resolution > minResLevels) {
442                resolution = minResLevels;
443            }
444
445            // Convert source region to lower resolution level.
446            if(resolution != minResLevels || stepX != 1 || stepY != 1) {
447                sourceRegion =
448                    J2KImageReader.getReducedRect(sourceRegion, minResLevels,
449                                                  resolution, stepX, stepY);
450            }
451
452            destinationRegion = (Rectangle)sourceRegion.clone();
453
454            J2KImageReader.computeRegionsWrapper(param,
455                                                 false,
456                                                 this.width,
457                                                 this.height,
458                                                 param.getDestination(),
459                                                 sourceRegion,
460                                                 destinationRegion);
461
462            sourceOrigin = new Point(sourceRegion.x, sourceRegion.y);
463            scaleX = param.getSourceXSubsampling();
464            scaleY = param.getSourceYSubsampling();
465            xOffset = param.getSubsamplingXOffset();
466            yOffset = param.getSubsamplingYOffset();
467
468            this.width = destinationRegion.width;
469            this.height = destinationRegion.height;
470
471            Point tileOffset = hd.getTilingOrigin(null);
472
473            this.tileWidth = hd.getNomTileWidth();
474            this.tileHeight = hd.getNomTileHeight();
475
476            // Convert tile 0 to lower resolution level.
477            if(resolution != minResLevels || stepX != 1 || stepY != 1) {
478                Rectangle tileRect = new Rectangle(tileOffset);
479                tileRect.width = tileWidth;
480                tileRect.height = tileHeight;
481                tileRect =
482                    J2KImageReader.getReducedRect(tileRect, minResLevels,
483                                                  resolution, stepX, stepY);
484                tileOffset = tileRect.getLocation();
485                tileWidth = tileRect.width;
486                tileHeight = tileRect.height;
487            }
488
489            tileXOffset = tileOffset.x;
490            tileYOffset = tileOffset.y;
491
492
493            // Set the tile step sizes. These values are used because it
494            // is possible that tiles will be empty. In particular at lower
495            // resolution levels when subsampling is used this may be the
496            // case. This method of calculation will work at least for
497            // Profile-0 images.
498            if(tileWidth*(1 << (minResLevels - resolution))*stepX >
499               hd.getNomTileWidth()) {
500                tileStepX =
501                    (tileWidth*(1 << (minResLevels - resolution))*stepX +
502                     hd.getNomTileWidth() - 1)/hd.getNomTileWidth();
503            } else {
504                tileStepX = 1;
505            }
506
507            if(tileHeight*(1 << (minResLevels - resolution))*stepY >
508               hd.getNomTileHeight()) {
509                tileStepY =
510                    (tileHeight*(1 << (minResLevels - resolution))*stepY +
511                     hd.getNomTileHeight() - 1)/hd.getNomTileHeight();
512            } else {
513                tileStepY = 1;
514            }
515
516            if (!destinationRegion.equals(sourceRegion))
517                noTransform = false;
518
519            // **** Header decoder ****
520            // Instantiate header decoder and read main header
521            decSpec = hd.getDecoderSpecs();
522
523            // **** Instantiate decoding chain ****
524            // Get demixed bitdepths
525            nComp = hd.getNumComps();
526
527            int[] depth = new int[nComp];
528            for (int i=0; i<nComp;i++)
529                depth[i] = hd.getOriginalBitDepth(i);
530
531            //Get channel mapping
532            ChannelDefinitionBox cdb = null;
533            if (metadata != null)
534                cdb = (ChannelDefinitionBox)metadata.getElement("JPEG2000ChannelDefinitionBox");
535
536            channelMap = new int[nComp];
537            if (cdb != null &&
538                metadata.getElement("JPEG2000PaletteBox") == null) {
539                short[] assoc = cdb.getAssociation();
540                short[] types = cdb.getTypes();
541                short[] channels = cdb.getChannel();
542
543                for (int i = 0; i < types.length; i++)
544                    if (types[i] == 0)
545                        channelMap[channels[i]] = assoc[i] - 1;
546                    else if (types[i] == 1 || types[i] == 2)
547                        channelMap[channels[i]] = channels[i];
548            } else {
549                for (int i = 0; i < nComp; i++)
550                    channelMap[i] = i;
551            }
552
553            // **** Bitstream reader ****
554            try {
555                boolean logJJ2000Messages =
556                    Boolean.getBoolean("jj2000.j2k.decoder.log");
557                breader =
558                    BitstreamReaderAgent.createInstance(in, hd,
559                                                        j2krparam, decSpec,
560                                                        logJJ2000Messages, hi);
561            } catch (IOException e) {
562                throw new RuntimeException(I18N.getString("J2KReadState3") + " " +
563                          ((e.getMessage() != null) ?
564                            (":\n"+e.getMessage()) : ""));
565            } catch (IllegalArgumentException e) {
566                throw new RuntimeException(I18N.getString("J2KReadState4") + " " +
567                               ((e.getMessage() != null) ?
568                               (":\n"+e.getMessage()) : ""));
569            }
570
571            // **** Entropy decoder ****
572            try {
573                entdec = hd.createEntropyDecoder(breader, j2krparam);
574            } catch (IllegalArgumentException e) {
575                throw new RuntimeException(I18N.getString("J2KReadState5") + " " +
576                              ((e.getMessage() != null) ?
577                               (":\n"+e.getMessage()) : ""));
578            }
579
580            // **** ROI de-scaler ****
581            try {
582                roids = hd.createROIDeScaler(entdec, j2krparam, decSpec);
583            } catch (IllegalArgumentException e) {
584                throw new RuntimeException(I18N.getString("J2KReadState6") + " " +
585                              ((e.getMessage() != null) ?
586                               (":\n"+e.getMessage()) : ""));
587            }
588
589
590            // **** Dequantizer ****
591            try {
592                deq = hd.createDequantizer(roids, depth, decSpec);
593            } catch (IllegalArgumentException e) {
594                throw new RuntimeException(I18N.getString("J2KReadState7") + " " +
595                              ((e.getMessage() != null) ?
596                               (":\n"+e.getMessage()) : ""));
597            }
598
599            // **** Inverse wavelet transform ***
600            try {
601                // full page inverse wavelet transform
602                invWT = InverseWT.createInstance(deq,decSpec);
603            } catch (IllegalArgumentException e) {
604                throw new RuntimeException(I18N.getString("J2KReadState8") + " " +
605                              ((e.getMessage() != null) ?
606                               (":\n"+e.getMessage()) : ""));
607            }
608
609            int res = breader.getImgRes();
610            int mrl = decSpec.dls.getMin();
611            invWT.setImgResLevel(res);
612
613            // **** Data converter **** (after inverse transform module)
614            converter = new ImgDataConverter(invWT,0);
615
616            // **** Inverse component transformation ****
617            ictransf = new InvCompTransf(converter, decSpec, depth);
618
619            // If the destination band is set used it
620            sourceBands = j2krparam.getSourceBands();
621
622            if (sourceBands == null) {
623                sourceBands = new int[nComp];
624                for (int i = 0; i < nComp; i++)
625                    sourceBands[i] = i;
626            }
627
628            nComp = sourceBands.length;
629
630            destinationBands = j2krparam.getDestinationBands();
631            if (destinationBands == null) {
632                destinationBands = new int[nComp];
633                for (int i = 0; i < nComp; i++)
634                    destinationBands[i] = i;
635            }
636
637            J2KImageReader.checkReadParamBandSettingsWrapper(param,
638                                                             hd.getNumComps(),
639                                                             destinationBands.length);
640
641            levelShift = new int[nComp];
642            minValues = new int[nComp];
643            maxValues = new int[nComp];
644            fracBits = new int[nComp];
645            dataBlocks = new DataBlkInt[nComp];
646
647            depth = new int[nComp];
648            bandOffsets = new int[nComp];
649            maxDepth = 0;
650            isSigned = false;
651            for (int i=0; i<nComp;i++) {
652                depth[i] = hd.getOriginalBitDepth(sourceBands[i]);
653                if (depth[i] > maxDepth)
654                    maxDepth = depth[i];
655                dataBlocks[i] = new DataBlkInt();
656
657                //XXX: may need to change if ChannelDefinition is used to
658                // define the color channels, such as BGR order
659                bandOffsets[i] = i;
660                if (hd.isOriginalSigned(sourceBands[i]))
661                    isSigned = true;
662                else {
663                    levelShift[i] =
664                        1<<(ictransf.getNomRangeBits(sourceBands[i])-1);
665                }
666
667                // Get the number of bits in the image, and decide what the max
668                // value should be, depending on whether it is signed or not
669                int nomRangeBits = ictransf.getNomRangeBits(sourceBands[i]);
670                maxValues[i] = (1 << (isSigned == true ? (nomRangeBits-1) :
671                                                          nomRangeBits)) - 1;
672                minValues[i] = isSigned ? -(maxValues[i]+1) : 0;
673
674                fracBits[i] = ictransf.getFixedPoint(sourceBands[i]);
675            }
676
677            iis.reset();
678        } catch (IllegalArgumentException e){
679            throw new RuntimeException(e.getMessage(), e);
680        } catch (Error e) {
681            if(e.getMessage()!=null)
682                throw new RuntimeException(e.getMessage(), e);
683            else {
684                throw new RuntimeException(I18N.getString("J2KReadState9"), e);
685            }
686        } catch (RuntimeException e) {
687            if(e.getMessage()!=null)
688                throw new RuntimeException(I18N.getString("J2KReadState10") + " " +
689                      e.getMessage(), e);
690            else {
691                throw new RuntimeException(I18N.getString("J2KReadState10"), e);
692            }
693        } catch (Throwable e) {
694            throw new RuntimeException(I18N.getString("J2KReadState10"), e);
695        }
696    }
697
698    private Raster readSubsampledRaster(WritableRaster raster) throws IOException {
699        if (raster == null)
700            raster = Raster.createWritableRaster(
701                sampleModel.createCompatibleSampleModel(destinationRegion.x +
702                                                        destinationRegion.width,
703                                                        destinationRegion.y +
704                                                        destinationRegion.height),
705                new Point(destinationRegion.x, destinationRegion.y));
706
707        int pixbuf[] = null;                  // line buffer for pixel data
708        boolean prog = false;                  // Flag for progressive data
709        Point nT = ictransf.getNumTiles(null);
710        int numBands = sourceBands.length;
711
712        Rectangle destRect = raster.getBounds().intersection(destinationRegion);
713
714        int offx = destinationRegion.x;
715        int offy = destinationRegion.y;
716
717        int sourceSX = (destRect.x - offx) * scaleX + sourceOrigin.x;
718        int sourceSY = (destRect.y - offy) * scaleY + sourceOrigin.y;
719        int sourceEX = (destRect.width - 1)* scaleX + sourceSX;
720        int sourceEY = (destRect.height - 1) * scaleY + sourceSY;
721
722        int startXTile = (sourceSX - tileXOffset) / tileWidth;
723        int startYTile = (sourceSY - tileYOffset) / tileHeight;
724        int endXTile = (sourceEX - tileXOffset) / tileWidth;
725        int endYTile = (sourceEY - tileYOffset) / tileHeight;
726
727        startXTile = clip(startXTile, 0, nT.x - 1);
728        startYTile = clip(startYTile, 0, nT.y - 1);
729        endXTile = clip(endXTile, 0, nT.x - 1);
730        endYTile = clip(endYTile, 0, nT.y - 1);
731
732        int totalXTiles = endXTile - startXTile + 1;
733        int totalYTiles = endYTile - startYTile + 1;
734        int totalTiles = totalXTiles * totalYTiles;
735
736        // Start the data delivery to the cached consumers tile by tile
737        for(int y=startYTile; y <= endYTile; y++){
738            if (reader.getAbortRequest())
739                break;
740
741            // Loop on horizontal tiles
742            for(int x=startXTile; x <= endXTile; x++){
743                if (reader.getAbortRequest())
744                    break;
745
746                float initialFraction =
747                    (x - startXTile + (y - startYTile)*totalXTiles)/totalTiles;
748
749                ictransf.setTile(x*tileStepX,y*tileStepY);
750
751                int sx = hd.getCompSubsX(0);
752                int cTileWidth = (ictransf.getTileWidth() + sx - 1)/sx;
753                int sy = hd.getCompSubsY(0);
754                int cTileHeight = (ictransf.getTileHeight() + sy - 1)/sy;
755
756                // Offsets within the tile.
757                int tx = 0;
758                int ty = 0;
759
760                // The region for this tile
761                int startX = tileXOffset + x * tileWidth;
762                int startY = tileYOffset + y * tileHeight;
763
764                // sourceSX is guaranteed to be >= startX
765                if (sourceSX > startX) {
766                    if(startX >= hd.getImgULX()) {
767                        tx = sourceSX - startX; // Intra-tile offset.
768                        cTileWidth -= tx;       // Reduce effective width.
769                    }
770                    startX = sourceSX;      // Absolute position.
771                }
772
773                // sourceSY is guaranteed to be >= startY
774                if (sourceSY > startY) {
775                    if(startY >= hd.getImgULY()) {
776                        ty = sourceSY - startY; // Intra-tile offset.
777                        cTileHeight -= ty;      // Reduce effective width.
778                    }
779                    startY = sourceSY;      // Absolute position.
780                }
781
782                // Decrement dimensions if end position is within tile.
783                if (sourceEX < startX + cTileWidth - 1) {
784                    cTileWidth += sourceEX - startX - cTileWidth + 1;
785                }
786                if (sourceEY < startY + cTileHeight - 1) {
787                    cTileHeight += sourceEY - startY - cTileHeight + 1;
788                }
789
790                // The start X in the destination
791                int x1 = (startX + scaleX - 1 - sourceOrigin.x) / scaleX;
792                int x2 = (startX + scaleX -1 + cTileWidth - sourceOrigin.x) /
793                         scaleX;
794                int lineLength = x2 - x1;
795                if (pixbuf == null || pixbuf.length < lineLength)
796                    pixbuf = new int[lineLength]; // line buffer for pixel data
797                x2 = (x2 - 1) * scaleX + sourceOrigin.x - startX;
798
799                int y1 = (startY + scaleY -1 - sourceOrigin.y) /scaleY;
800
801                x1 += offx;
802                y1 += offy;
803
804                // Deliver in lines to reduce memory usage
805                for (int l = ty, m = y1;
806                     l < ty + cTileHeight;
807                     l += scaleY, m++) {
808                    if (reader.getAbortRequest())
809                        break;
810                    // Request line data
811                    for (int i = 0; i < numBands; i++) {
812                        DataBlkInt db = dataBlocks[i];
813                        db.ulx = tx;
814                        db.uly = l;
815                        db.w = cTileWidth;
816                        db.h = 1;
817                        ictransf.getInternCompData(db, channelMap[sourceBands[i]]);
818                        prog = prog || db.progressive;
819
820                        int[] data = db.data;
821                        int k1 = db.offset + x2;
822
823                        int fracBit = fracBits[i];
824                        int lS = levelShift[i];
825                        int min = minValues[i];
826                        int max = maxValues[i];
827
828                        if (ImageUtil.isBinary(sampleModel)) {
829                            // Force min max to 0 and 1.
830                            min = 0;
831                            max = 1;
832                            if (bytebuf == null || bytebuf.length < cTileWidth * numBands)
833                                bytebuf = new byte[cTileWidth * numBands];
834                            for (int j = lineLength - 1; j >= 0; j--, k1-=scaleX) {
835                                int tmp = (data[k1] >> fracBit) + lS;
836                                bytebuf[j] =
837                                    (byte)((tmp < min) ? min : 
838                                           ((tmp > max) ? max : tmp));
839                            }
840
841                            ImageUtil.setUnpackedBinaryData(bytebuf,
842                                                            raster,
843                                                            new Rectangle(x1,
844                                                                          m,
845                                                                          lineLength,
846                                                                          1));
847                        } else {
848                            for (int j = lineLength - 1; j >= 0; j--, k1-=scaleX) {
849                                int tmp = (data[k1] >> fracBit) + lS;
850                                pixbuf[j] = (tmp < min) ? min : 
851                                    ((tmp > max) ? max : tmp);
852                            }
853
854                            // Send the line data to the BufferedImage
855                            raster.setSamples(x1,
856                                              m,
857                                              lineLength,
858                                              1,
859                                              destinationBands[i],
860                                              pixbuf);
861                        }
862                    }
863
864                    if (destImage != null)
865                        reader.processImageUpdateWrapper(destImage, x1, m,
866                                                         cTileWidth, 1, 1, 1,
867                                                         destinationBands);
868
869                    float fraction = initialFraction +
870                        (l - ty + 1.0F)/cTileHeight/totalTiles;
871                    reader.processImageProgressWrapper(100.0f*fraction);
872                }
873            } // End loop on horizontal tiles
874        } // End loop on vertical tiles
875
876        return raster;
877    }
878
879    public ImageTypeSpecifier getImageType()
880        throws IOException {
881
882        getSampleModel();
883        getColorModel();
884
885        return new ImageTypeSpecifier(colorModel, sampleModel);
886    }
887
888    public SampleModel getSampleModel() {
889        if (sampleModel != null)
890            return sampleModel;
891
892        if (nComp == 1 && (maxDepth == 1 || maxDepth == 2 || maxDepth == 4))
893            sampleModel =
894                new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
895                                                tileWidth,
896                                                tileHeight,
897                                                maxDepth);
898        else if (maxDepth <= 8)
899            sampleModel =
900                new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
901                                                tileWidth,
902                                                tileHeight,
903                                                nComp,
904                                                tileWidth * nComp,
905                                                bandOffsets);
906        else if (maxDepth <=16)
907            sampleModel =
908                new PixelInterleavedSampleModel(isSigned ?
909                                                DataBuffer.TYPE_SHORT :
910                                                DataBuffer.TYPE_USHORT,
911                                                tileWidth, tileHeight,
912                                                nComp,
913                                                tileWidth * nComp,
914                                                bandOffsets);
915        else if (maxDepth <= 32)
916            sampleModel =
917                new PixelInterleavedSampleModel(DataBuffer.TYPE_INT,
918                                                tileWidth,
919                                                tileHeight,
920                                                nComp,
921                                                tileWidth * nComp,
922                                                bandOffsets);
923        else
924            throw new IllegalArgumentException(I18N.getString("J2KReadState11") + " " +
925                                                + maxDepth);
926        return sampleModel;
927    }
928
929    public ColorModel getColorModel() {
930
931        if (colorModel != null)
932            return colorModel;
933
934        // Attempt to get the ColorModel from the JP2 boxes.
935        colorModel = ff.getColorModel();
936        if (colorModel != null)
937            return colorModel;
938
939        if(hi.siz.csiz <= 4) {
940            // XXX: Code essentially duplicated from FileFormatReader.getColorModel().
941            // Create the ColorModel from the SIZ marker segment parameters.
942            ColorSpace cs;
943            if(hi.siz.csiz > 2) {
944                cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
945            } else {
946                cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
947            }
948
949            int[] bitsPerComponent = new int[hi.siz.csiz];
950            boolean isSigned = false;
951            int maxBitDepth = -1;
952            for(int i = 0; i < hi.siz.csiz; i++) {
953                bitsPerComponent[i] = hi.siz.getOrigBitDepth(i);
954                if(maxBitDepth < bitsPerComponent[i]) {
955                    maxBitDepth = bitsPerComponent[i];
956                }
957                isSigned |= hi.siz.isOrigSigned(i);
958            }
959
960            boolean hasAlpha = hi.siz.csiz % 2 == 0;
961
962            int type = -1;
963
964            if (maxBitDepth <= 8) {
965                type = DataBuffer.TYPE_BYTE;
966            } else if (maxBitDepth <= 16) {
967                type = isSigned ? DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
968            } else if (maxBitDepth <= 32) {
969                type = DataBuffer.TYPE_INT;
970            }
971
972            if (type != -1) {
973                if(hi.siz.csiz == 1 &&
974                   (maxBitDepth == 1 || maxBitDepth == 2 || maxBitDepth == 4)) {
975                    colorModel = ImageUtil.createColorModel(getSampleModel());
976                } else {
977                    colorModel = new ComponentColorModel(cs,
978                                                         bitsPerComponent,
979                                                         hasAlpha,
980                                                         false,
981                                                         hasAlpha ?
982                                                         Transparency.TRANSLUCENT :
983                                                         Transparency.OPAQUE ,
984                                                         type);
985                }
986
987                return colorModel;
988            }
989        }
990
991        if(sampleModel == null) {
992            sampleModel = getSampleModel();
993        }
994
995        if (sampleModel == null)
996            return null;
997
998        return ImageUtil.createColorModel(null, sampleModel);
999    }
1000
1001    /**
1002     * Returns the bounding rectangle of the upper left tile at
1003     * the current resolution level.
1004     */
1005    Rectangle getTile0Rect() {
1006        return new Rectangle(tileXOffset, tileYOffset, tileWidth, tileHeight);
1007    }
1008
1009    private int clip(int value, int min, int max) {
1010        if (value < min)
1011            value = min;
1012        if (value > max)
1013            value = max;
1014        return value;
1015    }
1016
1017    private void clipDestination(Rectangle dest) {
1018        Point offset = j2krparam.getDestinationOffset();
1019        if (dest.x < offset.x) {
1020            dest.width += dest.x - offset.x;
1021            dest.x = offset.x ;
1022        }
1023        if (dest.y < offset.y) {
1024            dest.height += dest.y - offset.y;
1025            dest.y = offset.y ;
1026        }
1027    }
1028}