/*
 * $RCSfile: RenderedImageSrc.java,v $
 *
 * 
 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 * 
 * - Redistribution of source code must retain the above copyright 
 *   notice, this  list of conditions and the following disclaimer.
 * 
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any 
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES. 
 * 
 * You acknowledge that this software is not designed or intended for 
 * use in the design, construction, operation or maintenance of any 
 * nuclear facility. 
 *
 * $Revision: 1.2 $
 * $Date: 2006-09-22 23:07:25 $
 * $State: Exp $
 */
package com.sun.media.imageioimpl.plugins.jpeg2000;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.RenderedImage;
import java.awt.image.Raster;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;

import jj2000.j2k.image.*;
import jj2000.j2k.*;
import java.io.*;

import com.sun.media.imageioimpl.common.ImageUtil;

public class RenderedImageSrc implements BlkImgDataSrc {
    /** The width of the image */
    private int w;

    /** The height of the image */
    private int h;

    /** The tile width for encoding */
    int tileWidth;

    /** The tile height for encoding */
    int tileHeight;

    /** The tile grid offset for encoding */
    int tileXOffset, tileYOffset;

    /** The source -> destination transformation */
    int scaleX, scaleY, xOffset, yOffset;

    /** The source bands to be encoded. */
    int[] sourceBands = null;

    /** The destination upper-left corner */
    int minX, minY;

    /** The number of components in the image */
    private int nc;

    /** The number of bits that determine the nominal dynamic range */
    // XXX: Should be an int[] of length 'nc'.
    private int rb;

    /** Buffer for the 3 components of each pixel(in the current block) */
    private int[][] barr = null;

    /** Data block used only to store coordinates of the buffered blocks */
    private DataBlkInt dbi = new DataBlkInt();

    /** The line buffer. */
    private byte buf[];

    /** Temporary DataBlkInt object (needed when encoder uses floating-point
        filters). This avoid allocating new DataBlk at each time */
    private DataBlkInt intBlk;

    private RenderedImage src;
    private J2KImageWriteParamJava param;

    /** The input source raster. */
    private Raster raster;

    /** The raster for a destination tile */
    private Raster aTile;

    private Point co = new Point();

    private int dcOffset = 0;

    private boolean isBinary = false;

    private Rectangle destinationRegion;
    private Rectangle sourceRegion;

    private ColorModel cm;
    private SampleModel sm;

    private boolean noTransform = true;
    private boolean noSubband = true;

    /** Used to process abortion. */
    private J2KImageWriter writer;

    /** Indicates a <code>raster</code> rather than a <code>RenderedImage</code>
     *  to be encoded.
     */
    private boolean inputIsRaster = false;

    /**
     * Creates <code>RenderedImageSrc</code> for encoding a <code>Raster</code>.
     *
     * @param raster The <code>Raster</code> to be encoded.
     * @param param The <code>J2KImageWriteParamJava</code> used in encoding.
     * @param writer The <code>J2KImageWriter</code> performs the encoding.
     *
     * @param IOException If an error occurs while opening the file.
     */
    public RenderedImageSrc(Raster raster,
                            J2KImageWriteParamJava param,
                            J2KImageWriter writer) {
        this.raster = raster;
        this.param = param;
        this.writer = writer;
        this.inputIsRaster = true;

        sourceRegion = param.getSourceRegion();

        if (sourceRegion == null)
            sourceRegion = new Rectangle(raster.getMinX(), raster.getMinY(),
                                         raster.getWidth(), raster.getHeight());
        else
            sourceRegion = sourceRegion.intersection(raster.getBounds());

        if (sourceRegion.isEmpty())
            throw new RuntimeException(I18N.getString("J2KImageWriterCodecLib0"));

        sm = raster.getSampleModel();
        getFromParam();
        setSampleModelAndMore();
        setTile(0, 0);
    }

