View Javadoc
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 }