com.lizardtech.djvu.GPixmap.java Source code

Java tutorial

Introduction

Here is the source code for com.lizardtech.djvu.GPixmap.java

Source

//C- -------------------------------------------------------------------
//C- Java DjVu (r) (v. 0.8)
//C- Copyright (c) 2004-2005 LizardTech, Inc.  All Rights Reserved.
//C- Java DjVu is protected by U.S. Pat. No.C- 6,058,214 and patents
//C- pending.
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, Version 2. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- The computer code originally released by LizardTech under this
//C- license and unmodified by other parties is deemed "the LIZARDTECH
//C- ORIGINAL CODE."  Subject to any third party intellectual property
//C- claims, LizardTech grants recipient a worldwide, royalty-free,
//C- non-exclusive license to make, use, sell, or otherwise dispose of
//C- the LIZARDTECH ORIGINAL CODE or of programs derived from the
//C- LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
//C- General Public License.   This grant only confers the right to
//C- infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
//C- the extent such infringement is reasonably necessary to enable
//C- recipient to make, have made, practice, sell, or otherwise dispose
//C- of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
//C- any greater extent that may be necessary to utilize further
//C- modifications or combinations.
//C-
//C- The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C-
//C- In addition, as a special exception, LizardTech Inc. gives permission
//C- to link the code of this program with the proprietary Java
//C- implementation provided by Sun (or other vendors as well), and
//C- distribute linked combinations including the two. You must obey the
//C- GNU General Public License in all respects for all of the code used
//C- other than the proprietary Java implementation. If you modify this
//C- file, you may extend this exception to your version of the file, but
//C- you are not obligated to do so. If you do not wish to do so, delete
//C- this exception statement from your version.
//C- -------------------------------------------------------------------
//C- Developed by Bill C. Riemers, Foxtrot Technologies Inc. as work for
//C- hire under US copyright laws.
//C- -------------------------------------------------------------------
//
package com.lizardtech.djvu;

import java.io.IOException;

import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.typedarrays.shared.Int32Array;
import com.google.gwt.typedarrays.shared.TypedArrays;
import com.google.gwt.typedarrays.shared.Uint8Array;

/**
 * This class represents 24 bit color image maps.
 *
 * @author Bill C. Riemers
 * @version $ $
 */
public class GPixmap extends GMap implements Pixmap {
    //~ Static fields/initializers ---------------------------------------------

    /** Indentity color correction table. */
    protected final static int[] ctableI = new int[256];

    /** Cached color correction table. */
    protected static int[] ctable = new int[256];

    /**
     * The color correction subsample for the cached color table. 
     */
    protected static double lgamma = -1D;

    /** Used for attenuation */
    protected final static int[][] multiplierRefArray = new int[256][];

    /**
     * Static initializers.
     */
    static {
        for (int i = 0; i < ctableI.length; i++) {
            ctableI[i] = i;
        }
    }

    //~ Constructors -----------------------------------------------------------

    /**
     * Creates a new GPixmap object.
     */
    public GPixmap() {
        super(BYTES_PER_PIXEL, 0, 1, 2, false);
    }

    //~ Methods ----------------------------------------------------------------

    @Override
    public void putData(Context2d target) {
        target.putImageData(imageData, 0, 0);
    }

    /**
       * Fill the array with color correction constants.
       * 
       * @param gamma color correction subsample
       * 
       * @return the new color correction table
       */
    private static synchronized int[] getColorCorrection(double gamma) {
        if ((gamma < 0.10000000000000001D) || (gamma > 10D)) {
            DjVuOptions.err.println("(GPixmap::color_correct) Illegal parameter");
        }
        int[] retval;
        if ((gamma < 1.0009999999999999D) && (gamma > 0.999D)) {
            retval = ctableI;
        } else {
            if (gamma != lgamma) {
                for (int i = 0; i < 256; i++) {
                    double x = i / 255D;

                    if (DjVuOptions.BEZIERGAMMA) {
                        double t = (Math.sqrt(1.0D + (((gamma * gamma) - 1.0D) * x)) - 1.0D) / (gamma - 1.0D);
                        x = ((((1.0D - gamma) * t) + (2D * gamma)) * t) / (gamma + 1.0D);
                    } else {
                        x = Math.pow(x, 1.0D / gamma);
                    }

                    ctable[i] = (int) Math.floor((255D * x) + 0.5D);
                }
                lgamma = gamma;
            }
            retval = ctable;
        }
        return retval;
    }