    /**
     * Creates <code>RenderedImageSrc</code> for encoding a
     * <code>RenderedImage</code>.
     *
     * @param src The <code>RenderedImage</code> to be encoded.
     * @param param The <code>J2KImageWriteParamJava</code> used in encoding.
     * @param writer The <code>J2KImageWriter</code> performs the encoding.
     *
     * @param IOException If an error occurs while opening the file.
     * */
    public RenderedImageSrc(RenderedImage src,
                            J2KImageWriteParamJava param,
                            J2KImageWriter writer) {
        this.src = src;
        this.param = param;
        this.writer = writer;

        sourceRegion = param.getSourceRegion();

        if (sourceRegion == null)
            sourceRegion = new Rectangle(src.getMinX(), src.getMinY(),
                                         src.getWidth(), src.getHeight());
        else
            sourceRegion = sourceRegion.intersection(new Rectangle(src.getMinX(),
                                                                   src.getMinY(),
                                                                   src.getWidth(),
                                                                   src.getHeight()));
        if (sourceRegion.isEmpty())
            throw new RuntimeException(I18N.getString("J2KImageWriterCodecLib0"));

        sm = src.getSampleModel();
        cm = src.getColorModel();
        getFromParam();
        setSampleModelAndMore();
    }

    private void getFromParam() {
        try {
            tileWidth = param.getTileWidth();
            tileHeight = param.getTileHeight();
            tileXOffset = param.getTileGridXOffset();
            tileYOffset = param.getTileGridYOffset();
        } catch(IllegalStateException e) {
            param.setTilingMode(param.MODE_EXPLICIT);
            if (inputIsRaster) {
                param.setTiling(raster.getWidth(), raster.getHeight(),
                                raster.getMinX(), raster.getMinY());
            } else {
                param.setTiling(src.getWidth(), src.getHeight(),
                                src.getMinX(), src.getMinY());
            }
            tileWidth = param.getTileWidth();
            tileHeight = param.getTileHeight();
            tileXOffset = param.getTileGridXOffset();
            tileYOffset = param.getTileGridYOffset();
        }

        scaleX = param.getSourceXSubsampling();
        scaleY = param.getSourceYSubsampling();
        xOffset = param.getSubsamplingXOffset();
        yOffset = param.getSubsamplingYOffset();

        sourceRegion.translate(xOffset, yOffset);
        sourceRegion.width -= xOffset;
        sourceRegion.height -= yOffset;

	xOffset = sourceRegion.x % scaleX;
	yOffset = sourceRegion.y % scaleY;

        minX = sourceRegion.x / scaleX;
        minY = sourceRegion.y / scaleY;

        w = (sourceRegion.width + scaleX - 1) / scaleX;
        h = (sourceRegion.height + scaleY - 1) / scaleY;

        tileXOffset += (minX - tileXOffset)/tileWidth * tileWidth;
        tileYOffset += (minY - tileYOffset)/tileHeight * tileHeight;

        destinationRegion = new Rectangle(minX, minY, w, h);

        if (!destinationRegion.equals(sourceRegion) ||
            tileWidth != sm.getWidth() ||
            tileHeight != sm.getHeight() ||
            (!inputIsRaster &&
             (tileXOffset != src.getTileGridXOffset() ||
             tileYOffset != src.getTileGridYOffset())) ||
            (inputIsRaster &&
             (tileXOffset != raster.getMinX() ||
             tileYOffset != raster.getMinY())))
            noTransform = false;

    }

    private void setSampleModelAndMore() {
        nc = sm.getNumBands();
        sourceBands = param.getSourceBands();
        if (sourceBands != null) {
            sm = sm.createSubsetSampleModel(sourceBands);
            noSubband = false;
        } else {
            sourceBands = new int[nc];
            for (int i = 0; i < nc; i++)
                sourceBands[i] = i;
        }

        sm = sm.createCompatibleSampleModel(tileWidth, tileHeight);
        nc = sm.getNumBands();
        isBinary = ImageUtil.isBinary(sm);

        if(cm != null) {
            // XXX: rb should be set to getComponentSize();
            rb = cm.getComponentSize(0);
            for (int i = 1; i < cm.getNumComponents(); i++)
                if (rb < cm.getComponentSize(i))
                    rb = cm.getComponentSize(i);
        } else {
            // XXX: rb should be set to getSampleSize();
            rb = sm.getSampleSize(0);
            for (int i = 1; i < sm.getNumBands(); i++)
                if (rb < sm.getSampleSize(i))
                    rb = sm.getSampleSize(i);
        }

	if (!isOrigSigned(0) && rb > 1)
            // XXX: if rb is an int[] this will have to change.
	    dcOffset = 1 << rb - 1;
    }


    public int getTilePartULX() {
	return tileXOffset;
    }

    public int getTilePartULY() {
	return tileYOffset;
    }

