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}