    /**
     * Creates or retrieves a cached multiplier array to use when attenuating.
     *
     * @return attenuation array
     */
    protected static int[] getMultiplier(final int maxgray) {
        int[] retval = multiplierRefArray[maxgray];
        if (retval == null) {
            retval = new int[maxgray];

            for (int i = 0; i < maxgray; i++) {
                retval[i] = 0x10000 - ((i << 16) / maxgray);
            }
            multiplierRefArray[maxgray] = retval;
        }
        return retval;
    }

    /**
     * Attenuate the specified bitmap.
     * 
     * @param bm Bitmap to attenuate
     */
    public void attenuate(final GBitmap bm) {
        // Check
        // Compute number of rows and columns
        int xrows = Math.min(bm.rows(), rows());
        int xcolumns = Math.min(bm.columns(), columns());

        if ((xrows <= 0) || (xcolumns <= 0)) {
            return;
        }

        // Precompute multiplier map
        final int maxgray = bm.getGrays() - 1;
        final int[] multiplier = getMultiplier(maxgray);

        // Compute starting point
        int src = bm.rowOffset(0);
        int dst = rowOffset(0);

        final GPixelReference dstPixel = createGPixelReference(0);

        // Loop over rows
        for (int y = 0; y < xrows; y++) {
            // Loop over columns
            dstPixel.setOffset(dst);

            for (int x = 0; x < xcolumns; dstPixel.incOffset()) {
                int srcpix = bm.getByteAt(src + (x++));

                // Perform pixel operation
                if (srcpix > 0) {
                    if (srcpix >= maxgray) {
                        dstPixel.setGray(0);
                    } else {
                        final int level = multiplier[srcpix];
                        dstPixel.setBGR((dstPixel.getBlue() * level) >> 16, (dstPixel.getGreen() * level) >> 16,
                                (dstPixel.getRed() * level) >> 16);
                    }
                }
            }

            // Next line
            dst += getRowSize();
            src += bm.getRowSize();
        }
    }

    /**
     * Insert the specified bitmap with the specified color.
     * 
     * @param bm bitmap to insert
     * @param xpos horizontal position
     * @param ypos vertical position
     * @param color color to insert bitmap with
     */
    public void blit(final GBitmap bm, int xpos, int ypos, final GPixel color) {
        // Check
        if (color == null || bm == null) {
            return;
        }

        // Compute number of rows and columns
        int xrows = Math.min(ypos + bm.rows(), rows());
        if (ypos > 0) {
            xrows -= ypos;
        }

        int xcolumns = Math.min(xpos + bm.columns(), columns());
        if (xpos > 0) {
            xcolumns -= xpos;
        }

        if ((xrows <= 0) || (xcolumns <= 0)) {
            return;
        }

        // Precompute multiplier map
        final int maxgray = bm.getGrays() - 1;
        final int[] multiplier = getMultiplier(maxgray);

        // Cache target color
        int gr = color.getRed();
        int gg = color.getGreen();
        int gb = color.getBlue();

        // Compute starting point
        int src = bm.rowOffset((ypos < 0) ? (-ypos) : 0) - ((xpos < 0) ? xpos : 0);
        int dst = ((ypos > 0) ? rowOffset(ypos) : 0) + ((xpos > 0) ? xpos : 0);

        final GPixelReference dstPixel = createGPixelReference(dst);

        // Loop over rows
        for (int y = 0; y < xrows; y++) {
            // Loop over columns
            dstPixel.setOffset(dst);

            for (int x = 0; x < xcolumns; dstPixel.incOffset()) {
                final int srcpix = bm.getByteAt(src + (x++));

                // Perform pixel operation
                if (srcpix != 0) {
                    if (srcpix >= maxgray) {
                        dstPixel.setBGR(gb, gg, gr);
                    } else {
                        final int level0 = multiplier[srcpix];
                        final int level1 = 0x10000 - level0;
                        dstPixel.setBGR(Math.min((dstPixel.getBlue() * level0 + gb * level1) >> 16, 255),
                                Math.min((dstPixel.getGreen() * level0 + gg * level1) >> 16, 255),
                                Math.min((dstPixel.getRed() * level0 + gr * level1) >> 16, 255));
                    }
                }
            }

            // Next line
            dst += getRowSize();
            src += bm.getRowSize();
        }
    }