    /**
     * Returns the width of the current tile in pixels.
     *
     * @return The total image width in pixels.
     * */
    public int getTileWidth() {
        int width = tileWidth;
        int maxX = getImgULX() + getImgWidth();
        int x = co.x * tileWidth + tileXOffset;
        if (x + tileWidth >= maxX)
            width = maxX - x;
        return width;
    }

    /**
     * Returns the overall height of the current tile in pixels.
     *
     * @return The total image height in pixels.  */
    public int getTileHeight() {
        int height = tileHeight;
        int maxY = getImgULY() + getImgHeight();
        int y = co.y * tileHeight + tileYOffset;
        if (y + tileHeight >= maxY)
            height = maxY - y;

        return height;
    }

    public int getNomTileWidth() {
	return tileWidth;
    }

    public int getNomTileHeight() {
	return tileHeight;
    }

    /**
     * Returns the overall width of the image in pixels. This is the image's
     * width without accounting for any component subsampling or tiling. The
     * value of <tt>w</tt> is returned.
     *
     * @return The total image's width in pixels.
     * */
    public int getImgWidth() {
        return w;
    }

    /**
     * Returns the overall height of the image in pixels. This is the image's
     * height without accounting for any component subsampling or tiling. The
     * value of <tt>h</tt> is returned.
     *
     * @return The total image's height in pixels.
     * */
    public int getImgHeight() {
        return h;
    }

    /**
     * Returns the number of components in the image. The value of <tt>nc</tt>
     * is returned.
     *
     * @return The number of components in the image.
     * */
    public int getNumComps() {
        return nc;
    }

    public int getTileGridXOffset() {
        return param.getTileGridXOffset();
    }

    public int getTileGridYOffset() {
        return param.getTileGridYOffset();
    }

    public int getTileCompHeight(int t, int c) {
        return tileHeight;
    }

    public int getTileCompWidth(int t, int c) {
        return tileWidth;
    }

    /**
     * Returns the component subsampling factor in the horizontal direction,
     * for the specified component. This is, approximately, the ratio of
     * dimensions between the reference grid and the component itself, see the
     * 'ImgData' interface desription for details.
     *
     * @param c The index of the component (between 0 and C-1)
     *
     * @return The horizontal subsampling factor of component 'c'
     *
     * @see ImgData
     * */
    public int getCompSubsX(int c) {
        return 1;
    }

    /**
     * Returns the component subsampling factor in the vertical direction, for
     * the specified component. This is, approximately, the ratio of
     * dimensions between the reference grid and the component itself, see the
     * 'ImgData' interface desription for details.
     *
     * @param c The index of the component (between 0 and C-1)
     *
     * @return The vertical subsampling factor of component 'c'
     *
     * @see ImgData
     * */
    public int getCompSubsY(int c) {
        return 1;
    }

    /**
     * Returns the width in pixels of the specified component in the current
     * tile. This default implementation assumes no tiling and no component
     * subsampling (i.e., all components, or components, have the same
     * dimensions in pixels).
     *
     * @param c The index of the component, from 0 to C-1.
     *
     * @return The width in pixels of component <tt>n</tt> in the current
     * tile.
     * */
    public int getCompWidth(int n) {
        return w;
    }

    /**
     * Returns the height in pixels of the specified component in the current
     * tile. This default implementation assumes no tiling and no component
     * subsampling (i.e., all components, or components, have the same
     * dimensions in pixels).
     *
     * @param c The index of the component, from 0 to C-1.
     *
     * @return The height in pixels of component <tt>c</tt> in the current
     * tile.
     * */
    public int getCompHeight(int c) {
        return h;
    }

    /**
     * Returns the width in pixels of the specified component in the overall
     * image. This default implementation assumes no component, or component,
     * subsampling (i.e. all components have the same dimensions in pixels).
     *
     * @param c The index of the component, from 0 to C-1.
     *
     * @return The width in pixels of component <tt>c</tt> in the overall
     * image.
     * */
    public int getCompImgWidth(int c) {
        return w;
    }

    /**
     * Returns the height in pixels of the specified component in the overall
     * image. This default implementation assumes no component, or component,
     * subsampling (i.e. all components have the same dimensions in pixels).
     *
     * @param c The index of the component, from 0 to C-1.
     *
     * @return The height in pixels of component <tt>c</tt> in the overall
     * image.
     * */
    public int getCompImgHeight(int c) {
        return h;
    }

