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