    /**
     * Correct the colors with a gamma subsample normalized to 1.0 for no correction.
     * 
     * @param gamma color correction
     */
    public final void applyGammaCorrection(final double gamma) {
        if (((gamma > 0.999D) && (gamma < 1.0009999999999999D))) {
            return;
        }

        int[] gtable = getColorCorrection(gamma);

        for (int i = 0; i < data.length(); i++) {
            data.set(i, (byte) gtable[data.get(i)]);
        }
    }

    /**
     * Fill this image from another source at reduced resolution of 4 vertical
     * pixels to 3.  An extrapulating pixel averaging algorithm is used. 
     * 
     * @param src image map to reduce
     * @param pdr target bounds
     *
     * @throws IllegalArgumentException if the target rectangle is out of bounds
     */
    public void downsample43(final GPixmap src, final GRect pdr) {
        final int srcwidth = src.columns();
        final int srcheight = src.rows();
        int destwidth = (int) Math.ceil(srcwidth * 0.75D);
        int destheight = (int) Math.ceil(srcheight * 0.75D);
        GRect rect = new GRect(0, 0, destwidth, destheight);

        if (pdr != null) {
            if ((pdr.xmin < rect.xmin) || (pdr.ymin < rect.ymin) || (pdr.xmax > rect.xmax)
                    || (pdr.ymax > rect.ymax)) {
                throw new IllegalArgumentException("rectangle out of bounds" + "pdr=(" + pdr.xmin + "," + pdr.ymin
                        + "," + pdr.xmax + "," + pdr.ymax + "),rect=(" + rect.xmin + "," + rect.ymin + ","
                        + rect.xmax + "," + rect.ymax + ")");
            }

            rect = pdr;
            destwidth = rect.width();
            destheight = rect.height();
        }

        init(destheight, destwidth, null);

        int sy = rect.ymin / 3;
        int dy = rect.ymin - (3 * sy);

        //    if(dy < 0)
        //    {
        //      sy--;
        //      dy += 3;
        //    }

        int sxz = rect.xmin / 3;
        int dxz = rect.xmin - (3 * sxz);

        if (dxz < 0) {
            sxz--;
            dxz += 3;
        }

        sxz *= 4;
        sy *= 4;

        final GPixelReference spix0 = src.createGPixelReference(0);
        final GPixelReference spix1 = src.createGPixelReference(0);
        final GPixelReference spix2 = src.createGPixelReference(0);
        final GPixelReference spix3 = src.createGPixelReference(0);
        final GPixelReference dpix0 = createGPixelReference(0);
        final GPixelReference dpix1 = createGPixelReference(0);
        final GPixelReference dpix2 = createGPixelReference(0);
        while (dy < destheight) {
            spix0.setOffset(sy++, sxz);

            if (sy >= srcheight) {
                sy--;
            }

            spix1.setOffset(sy++, sxz);

            if (sy >= srcheight) {
                sy--;
            }

            spix2.setOffset(sy++, sxz);

            if (sy >= srcheight) {
                sy--;
            }

            spix3.setOffset(sy++, sxz);

            dpix0.setOffset((dy < 0) ? 0 : dy, dxz);

            if (++dy >= destheight) {
                dy--;
            }

            dpix1.setOffset((dy < 0) ? 0 : dy, dxz);

            if (++dy >= destheight) {
                dy--;
            }

            dpix2.setOffset(dy++, dxz);
            int dx = dxz;
            int sx = sxz;

            GPixel pix0 = src.ramp(spix0);
            GPixel pix1 = src.ramp(spix1);
            GPixel pix2 = src.ramp(spix2);
            GPixel pix3 = src.ramp(spix3);
            while (dx < destwidth) {
                final int s00b = pix0.getBlue();
                final int s00g = pix0.getGreen();
                final int s00r = pix0.getRed();
                final int s01b = pix1.getBlue();
                final int s01g = pix1.getGreen();
                final int s01r = pix1.getRed();
                final int s02b = pix2.getBlue();
                final int s02g = pix2.getGreen();
                final int s02r = pix2.getRed();
                final int s03b = pix3.getBlue();
                final int s03g = pix3.getGreen();
                final int s03r = pix3.getRed();

                if (++sx < srcwidth) {
                    spix0.incOffset();
                    spix1.incOffset();
                    spix2.incOffset();
                    spix3.incOffset();
                    pix0 = src.ramp(spix0);
                    pix1 = src.ramp(spix1);
                    pix2 = src.ramp(spix2);
                    pix3 = src.ramp(spix3);
                }

                final int s10b = pix0.getBlue();
                final int s10g = pix0.getGreen();
                final int s10r = pix0.getRed();
                final int s11b = pix1.getBlue();
                final int s11g = pix1.getGreen();
                final int s11r = pix1.getRed();
                final int s12b = pix2.getBlue();
                final int s12g = pix2.getGreen();
                final int s12r = pix2.getRed();
                final int s13b = pix3.getBlue();
                final int s13g = pix3.getGreen();
                final int s13r = pix3.getRed();

                if (++sx < srcwidth) {
                    spix0.incOffset();
                    spix1.incOffset();
                    spix2.incOffset();
                    spix3.incOffset();
                    pix0 = src.ramp(spix0);
                    pix1 = src.ramp(spix1);
                    pix2 = src.ramp(spix2);
                    pix3 = src.ramp(spix3);
                }

                final int s20b = pix0.getBlue();
                final int s20g = pix0.getGreen();
                final int s20r = pix0.getRed();
                final int s21b = pix1.getBlue();
                final int s21g = pix1.getGreen();
                final int s21r = pix1.getRed();
                final int s22b = pix2.getBlue();
                final int s22g = pix2.getGreen();
                final int s22r = pix2.getRed();
                final int s23b = pix3.getBlue();
                final int s23g = pix3.getGreen();
                final int s23r = pix3.getRed();

                if (++sx < srcwidth) {
                    spix0.incOffset();
                    spix1.incOffset();
                    spix2.incOffset();
                    spix3.incOffset();
                    pix0 = src.ramp(spix0);
                    pix1 = src.ramp(spix1);
                    pix2 = src.ramp(spix2);
                    pix3 = src.ramp(spix3);
                }

                final int s30b = pix0.getBlue();
                final int s30g = pix0.getGreen();
                final int s30r = pix0.getRed();
                final int s31b = pix1.getBlue();
                final int s31g = pix1.getGreen();
                final int s31r = pix1.getRed();
                final int s32b = pix2.getBlue();
                final int s32g = pix2.getGreen();
                final int s32r = pix2.getRed();
                final int s33b = pix3.getBlue();
                final int s33g = pix3.getGreen();
                final int s33r = pix3.getRed();

                if (++sx < srcwidth) {
                    spix0.incOffset();
                    spix1.incOffset();
                    spix2.incOffset();
                    spix3.incOffset();
                    pix0 = src.ramp(spix0);
                    pix1 = src.ramp(spix1);
                    pix2 = src.ramp(spix2);
                    pix3 = src.ramp(spix3);
                }

                dpix0.setBlue(((11 * s00b) + (2 * (s01b + s10b)) + s11b + 8) >> 4);
                dpix0.setGreen(((11 * s00g) + (2 * (s01g + s10g)) + s11g + 8) >> 4);
                dpix0.setRed(((11 * s00r) + (2 * (s01r + s10r)) + s11r + 8) >> 4);
                dpix1.setBlue(((7 * (s01b + s02b)) + s11b + s12b + 8) >> 4);
                dpix1.setGreen(((7 * (s01g + s02g)) + s11g + s12g + 8) >> 4);
                dpix1.setRed(((7 * (s01r + s02r)) + s11r + s12r + 8) >> 4);
                dpix2.setBlue(((11 * s03b) + (2 * (s02b + s13b)) + s12b + 8) >> 4);
                dpix2.setGreen(((11 * s03g) + (2 * (s02g + s13g)) + s12g + 8) >> 4);
                dpix2.setRed(((11 * s03r) + (2 * (s02r + s13r)) + s12r + 8) >> 4);

                if (++dx < destwidth) {
                    dpix0.incOffset();
                    dpix1.incOffset();
                    dpix2.incOffset();
                }

                dpix0.setBlue(((7 * (s10b + s20b)) + s11b + s21b + 8) >> 4);
                dpix0.setGreen(((7 * (s10g + s20g)) + s11g + s21g + 8) >> 4);
                dpix0.setRed(((7 * (s10r + s20r)) + s11r + s21r + 8) >> 4);
                dpix1.setBlue((s12b + s22b + s11b + s21b + 2) >> 2);
                dpix1.setGreen((s12g + s22g + s11g + s21g + 2) >> 2);
                dpix1.setRed((s12r + s22r + s11r + s21r + 2) >> 2);
                dpix2.setBlue(((7 * (s13b + s23b)) + s12b + s22b + 8) >> 4);
                dpix2.setGreen(((7 * (s13g + s23g)) + s12g + s22g + 8) >> 4);
                dpix2.setRed(((7 * (s13r + s23r)) + s12r + s22r + 8) >> 4);

                if (++dx < destwidth) {
                    dpix0.incOffset();
                    dpix1.incOffset();
                    dpix2.incOffset();
                }

                dpix0.setBlue(((11 * s30b) + (2 * (s31b + s20b)) + s21b + 8) >> 4);
                dpix0.setGreen(((11 * s30g) + (2 * (s31g + s20g)) + s21g + 8) >> 4);
                dpix0.setRed(((11 * s30r) + (2 * (s31r + s20r)) + s21r + 8) >> 4);
                dpix1.setBlue(((7 * (s31b + s32b)) + s21b + s22b + 8) >> 4);
                dpix1.setGreen(((7 * (s31g + s32g)) + s21g + s22g + 8) >> 4);
                dpix1.setRed(((7 * (s31r + s32r)) + s21r + s22r + 8) >> 4);
                dpix2.setBlue(((11 * s33b) + (2 * (s32b + s23b)) + s22b + 8) >> 4);
                dpix2.setGreen(((11 * s33g) + (2 * (s32g + s23g)) + s22g + 8) >> 4);
                dpix2.setRed(((11 * s33r) + (2 * (s32r + s23r)) + s22r + 8) >> 4);

                if (++dx < destwidth) {
                    dpix0.incOffset();
                    dpix1.incOffset();
                    dpix2.incOffset();
                }
            }
        }
    }

