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}