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}