    /**
     * Initiallize this pixmap with a preallocated buffer.
     *
     * @param data buffer to use
     * @param arows number of rows
     * @param acolumns number of columns
     *
     * @return the initialized pixmap
     */
    GPixmap init(Uint8Array data, int arows, int acolumns) {
        nrows = arows;
        ncolumns = acolumns;
        this.data = data;

        return this;
    }

    /**
     * Initialize this pixmap to the specified size and fill in the specified color.
     *
     * @param arows number of rows
     * @param acolumns number of columns
     * @param filler fill color
     *
     * @return the initialized pixmap
     */
    public GPixmap init(int arows, int acolumns, GPixel filler) {
        //    boolean needFill=false;
        if ((arows != nrows) || (acolumns != ncolumns)) {
            data = null;
            nrows = arows;
            ncolumns = acolumns;
        }

        final int npix = rowOffset(rows());

        if (npix > 0) {
            if (data == null) {
                setImageData(imageDataFactory.createImageData(ncolumns, nrows));
                if (filler == null) {
                    for (int i = 0; i < npix; i++)
                        data.set(i * ncolors + 3, 0xFF);
                }
            }

            if (filler != null) {
                data.set(redOffset, filler.redByte());
                data.set(greenOffset, filler.greenByte());
                data.set(blueOffset, filler.blueByte());
                data.set(3, 0xFF);
                Int32Array fillBuffer = TypedArrays.createInt32Array(data.buffer());
                int fillValue = fillBuffer.get(0);
                for (int i = 0; i < npix; i++)
                    fillBuffer.set(i, fillValue);
            }
        }

        return this;
    }