    /**
     * Changes the current tile, given the new coordinates.
     *
     * @param x The horizontal coordinate of the tile.
     *
     * @param y The vertical coordinate of the new tile.
     * */
    public void setTile(int x, int y) {
        if (x >= getNumXTiles()) {
            y += x/ getNumXTiles();
            x = x % getNumXTiles();
        }
        co.x = x;
        co.y = y;
        aTile = null;
    }

    /**
     * Advances to the next tile, in standard scan-line order (by rows then
     * columns).
     * */
    public void nextTile() {
        co.x++;
        if (co.x >= getNumXTiles()) {
            co.x = 0;
            co.y++;
        }
        setTile(co.x, co.y);
    }

    /**
     * Returns the coordinates of the current tile. This default
     * implementation assumes no-tiling, so (0,0) is returned.
     *
     * @param co If not null this object is used to return the information. If
     * null a new one is created and returned.
     *
     * @return The current tile's coordinates.
     * */
    public Point getTile(Point co) {
        if (co != null)
            return co;
        else
            return new Point(0, 0);
    }

    /**
     * Returns the index of the current tile, relative to a standard scan-line
     * order.
     *
     * @return The current tile's index (starts at 0).
     * */
    public int getTileIdx() {
        return getNumXTiles() * co.y + co.x;
    }

    /**
     * Returns the horizontal and vertical offset of the upper-left corner of
     * the current tile, in the specified component, relative to the canvas
     * origin, in the component coordinates (not in the reference grid
     * coordinates). These are the coordinates of the current tile's (not
     * active tile) upper-left corner relative to the canvas.
     *
     * @param co If not null the object is used to return the values, if null
     * a new one is created and returned.
     *
     * @param c The index of the component (between 0 and C-1)
     *
     * @return The horizontal and vertical offsets of the upper-left corner of
     * the current tile, for the specified component, relative to the canvas
     * origin, in the component coordinates.
     * */
    public Point getTileOff(Point p, int c) {
        if (p != null) {
            p.x = co.x * tileWidth + tileXOffset;
            p.y = co.y * tileHeight + tileYOffset;
            return co;
        } else
            return new Point(co.x * tileWidth + tileXOffset,
                             co.y * tileHeight + tileYOffset);
    }

    /**
     * Returns the horizontal coordinate of the upper-left corner of the
     * active tile, with respect to the canvas origin, in the component
     * coordinates, for the specified component.
     *
     * @param c The index of the component (between 0 and C-1)
     *
     * @return The horizontal coordinate of the upper-left corner of the
     * active tile, with respect to the canvas origin, for component 'c', in
     * the component coordinates.
     * */
    public int getCompULX(int c) {
        return raster.getMinX();
    }

    /**
     * Returns the vertical coordinate of the upper-left corner of the active
     * tile, with respect to the canvas origin, in the component coordinates,
     * for the specified component.
     *
     * @param c The index of the component (between 0 and C-1)
     *
     * @return The vertical coordinate of the upper-left corner of the active
     * tile, with respect to the canvas origin, for component 'c', in the
     * component coordinates.
     * */
    public int getCompULY(int c) {
        return raster.getMinY();
    }

    /**
     * Returns the horizontal coordinate of the image origin, the top-left
     * corner, in the canvas system, on the reference grid.
     *
     * @return The horizontal coordinate of the image origin in the canvas
     * system, on the reference grid.
     * */
    public int getImgULX() {
        return destinationRegion.x;
    }

    /**
     * Returns the vertical coordinate of the image origin, the top-left
     * corner, in the canvas system, on the reference grid.
     *
     * @return The vertical coordinate of the image origin in the canvas
     * system, on the reference grid.
     * */
    public int getImgULY() {
        return destinationRegion.y;
    }

    /**
     * Returns the number of tiles in the horizontal and vertical
     * directions.
     *
     * @param co If not null this object is used to return the information. If
     * null a new one is created and returned.
     *
     * @return The number of tiles in the horizontal (Point.x) and vertical
     * (Point.y) directions.
     * */
    public Point getNumTiles(Point co) {
        if (co != null) {
            co.x = getNumXTiles();
            co.y = getNumYTiles();
            return co;
        }
        else {
            return new Point(getNumXTiles(), getNumYTiles());
        }
    }

    /**
     * Returns the total number of tiles in the image. This default
     * implementation assumes no tiling, so 1 is always returned.
     *
     * @return The total number of tiles in the image.
     * */
    public int getNumTiles() {
        return getNumXTiles() * getNumYTiles();
    }

    /**
     * Returns the number of bits corresponding to the nominal range of the
     * data in the specified component. This is the value rb (range bits) that
     * was specified in the constructor, which normally is 8 for non bilevel
     * data, and 1 for bilevel data.
     *
     * <P>If this number is <i>b</b> then the nominal range is between
     * -2^(b-1) and 2^(b-1)-1, since unsigned data is level shifted to have a
     * nominal avergae of 0.
     *
     * @param c The index of the component.
     *
     * @return The number of bits corresponding to the nominal range of the
     * data. For floating-point data this value is not applicable and the
     * return value is undefined.
     * */
    public int getNomRangeBits(int c) {
        // Check component index
        // XXX: Should be component-dependent.
        return rb;
    }

    /**
     * Returns the position of the fixed point in the specified component
     * (i.e. the number of fractional bits), which is always 0 for this
     * ImgReader.
     *
     * @param c The index of the component.
     *
     * @return The position of the fixed-point (i.e. the number of fractional
     * bits). Always 0 for this ImgReader.
     * */
    public int getFixedPoint(int c) {
        // Check component index
        return 0;
    }


    /**
     * Returns, in the blk argument, the block of image data containing the
     * specifed rectangular area, in the specified component. The data is
     * returned, as a reference to the internal data, if any, instead of as a
     * copy, therefore the returned data should not be modified.
     *
     * <P> After being read the coefficients are level shifted by subtracting
     * 2^(nominal bit range - 1)
     *
     * <P>The rectangular area to return is specified by the 'ulx', 'uly', 'w'
     * and 'h' members of the 'blk' argument, relative to the current
     * tile. These members are not modified by this method. The 'offset' and
     * 'scanw' of the returned data can be arbitrary. See the 'DataBlk' class.
     *
     * <P>If the data array in <tt>blk</tt> is <tt>null</tt>, then a new one
     * is created if necessary. The implementation of this interface may
     * choose to return the same array or a new one, depending on what is more
     * efficient. Therefore, the data array in <tt>blk</tt> prior to the
     * method call should not be considered to contain the returned data, a
     * new array may have been created. Instead, get the array from
     * <tt>blk</tt> after the method has returned.
     *
     * <P>The returned data always has its 'progressive' attribute unset
     * (i.e. false).
     *
     * <P>When an I/O exception is encountered the JJ2KExceptionHandler is
     * used. The exception is passed to its handleException method. The action
     * that is taken depends on the action that has been registered in
     * JJ2KExceptionHandler. See JJ2KExceptionHandler for details.
     *
     * <P>This method implements buffering for the 3 components: When the
     * first one is asked, all the 3 components are read and stored until they
     * are needed.
     *
     * @param blk Its coordinates and dimensions specify the area to
     * return. Some fields in this object are modified to return the data.
     *
     * @param c The index of the component from which to get the data. Only 0,
     * 1 and 3 are valid.
     *
     * @return The requested DataBlk
     *
     * @see #getCompData
     *
     * @see JJ2KExceptionHandler
     */
    public final DataBlk getInternCompData(DataBlk blk, int c) {
        if (writer != null && writer.getAbortRequest())
            throw new RuntimeException(J2KImageWriter.WRITE_ABORTED);

        if (barr == null)
            barr = new int[nc][];

	// Check type of block provided as an argument
	if(blk.getDataType()!=DataBlk.TYPE_INT){
	    if(intBlk==null)
		intBlk = new DataBlkInt(blk.ulx,blk.uly,blk.w,blk.h);
	    else{
		intBlk.ulx = blk.ulx;
		intBlk.uly = blk.uly;
		intBlk.w = blk.w;
		intBlk.h = blk.h;
	    }
	    blk = intBlk;
	}

        float percentage =
            (getTileIdx() + (blk.uly + 1.0F) / blk.h) / getNumTiles();
        writer.processImageProgressWrapper(percentage * 100.0F);

        // If asking a component for the first time for this block, read the 3
        // components
        if ((barr[c] == null) ||
            (dbi.ulx > blk.ulx) || (dbi.uly > blk.uly) ||
            (dbi.ulx+dbi.w < blk.ulx+blk.w) ||
            (dbi.uly+dbi.h < blk.uly+blk.h)) {
            int k,j,i,mi;

            // Reset data arrays if needed
            if (barr[c] == null || barr[c].length < blk.w*blk.h) {
                barr[c] = new int[blk.w*blk.h];
            }
            blk.setData(barr[c]);

            for (i = (c + 1) % nc; i != c; i = (i + 1) % nc)
                if (barr[i] == null || barr[i].length < blk.w*blk.h) {
                    barr[i] = new int[blk.w*blk.h];
                }

            // set attributes of the DataBlk used for buffering
            dbi.ulx = blk.ulx;
            dbi.uly = blk.uly;
            dbi.w = blk.w;
            dbi.h = blk.h;

            // get data from the image
            if (aTile == null) {
                aTile = getTile(co.x, co.y);
                Rectangle temp = aTile.getBounds();
                aTile = aTile.createTranslatedChild(temp.x-minX,
                                                    temp.y-minY);
            }

            for (i = 0; i < nc ; i++) {
                aTile.getSamples(blk.ulx, blk.uly, blk.w, blk.h, i, barr[i]);
                for (k = 0; k < barr[i].length; k++)
                    barr[i][k] -= dcOffset;
            }
            //getByteData(raster, new Rectangle(blk.ulx, blk.uly, blk.w, blk.h), barr);

            // Set buffer attributes
            blk.setData(barr[c]);
            blk.offset = 0;
            blk.scanw = blk.w;
        } else { //Asking for the 2nd or 3rd block component
            blk.setData(barr[c]);
            blk.offset = (blk.ulx-dbi.ulx)*dbi.w+blk.ulx-dbi.ulx;
            blk.scanw = dbi.scanw;
        }

        // Turn off the progressive attribute
        blk.progressive = false;
	return blk;
    }

