org.apache.commons.imaging.common.ImageBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-imaging Show documentation
Show all versions of commons-imaging Show documentation
Apache Commons Imaging (previously Sanselan) is a pure-Java image library.
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.imaging.common;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.RasterFormatException;
import java.awt.image.WritableRaster;
import java.util.Properties;
/*
* Development notes:
* This class was introduced to the Apache Commons Imaging library in
* order to improve performance in building images. The setRGB method
* provided by this class represents a substantial improvement in speed
* compared to that of the BufferedImage class that was originally used
* in Apache Sanselan.
* This increase is attained because ImageBuilder is a highly specialized
* class that does not need to perform the general-purpose logic required
* for BufferedImage. If you need to modify this class to add new
* image formats or functionality, keep in mind that some of its methods
* are invoked literally millions of times when building an image.
* Since even the introduction of something as small as a single conditional
* inside of setRGB could result in a noticeable increase in the
* time to read a file, changes should be made with care.
* During development, I experimented with inlining the setRGB logic
* in some of the code that uses it. This approach did not significantly
* improve performance, leading me to speculate that the Java JIT compiler
* might have inlined the method at run time. Further investigation
* is required.
*/
/**
* A utility class primary intended for storing data obtained by reading image files.
*/
public class ImageBuilder {
private final int[] data;
private final int width;
private final int height;
private final boolean hasAlpha;
private final boolean isAlphaPremultiplied;
/**
* Constructs an ImageBuilder instance.
*
* @param width the width of the image to be built
* @param height the height of the image to be built
* @param hasAlpha indicates whether the image has an alpha channel (the selection of alpha channel does not change the memory requirements for the
* ImageBuilder or resulting BufferedImage.
* @throws RasterFormatException if {@code width} or {@code height} are equal or less than zero
*/
public ImageBuilder(final int width, final int height, final boolean hasAlpha) {
checkDimensions(width, height);
data = Allocator.intArray(width * height);
this.width = width;
this.height = height;
this.hasAlpha = hasAlpha;
this.isAlphaPremultiplied = false;
}
/**
* Constructs an ImageBuilder instance.
*
* @param width the width of the image to be built
* @param height the height of the image to be built
* @param hasAlpha indicates whether the image has an alpha channel (the selection of alpha channel does not change the memory requirements for
* the ImageBuilder or resulting BufferedImage.
* @param isAlphaPremultiplied indicates whether alpha values are pre-multiplied; this setting is relevant only if alpha is true.
* @throws RasterFormatException if {@code width} or {@code height} are equal or less than zero
*/
public ImageBuilder(final int width, final int height, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
checkDimensions(width, height);
data = Allocator.intArray(width * height);
this.width = width;
this.height = height;
this.hasAlpha = hasAlpha;
this.isAlphaPremultiplied = isAlphaPremultiplied;
}
/**
* Performs a check on the specified sub-region to verify that it is within the constraints of the ImageBuilder bounds.
*
* @param x the X coordinate of the upper-left corner of the specified rectangular region
* @param y the Y coordinate of the upper-left corner of the specified rectangular region
* @param w the width of the specified rectangular region
* @param h the height of the specified rectangular region
* @throws RasterFormatException if width or height are equal or less than zero, or if the subimage is outside raster (on x or y axis)
*/
private void checkBounds(final int x, final int y, final int w, final int h) {
if (w <= 0) {
throw new RasterFormatException("negative or zero subimage width");
}
if (h <= 0) {
throw new RasterFormatException("negative or zero subimage height");
}
if (x < 0 || x >= width) {
throw new RasterFormatException("subimage x is outside raster");
}
if (x + w > width) {
throw new RasterFormatException("subimage (x+width) is outside raster");
}
if (y < 0 || y >= height) {
throw new RasterFormatException("subimage y is outside raster");
}
if (y + h > height) {
throw new RasterFormatException("subimage (y+height) is outside raster");
}
}
/**
* Checks for valid dimensions and throws {@link RasterFormatException} if the inputs are invalid.
*
* @param width image width (must be greater than zero)
* @param height image height (must be greater than zero)
* @throws RasterFormatException if {@code width} or {@code height} are equal or less than zero
*/
private void checkDimensions(final int width, final int height) {
if (width <= 0) {
throw new RasterFormatException("zero or negative width value");
}
if (height <= 0) {
throw new RasterFormatException("zero or negative height value");
}
}
/**
* Create a BufferedImage using the data stored in the ImageBuilder.
*
* @return a valid BufferedImage.
*/
public BufferedImage getBufferedImage() {
return makeBufferedImage(data, width, height, hasAlpha);
}
/**
* Gets the height of the ImageBuilder pixel field
*
* @return a positive integer
*/
public int getHeight() {
return height;
}
/**
* Gets the RGB or ARGB value for the pixel at the position (x,y) within the image builder pixel field. For performance reasons no bounds checking is
* applied.
*
* @param x the X coordinate of the pixel to be read
* @param y the Y coordinate of the pixel to be read
* @return the RGB or ARGB pixel value
*/
public int getRgb(final int x, final int y) {
final int rowOffset = y * width;
return data[rowOffset + x];
}
/**
* Gets a subimage from the ImageBuilder using the specified parameters. If the parameters specify a rectangular region that is not entirely contained
* within the bounds defined by the ImageBuilder, this method will throw a RasterFormatException. This runtime-exception behavior is consistent with the
* behavior of the getSubimage method provided by BufferedImage.
*
* @param x the X coordinate of the upper-left corner of the specified rectangular region
* @param y the Y coordinate of the upper-left corner of the specified rectangular region
* @param w the width of the specified rectangular region
* @param h the height of the specified rectangular region
* @return a BufferedImage that constructed from the data within the specified rectangular region
* @throws RasterFormatException f the specified area is not contained within this ImageBuilder
*/
public BufferedImage getSubimage(final int x, final int y, final int w, final int h) {
checkBounds(x, y, w, h);
// Transcribe the data to an output image array
final int[] argb = Allocator.intArray(w * h);
int k = 0;
for (int iRow = 0; iRow < h; iRow++) {
final int dIndex = (iRow + y) * width + x;
System.arraycopy(this.data, dIndex, argb, k, w);
k += w;
}
return makeBufferedImage(argb, w, h, hasAlpha);
}
/**
* Gets a subset of the ImageBuilder content using the specified parameters to indicate an area of interest. If the parameters specify a rectangular region
* that is not entirely contained within the bounds defined by the ImageBuilder, this method will throw a RasterFormatException. This run- time exception is
* consistent with the behavior of the getSubimage method provided by BufferedImage.
*
* @param x the X coordinate of the upper-left corner of the specified rectangular region
* @param y the Y coordinate of the upper-left corner of the specified rectangular region
* @param w the width of the specified rectangular region
* @param h the height of the specified rectangular region
* @return a valid instance of the specified width and height.
* @throws RasterFormatException if the specified area is not contained within this ImageBuilder
*/
public ImageBuilder getSubset(final int x, final int y, final int w, final int h) {
checkBounds(x, y, w, h);
final ImageBuilder b = new ImageBuilder(w, h, hasAlpha, isAlphaPremultiplied);
for (int i = 0; i < h; i++) {
final int srcDex = (i + y) * width + x;
final int outDex = i * w;
System.arraycopy(data, srcDex, b.data, outDex, w);
}
return b;
}
/**
* Gets the width of the ImageBuilder pixel field
*
* @return a positive integer
*/
public int getWidth() {
return width;
}
private BufferedImage makeBufferedImage(final int[] argb, final int w, final int h, final boolean useAlpha) {
ColorModel colorModel;
WritableRaster raster;
final DataBufferInt buffer = new DataBufferInt(argb, w * h);
if (useAlpha) {
colorModel = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000,
isAlphaPremultiplied, DataBuffer.TYPE_INT);
raster = Raster.createPackedRaster(buffer, w, h, w, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }, null);
} else {
colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
raster = Raster.createPackedRaster(buffer, w, h, w, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff }, null);
}
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
}
/**
* Sets the RGB or ARGB value for the pixel at position (x,y) within the image builder pixel field. For performance reasons, no bounds checking is applied.
*
* @param x the X coordinate of the pixel to be set.
* @param y the Y coordinate of the pixel to be set.
* @param argb the RGB or ARGB value to be stored.
* @throws ArithmeticException if the index computation overflows an int.
* @throws IllegalArgumentException if the resulting index is illegal.
*/
public void setRgb(final int x, final int y, final int argb) {
// Throw ArithmeticException if the result overflows an int.
final int rowOffset = Math.multiplyExact(y, width);
// Throw ArithmeticException if the result overflows an int.
final int index = Math.addExact(rowOffset, x);
if (index > data.length) {
throw new IllegalArgumentException("setRGB: Illegal array index.");
}
data[index] = argb;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy