    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018
    019/**
    020 * Development notes:
    021 * This class was introduced to the Apache Commons Imaging library in
    022 * order to improve performance in building images.  The setRGB method
    023 * provided by this class represents a substantial improvement in speed
    024 * compared to that of the BufferedImage class that was originally used
    025 * in Apache Sanselan.
    026 *   This increase is attained because ImageBuilder is a highly specialized
    027 * class that does not need to perform the general-purpose logic required
    028 * for BufferedImage.  If you need to modify this class to add new
    029 * image formats or functionality, keep in mind that some of its methods
    030 * are invoked literally millions of times when building an image.
    031 * Since even the introduction of something as small as a single conditional
    032 * inside of setRGB could result in a noticeable increase in the
    033 * time to read a file, changes should be made with care.
    034 *    During development, I experimented with inlining the setRGB logic
    035 * in some of the code that uses it. This approach did not significantly
    036 * improve performance, leading me to speculate that the Java JIT compiler
    037 * might have inlined the method at run time.  Further investigation
    038 * is required.
    039 *
    040 */
    041package org.apache.commons.imaging.common;
    042
    043import java.awt.image.BufferedImage;
    044import java.awt.image.ColorModel;
    045import java.awt.image.DataBufferInt;
    046import java.awt.image.DirectColorModel;
    047import java.awt.image.Raster;
    048import java.awt.image.RasterFormatException;
    049import java.awt.image.WritableRaster;
    050import java.util.Properties;
    051
    052/**
    053 * A utility class primary intended for storing data obtained by reading
    054 * image files.
    055 */
    056public class ImageBuilder {
    057    private final int[] data;
    058    private final int width;
    059    private final int height;
    060    private final boolean hasAlpha;
    061
    062    /**
    063     * Construct an ImageBuilder instance
    064     * @param width the width of the image to be built
    065     * @param height the height of the image to be built
    066     * @param hasAlpha indicates whether the image has an alpha channel
    067     * (the selection of alpha channel does not change the memory
    068     * requirements for the ImageBuilder or resulting BufferedImage.
    069     */
    070    public ImageBuilder(final int width, final int height, final boolean hasAlpha) {
    071        if (width <= 0) {
    072            throw new RasterFormatException("zero or negative width value");
    073        }
    074        if (height <= 0) {
    075            throw new RasterFormatException("zero or negative height value");
    076        }
    077
    078        data = new int[width * height];
    079        this.width = width;
    080        this.height = height;
    081        this.hasAlpha = hasAlpha;
    082    }
    083
    084    /**
    085     * Get the width of the ImageBuilder pixel field
    086     * @return a positive integer
    087     */
    088    public int getWidth() {
    089        return width;
    090    }
    091
    092    /**
    093     * Get the height of the ImageBuilder pixel field
    094     * @return  a positive integer
    095     */
    096    public int getHeight() {
    097        return height;
    098    }
    099
    100    /**
    101     * Get the RGB or ARGB value for the pixel at the position (x,y)
    102     * within the image builder pixel field. For performance reasons
    103     * no bounds checking is applied.
    104     * @param x the X coordinate of the pixel to be read
    105     * @param y the Y coordinate of the pixel to be read
    106     * @return the RGB or ARGB pixel value
    107     */
    108    public int getRGB(final int x, final int y) {
    109        final int rowOffset = y * width;
    110        return data[rowOffset + x];
    111    }
    112
    113    /**
    114     * Set the RGB or ARGB value for the pixel at position (x,y)
    115     * within the image builder pixel field. For performance reasons,
    116     * no bounds checking is applied.
    117     * @param x the X coordinate of the pixel to be set
    118     * @param y the Y coordinate of the pixel to be set
    119     * @param argb the RGB or ARGB value to be stored.
    120     */
    121    public void setRGB(final int x, final int y, final int argb) {
    122        final int rowOffset = y * width;
    123        data[rowOffset + x] = argb;
    124    }
    125
    126    /**
    127     * Create a BufferedImage using the data stored in the ImageBuilder.
    128     * @return a valid BufferedImage.
    129     */
    130    public BufferedImage getBufferedImage() {
    131        return makeBufferedImage(data, width, height, hasAlpha);
    132    }
    133
    134    /**
    135     * Gets a subimage from the ImageBuilder using the specified parameters.
    136     * If the parameters specify a rectangular region that is not entirely
    137     * contained within the bounds defined by the ImageBuilder, this method will
    138     * throw a RasterFormatException.  This runtime-exception behavior
    139     * is consistent with the behavior of the getSubimage method
    140     * provided by BufferedImage.
    141     * @param x the X coordinate of the upper-left corner of the
    142     *          specified rectangular region
    143     * @param y the Y coordinate of the upper-left corner of the
    144     *          specified rectangular region
    145     * @param w the width of the specified rectangular region
    146     * @param h the height of the specified rectangular region
    147     * @return a BufferedImage that constructed from the data within the
    148     *           specified rectangular region
    149     * @throws RasterFormatException f the specified area is not contained
    150     *         within this ImageBuilder
    151     */
    152    public BufferedImage getSubimage(final int x, final int y, final int w, final int h) {
    153        if (w <= 0) {
    154            throw new RasterFormatException("negative or zero subimage width");
    155        }
    156        if (h <= 0) {
    157            throw new RasterFormatException("negative or zero subimage height");
    158        }
    159        if (x < 0 || x >= width) {
    160            throw new RasterFormatException("subimage x is outside raster");
    161        }
    162        if (x + w > width) {
    163            throw new RasterFormatException(
    164                    "subimage (x+width) is outside raster");
    165        }
    166        if (y < 0 || y >= height) {
    167            throw new RasterFormatException("subimage y is outside raster");
    168        }
    169        if (y + h > height) {
    170            throw new RasterFormatException(
    171                    "subimage (y+height) is outside raster");
    172        }
    173
    174
    175        // Transcribe the data to an output image array
    176        final int[] argb = new int[w * h];
    177        int k = 0;
    178        for (int iRow = 0; iRow < h; iRow++) {
    179            final int dIndex = (iRow + y) * width + x;
    180            System.arraycopy(this.data, dIndex, argb, k, w);
    181            k += w;
    182
    183        }
    184
    185        return makeBufferedImage(argb, w, h, hasAlpha);
    186
    187    }
    188
    189    private BufferedImage makeBufferedImage(
    190            final int[] argb, final int w, final int h, final boolean useAlpha) {
    191        ColorModel colorModel;
    192        WritableRaster raster;
    193        final DataBufferInt buffer = new DataBufferInt(argb, w * h);
    194        if (useAlpha) {
    195            colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00,
    196                    0x000000ff, 0xff000000);
    197            raster = Raster.createPackedRaster(buffer, w, h,
    198                    w, new int[]{0x00ff0000, 0x0000ff00, 0x000000ff,
    199                            0xff000000}, null);
    200        } else {
    201            colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00,
    202                    0x000000ff);
    203            raster = Raster.createPackedRaster(buffer, w, h,
    204                    w, new int[]{0x00ff0000, 0x0000ff00, 0x000000ff},
    205                    null);
    206        }
    207        return new BufferedImage(colorModel, raster,
    208                colorModel.isAlphaPremultiplied(), new Properties());
    209    }
    210}