    /**
     * Returns, in the blk argument, a block of image data containing the
     * specifed rectangular area, in the specified component. The data is
     * returned, as a copy of the internal data, therefore the returned data
     * can be modified "in place".
     *
     * <P> After being read the coefficients are level shifted by subtracting
     * 2^(nominal bit range - 1)
     *
     * <P>The rectangular area to return is specified by the 'ulx', 'uly', 'w'
     * and 'h' members of the 'blk' argument, relative to the current
     * tile. These members are not modified by this method. The 'offset' of
     * the returned data is 0, and the 'scanw' is the same as the block's
     * width. See the 'DataBlk' class.
     *
     * <P>If the data array in 'blk' is 'null', then a new one is created. If
     * the data array is not 'null' then it is reused, and it must be large
     * enough to contain the block's data. Otherwise an 'ArrayStoreException'
     * or an 'IndexOutOfBoundsException' is thrown by the Java system.
     *
     * <P>The returned data has its 'progressive' attribute unset
     * (i.e. false).
     *
     * <P>When an I/O exception is encountered the JJ2KExceptionHandler is
     * used. The exception is passed to its handleException method. The action
     * that is taken depends on the action that has been registered in
     * JJ2KExceptionHandler. See JJ2KExceptionHandler for details.
     *
     * @param blk Its coordinates and dimensions specify the area to
     * return. If it contains a non-null data array, then it must have the
     * correct dimensions. If it contains a null data array a new one is
     * created. The fields in this object are modified to return the data.
     *
     * @param c The index of the component from which to get the data. Only
     * 0,1 and 2 are valid.
     *
     * @return The requested DataBlk
     *
     * @see #getInternCompData
     *
     * @see JJ2KExceptionHandler
     * */
    public final DataBlk getCompData(DataBlk blk, int c) {
        // NOTE: can not directly call getInterCompData since that returns
        // internally buffered data.
        int ulx,uly,w,h;

	// Check type of block provided as an argument
	if(blk.getDataType()!=DataBlk.TYPE_INT){
	    DataBlkInt tmp = new DataBlkInt(blk.ulx,blk.uly,blk.w,blk.h);
	    blk = tmp;
	}

        int bakarr[] = (int[])blk.getData();
        // Save requested block size
        ulx = blk.ulx;
        uly = blk.uly;
        w = blk.w;
        h = blk.h;
        // Force internal data buffer to be different from external
        blk.setData(null);
        getInternCompData(blk,c);
        // Copy the data
        if (bakarr == null) {
            bakarr = new int[w*h];
        }
        if (blk.offset == 0 && blk.scanw == w) {
            // Requested and returned block buffer are the same size
            System.arraycopy(blk.getData(),0,bakarr,0,w*h);
        }
        else { // Requested and returned block are different
            for (int i=h-1; i>=0; i--) { // copy line by line
                System.arraycopy(blk.getData(),blk.offset+i*blk.scanw,
                                 bakarr,i*w,w);
            }
        }
        blk.setData(bakarr);
        blk.offset = 0;
        blk.scanw = blk.w;
	return blk;
    }