    /**
     * Draw the foreground layer onto this background image.
     * 
     * @param mask the mask layer
     * @param foregroundMap the foreground colors
     * @param supersample rate to upsample the foreground colors
     * @param subsample rate to subsample the foreground colors
     * @param bounds the target rectangle
     * @param gamma color correction factor
     * 
     * @throws IllegalArgumentException if the specified bounds are not contained in the page
     */
    public void stencil(final GBitmap mask, final GPixmap foregroundMap, final int supersample, final int subsample,
            final GRect bounds, final double gamma) {
        // Check arguments
        GRect rect = new GRect(0, 0, (foregroundMap.columns() * supersample + subsample - 1) / subsample,
                (foregroundMap.rows() * supersample + subsample - 1) / subsample);

        if (bounds != null) {
            if ((bounds.xmin < rect.xmin) || (bounds.ymin < rect.ymin) || (bounds.xmax > rect.xmax)
                    || (bounds.ymax > rect.ymax)) {
                throw new IllegalArgumentException("rectangle out of bounds" + "bounds=(" + bounds.xmin + ","
                        + bounds.ymin + "," + bounds.xmax + "," + bounds.ymax + "),rect=(" + rect.xmin + ","
                        + rect.ymin + "," + rect.xmax + "," + rect.ymax + ")");
            }

            rect = bounds;
        }

        // Compute number of rows
        int xrows = Math.min(Math.min(rows(), mask.rows()), rect.height());

        // Compute number of columns
        int xcolumns = Math.min(Math.min(columns(), mask.columns()), rect.width());

        // Precompute multiplier map
        int maxgray = mask.getGrays() - 1;

        // Prepare color correction table
        int[] gtable = getColorCorrection(gamma);

        double ratioFg = (double) supersample / (double) subsample;
        // Compute starting point in blown up foreground pixmap
        int fgy = (rect.ymin * subsample) / supersample;
        double fgy1 = rect.ymin - ratioFg * fgy;

        if (fgy1 < 0) {
            fgy--;
            fgy1 += ratioFg;
        }

        int fgxz = (rect.xmin * subsample) / supersample;
        double fgx1z = rect.xmin - ratioFg * fgxz;

        if (fgx1z < 0) {
            fgxz--;
            fgx1z += ratioFg;
        }

        int fg = foregroundMap.rowOffset(fgy);
        GPixelReference fgx = foregroundMap.createGPixelReference(0);
        GPixelReference dst = createGPixelReference(0);

        // Loop over rows
        for (int y = 0; y < xrows; y++) {
            // Loop over columns
            fgx.setOffset(fg + fgxz);

            double fgx1 = fgx1z;
            dst.setOffset(y, 0);

            int src = mask.rowOffset(y);

            for (int x = 0; x < xcolumns; x++, dst.incOffset()) {
                int srcpix = mask.getByteAt(src + x);

                // Perform pixel operation
                if (srcpix > 0) {
                    if (srcpix >= maxgray) {
                        dst.setBGR(gtable[fgx.getBlue()], gtable[fgx.getGreen()], gtable[fgx.getRed()]);
                    } else {
                        int level = (0x10000 * srcpix) / maxgray;
                        dst.setBGR(((dst.getBlue() * (0x10000 - level)) + (level * gtable[fgx.getBlue()])) >> 16,
                                ((dst.getGreen() * (0x10000 - level)) + (level * gtable[fgx.getGreen()])) >> 16,
                                ((dst.getRed() * (0x10000 - level)) + (level * gtable[fgx.getRed()])) >> 16);
                    }
                }

                // Next column
                if (++fgx1 >= ratioFg) {
                    fgx1 -= ratioFg;
                    fgx.incOffset();
                }
            }

            // Next line
            if (++fgy1 >= ratioFg) {
                fgy1 -= ratioFg;
                fg += foregroundMap.getRowSize();
            }
        }
    }

