1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 19 package org.apache.commons.imaging.common; 20 21 import java.awt.color.ColorSpace; 22 import java.awt.image.BufferedImage; 23 import java.awt.image.ColorModel; 24 import java.awt.image.DataBuffer; 25 import java.awt.image.DataBufferInt; 26 import java.awt.image.DirectColorModel; 27 import java.awt.image.Raster; 28 import java.awt.image.RasterFormatException; 29 import java.awt.image.WritableRaster; 30 import java.util.Properties; 31 32 /* 33 * Development notes: 34 * This class was introduced to the Apache Commons Imaging library in 35 * order to improve performance in building images. The setRGB method 36 * provided by this class represents a substantial improvement in speed 37 * compared to that of the BufferedImage class that was originally used 38 * in Apache Sanselan. 39 * This increase is attained because ImageBuilder is a highly specialized 40 * class that does not need to perform the general-purpose logic required 41 * for BufferedImage. If you need to modify this class to add new 42 * image formats or functionality, keep in mind that some of its methods 43 * are invoked literally millions of times when building an image. 44 * Since even the introduction of something as small as a single conditional 45 * inside of setRGB could result in a noticeable increase in the 46 * time to read a file, changes should be made with care. 47 * During development, I experimented with inlining the setRGB logic 48 * in some of the code that uses it. This approach did not significantly 49 * improve performance, leading me to speculate that the Java JIT compiler 50 * might have inlined the method at run time. Further investigation 51 * is required. 52 * 53 */ 54 55 /** 56 * A utility class primary intended for storing data obtained by reading 57 * image files. 58 */ 59 public class ImageBuilder { 60 private final int[] data; 61 private final int width; 62 private final int height; 63 private final boolean hasAlpha; 64 private final boolean isAlphaPremultiplied; 65 66 /** 67 * Construct an ImageBuilder instance 68 * @param width the width of the image to be built 69 * @param height the height of the image to be built 70 * @param hasAlpha indicates whether the image has an alpha channel 71 * (the selection of alpha channel does not change the memory 72 * requirements for the ImageBuilder or resulting BufferedImage. 73 */ 74 public ImageBuilder(final int width, final int height, final boolean hasAlpha) { 75 checkDimensions(width, height); 76 77 data = new int[width * height]; 78 this.width = width; 79 this.height = height; 80 this.hasAlpha = hasAlpha; 81 this.isAlphaPremultiplied = false; 82 } 83 84 85 /** 86 * Construct an ImageBuilder instance 87 * @param width the width of the image to be built 88 * @param height the height of the image to be built 89 * @param hasAlpha indicates whether the image has an alpha channel 90 * (the selection of alpha channel does not change the memory 91 * requirements for the ImageBuilder or resulting BufferedImage. 92 * @param isAlphaPremultiplied indicates whether alpha values are 93 * pre-multiplied; this setting is relevant only if alpha is true. 94 * 95 */ 96 public ImageBuilder(final int width, final int height, 97 final boolean hasAlpha, final boolean isAlphaPremultiplied) { 98 checkDimensions(width, height); 99 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/common/ImageBuilder.html#ImageBuilder">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 }