    /**
     * Returns true if the data read was originally signed in the specified
     * component, false if not. This method always returns false since PPM
     * data is always unsigned.
     *
     * @param c The index of the component, from 0 to N-1.
     *
     * @return always false, since PPM data is always unsigned.
     * */
    public boolean isOrigSigned(int c) {
        if (isBinary) return true;

        // Check component index
        SampleModel sm = null;
        if (inputIsRaster)
            sm = raster.getSampleModel();
        else
            sm = src.getSampleModel();

        if (sm.getDataType() == DataBuffer.TYPE_USHORT ||
            sm.getDataType() == DataBuffer.TYPE_BYTE)
            return false;
        return true;
    }

    private int getNumXTiles() {
        int x = destinationRegion.x;
        int tx = tileXOffset;
        int tw = tileWidth;
        return ToTile(x + destinationRegion.width - 1, tx, tw) - ToTile(x, tx, tw) + 1;
    }

    private int getNumYTiles() {
        int y = destinationRegion.y;
        int ty = tileYOffset;
        int th = tileHeight;
        return ToTile(y + destinationRegion.height - 1, ty, th) - ToTile(y, ty, th) + 1;
    }

    private static int ToTile(int pos, int tileOffset, int tileSize) {
        pos -= tileOffset;
        if (pos < 0) {
            pos += 1 - tileSize;         // force round to -infinity (ceiling)
        }
        return pos/tileSize;
    }

    private Raster getTile(int tileX, int tileY) {
        int sx = tileXOffset + tileX * tileWidth;
        int sy = tileYOffset + tileY * tileHeight;
        tileX += tileXOffset / tileWidth;
        tileY += tileYOffset / tileHeight;

        if (inputIsRaster) {
            if (noTransform) {
                return raster.createChild(sx, sy, getTileWidth(), getTileHeight(),
                                          sx, sy, sourceBands);
            }

            WritableRaster ras =
                Raster.createWritableRaster(sm, new Point(sx, sy));

            int x = mapToSourceX(sx);
            int y = mapToSourceY(sy);

            int minY = raster.getMinY();
            int maxY = raster.getMinY() + raster.getHeight();

            int cTileWidth = getTileWidth();
            for (int j = 0; j < getTileHeight(); j++, sy++, y += scaleY) {
                if (y < minY || y >= maxY)
                    continue;
                Raster source = raster.createChild(x, y, (cTileWidth - 1) * scaleX + 1, 1,
                                                   x, y, null);
                int tempX = sx;
                for (int i = 0, offset = x; i < cTileWidth; i++, tempX++, offset += scaleX) {
                    for (int k = 0; k < nc; k++) {
                        int p = source.getSample(offset, y, sourceBands[k]);
                        ras.setSample(tempX, sy, k, p);
                    }
                }
            }

            return ras;

        } else {
            if (noTransform) {
                Raster ras = src.getTile(tileX, tileY);
                if (noSubband)
                    return ras;
                else {
                    return ras.createChild(sx, sy, tileWidth, tileHeight,
                                           sx, sy, sourceBands);
                }
            }

            WritableRaster ras = Raster.createWritableRaster(sm, new Point(sx, sy));

            int x = mapToSourceX(sx);
            int y = mapToSourceY(sy);

            int minY = src.getMinY();
            int maxY = src.getMinY() + src.getHeight();
	    int length = tileWidth * scaleX;

	    if (x + length >= src.getWidth())
		length = src.getWidth() - x;
	    int dLength = (length + scaleX -1 ) / scaleX;

            for (int j = 0; j < tileHeight; j++, sy++, y += scaleY) {
                if (y < minY || y >= maxY)
                    continue;

                Raster source = src.getData(new Rectangle(x, y, length, 1));

                int tempX = sx;
                for (int i = 0, offset = x; i < dLength; i++, tempX++, offset += scaleX) {

                    for (int k = 0; k < nc; k++) {
                        int p = source.getSample(offset, y, sourceBands[k]);

                        ras.setSample(tempX, sy, k, p);
                    }
                }
            }
            return ras;
        }
    }

    private int mapToSourceX(int x) {
        return x * scaleX + xOffset;
    }

    private int mapToSourceY(int y) {
        return y * scaleY + yOffset;
    }
}
