    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
    019package org.apache.commons.imaging.common;
    020
    021import java.awt.color.ColorSpace;
    022import java.awt.image.BufferedImage;
    023import java.awt.image.ColorModel;
    024import java.awt.image.DataBuffer;
    025import java.awt.image.DataBufferInt;
    026import java.awt.image.DirectColorModel;
    027import java.awt.image.Raster;
    028import java.awt.image.RasterFormatException;
    029import java.awt.image.WritableRaster;
    030import java.util.Properties;
    031
    032/*
    033 * Development notes:
    034 * This class was introduced to the Apache Commons Imaging library in
    035 * order to improve performance in building images.  The setRGB method
    036 * provided by this class represents a substantial improvement in speed
    037 * compared to that of the BufferedImage class that was originally used
    038 * in Apache Sanselan.
    039 *   This increase is attained because ImageBuilder is a highly specialized
    040 * class that does not need to perform the general-purpose logic required
    041 * for BufferedImage.  If you need to modify this class to add new
    042 * image formats or functionality, keep in mind that some of its methods
    043 * are invoked literally millions of times when building an image.
    044 * Since even the introduction of something as small as a single conditional
    045 * inside of setRGB could result in a noticeable increase in the
    046 * time to read a file, changes should be made with care.
    047 *    During development, I experimented with inlining the setRGB logic
    048 * in some of the code that uses it. This approach did not significantly
    049 * improve performance, leading me to speculate that the Java JIT compiler
    050 * might have inlined the method at run time.  Further investigation
    051 * is required.
    052 *
    053 */
    054
    055/**
    056 * A utility class primary intended for storing data obtained by reading
    057 * image files.
    058 */
    059public class ImageBuilder {
    060    private final int[] data;
    061    private final int width;
    062    private final int height;
    063    private final boolean hasAlpha;
    064    private final boolean isAlphaPremultiplied;
    065
    066    /**
    067     * Construct an ImageBuilder instance
    068     * @param width the width of the image to be built
    069     * @param height the height of the image to be built
    070     * @param hasAlpha indicates whether the image has an alpha channel
    071     * (the selection of alpha channel does not change the memory
    072     * requirements for the ImageBuilder or resulting BufferedImage.
    073     */
    074    public ImageBuilder(final int width, final int height, final boolean hasAlpha) {
    075        checkDimensions(width, height);
    076
    077        data = new int[width * height];
    078        this.width = width;
    079        this.height = height;
    080        this.hasAlpha = hasAlpha;
    081        this.isAlphaPremultiplied = false;
    082    }
    083
    084
    085    /**
    086     * Construct an ImageBuilder instance
    087     * @param width the width of the image to be built
    088     * @param height the height of the image to be built
    089     * @param hasAlpha indicates whether the image has an alpha channel
    090     * (the selection of alpha channel does not change the memory
    091     * requirements for the ImageBuilder or resulting BufferedImage.
    092     * @param isAlphaPremultiplied indicates whether alpha values are
    093     * pre-multiplied; this setting is relevant only if alpha is true.
    094     *
    095     */
    096    public ImageBuilder(final int width, final int height,
    097        final boolean hasAlpha, final boolean isAlphaPremultiplied) {
    098        checkDimensions(width, height);
    099        data = new int[width * height];
    100        this.width = width;
    101        this.height = height;
    102        this.hasAlpha = hasAlpha;
    103        this.isAlphaPremultiplied = isAlphaPremultiplied;
    104    }
    105
    106    /**
    107     * @param width image width (must be greater than zero)
    108     * @param height image height (must be greater than zero)
    109     * @throws RasterFormatException if {@code width} or {@code height} are equal or less than zero
    110     */
    111    private void checkDimensions(final int width, final int height) {
    112        if (width <= 0) {
    113            throw new RasterFormatException("zero or negative width value");
    114        }
    115        if (height <= 0) {
    116            throw new RasterFormatException("zero or negative height value");
    117        }
    118
    119    }
    120
    121    /**
    122     * Get the width of the ImageBuilder pixel field
    123     * @return a positive integer
    124     */
    125    public int getWidth() {
    126        return width;
    127    }
    128
    129    /**
    130     * Get the height of the ImageBuilder pixel field
    131     * @return  a positive integer
    132     */
    133    public int getHeight() {
    134        return height;
    135    }
    136
    137    /**
    138     * Get the RGB or ARGB value for the pixel at the position (x,y)
    139     * within the image builder pixel field. For performance reasons
    140     * no bounds checking is applied.
    141     * @param x the X coordinate of the pixel to be read
    142     * @param y the Y coordinate of the pixel to be read
    143     * @return the RGB or ARGB pixel value
    144     */
    145    public int getRGB(final int x, final int y) {
    146        final int rowOffset = y * width;
    147        return data[rowOffset + x];
    148    }
    149
    150    /**
    151     * Set the RGB or ARGB value for the pixel at position (x,y)
    152     * within the image builder pixel field. For performance reasons,
    153     * no bounds checking is applied.
    154     * @param x the X coordinate of the pixel to be set
    155     * @param y the Y coordinate of the pixel to be set
    156     * @param argb the RGB or ARGB value to be stored.
    157     */
    158    public void setRGB(final int x, final int y, final int argb) {
    159        final int rowOffset = y * width;
    160        data[rowOffset + x] = argb;
    161    }
    162
    163    /**
    164     * Create a BufferedImage using the data stored in the ImageBuilder.
    165     * @return a valid BufferedImage.
    166     */
    167    public BufferedImage getBufferedImage() {
    168        return makeBufferedImage(data, width, height, hasAlpha);
    169    }
    170
    171    /**
    172     * Performs a check on the specified sub-region to verify
    173     * that it is within the constraints of the ImageBuilder bounds.
    174     *
    175     * @param x the X coordinate of the upper-left corner of the
    176     * specified rectangular region
    177     * @param y the Y coordinate of the upper-left corner of the
    178     * specified rectangular region
    179     * @param w the width of the specified rectangular region
    180     * @param h the height of the specified rectangular region
    181     * @throws RasterFormatException if width or height are equal or less than zero, or if the subimage is outside raster (on x or y axis)
    182     */
    183    private void checkBounds(final int x, final int y, final int w, final int h) {
    184        if (w <= 0) {
    185            throw new RasterFormatException("negative or zero subimage width");
    186        }
    187        if (h <= 0) {
    188            throw new RasterFormatException("negative or zero subimage height");
    189        }
    190        if (x < 0 || x >= width) {
    191            throw new RasterFormatException("subimage x is outside raster");
    192        }
    193        if (x + w > width) {
    194            throw new RasterFormatException(
    195                "subimage (x+width) is outside raster");
    196        }
    197        if (y < 0 || y >= height) {
    198            throw new RasterFormatException("subimage y is outside raster");
    199        }
    200        if (y + h > height) {
    201            throw new RasterFormatException(
    202                "subimage (y+height) is outside raster");
    203        }
    204    }
    205
    206     /**
    207     * Gets a subset of the ImageBuilder content using the specified parameters
    208     * to indicate an area of interest. If the parameters specify a rectangular
    209     * region that is not entirely contained within the bounds defined
    210     * by the ImageBuilder, this method will throw a RasterFormatException.
    211     * This run- time exception is consistent with the behavior of the
    212     * getSubimage method provided by BufferedImage.
    213     * @param x the X coordinate of the upper-left corner of the
    214     *          specified rectangular region
    215     * @param y the Y coordinate of the upper-left corner of the
    216     *          specified rectangular region
    217     * @param w the width of the specified rectangular region
    218     * @param h the height of the specified rectangular region
    219     * @return a valid instance of the specified width and height.
    220     * @throws RasterFormatException if the specified area is not contained
    221     *         within this ImageBuilder
    222     */
    223    public ImageBuilder getSubset(final int x, final int y, final int w, final int h) {
    224        checkBounds(x, y, w, h);
    225        final ImageBuilder b = new ImageBuilder(w, h, hasAlpha, isAlphaPremultiplied);
    226        for(int i=0; i<h; i++){
    227            final int srcDex = (i+y)*width+x;
    228            final int outDex = i*w;
    229            System.arraycopy(data, srcDex, b.data, outDex, w);
    230        }
    231        return b;
    232    }
    233
    234
    235    /**
    236     * Gets a subimage from the ImageBuilder using the specified parameters.
    237     * If the parameters specify a rectangular region that is not entirely
    238     * contained within the bounds defined by the ImageBuilder, this method will
    239     * throw a RasterFormatException.  This runtime-exception behavior
    240     * is consistent with the behavior of the getSubimage method
    241     * provided by BufferedImage.
    242     * @param x the X coordinate of the upper-left corner of the
    243     *          specified rectangular region
    244     * @param y the Y coordinate of the upper-left corner of the
    245     *          specified rectangular region
    246     * @param w the width of the specified rectangular region
    247     * @param h the height of the specified rectangular region
    248     * @return a BufferedImage that constructed from the data within the
    249     *           specified rectangular region
    250     * @throws RasterFormatException f the specified area is not contained
    251     *         within this ImageBuilder
    252     */
    253    public BufferedImage getSubimage(final int x, final int y, final int w, final int h) {
    254        checkBounds(x, y, w, h);
    255
    256        // Transcribe the data to an output image array
    257        final int[] argb = new int[w * h];
    258        int k = 0;
    259        for (int iRow = 0; iRow < h; iRow++) {
    260            final int dIndex = (iRow + y) * width + x;
    261            System.arraycopy(this.data, dIndex, argb, k, w);
    262            k += w;
    263
    264        }
    265
    266        return makeBufferedImage(argb, w, h, hasAlpha);
    267
    268    }
    269
    270    private BufferedImage makeBufferedImage(
    271            final int[] argb, final int w, final int h, final boolean useAlpha) {
    272        ColorModel colorModel;
    273        WritableRaster raster;
    274        final DataBufferInt buffer = new DataBufferInt(argb, w * h);
    275        if (useAlpha) {
    276            colorModel = new DirectColorModel(
    277                    ColorSpace.getInstance(ColorSpace.CS_sRGB),
    278                    32,
    279                    0x00ff0000, 0x0000ff00,
    280                    0x000000ff, 0xff000000,
    281                    isAlphaPremultiplied, DataBuffer.TYPE_INT);
    282            raster = Raster.createPackedRaster(
    283                    buffer, w, h, w,
    284                    new int[]{
    285                            0x00ff0000,
    286                            0x0000ff00,
    287                            0x000000ff,
    288                            0xff000000},
    289                    null);
    290        } else {
    291            colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00,
    292                    0x000000ff);
    293            raster = Raster.createPackedRaster(
    294                    buffer, w, h, w,
    295                    new int[]{
    296                        0x00ff0000,
    297                        0x0000ff00,
    298                        0x000000ff},
    299                    null);
    300        }
    301        return new BufferedImage(colorModel, raster,
    302                colorModel.isAlphaPremultiplied(), new Properties());
    303    }
    304}