    /**
     * Create a GPixelReference (a pixel iterator) that refers to this map
     * starting at the specified offset.
     *
     * @param offset position of the first pixel to reference
     *
     * @return the newly created GPixelReference
     */
    public GPixelReference createGPixelReference(final int offset) {
        return new GPixelReference(this, offset);
    }

    /**
     * Create a GPixelReference (a pixel iterator) that refers to this map
     * starting at the specified position.
     *
     * @param row initial vertical position
     * @param column initial horizontal position
     *
     * @return the newly created GPixelReference
     */
    public GPixelReference createGPixelReference(final int row, final int column) {
        return new GPixelReference(this, row, column);
    }

    @Override
    public int getWidth() {
        return columns();
    }

    @Override
    public int getHeight() {
        return rows();
    }

    @Override
    public GPixmap getPixmap(int subsample, GRect rect, GPixmap retval) {
        if (retval == null)
            retval = new GPixmap();
        retval.init(rect.height(), rect.width(), null);
        GPixelReference src = new GPixelReference(this, 0);
        GPixelReference dst = new GPixelReference(retval, 0);
        for (int x = rect.xmin; x < rect.xmax; x++) {
            for (int y = rect.ymin; y < rect.ymax; y++) {
                dst.setOffset(y - rect.ymin, x - rect.xmin);
                int r = 0, g = 0, b = 0;
                int count = 0;
                for (int x2 = x * subsample; x2 < (x + 1) * subsample && x2 < getWidth(); x2++) {
                    for (int y2 = y * subsample; y2 < (y + 1) * subsample && y2 < getHeight(); y2++) {
                        src.setOffset(getHeight() - y2, x2);
                        r += src.getRed();
                        g += src.getGreen();
                        b += src.getBlue();
                        count++;
                    }
                }
                dst.setRed(r / count);
                dst.setGreen(g / count);
                dst.setBlue(b / count);
            }
        }
        return retval;
    }

    @Override
    public void decode(CachedInputStream pool) throws IOException {
        throw new IllegalStateException();
    }

    @Override
    public boolean isImageData() {
        return false;
    }
}