ome.codecs.gui.AWTImageTools Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ome-codecs Show documentation
Show all versions of ome-codecs Show documentation
Image encoding and decoding routines.
The newest version!
/*
* #%L
* Image encoding and decoding routines.
* %%
* Copyright (C) 2005 - 2017 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package ome.codecs.gui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferDouble;
import java.awt.image.DataBufferFloat;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferUShort;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.Hashtable;
import loci.common.DataTools;
import ome.codecs.CodecException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A utility class with convenience methods for manipulating images
* in {@link java.awt.image.BufferedImage} form.
*
* To work with images in primitive array form,
* use the {@link ome.codecs.ImageTools} class.
*
* Much code was stolen and adapted from
*
* DrLaszloJamf's posts
* on the Java forums.
*
* @author Curtis Rueden ctrueden at wisc.edu
*/
public final class AWTImageTools {
// -- Constants --
/** ImageObserver for working with AWT images. */
protected static final Component OBS = new Container();
private static final Logger LOGGER =
LoggerFactory.getLogger(AWTImageTools.class);
// -- Constructor --
private AWTImageTools() { }
// -- Image construction - from 1D (single channel) data arrays --
/**
* Creates an image from the given single-channel byte data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param signed Whether the byte values should be treated as signed
* (-128 to 127) instead of unsigned (0 to 255).
*/
public static BufferedImage makeImage(byte[] data,
int w, int h, boolean signed)
{
return makeImage(new byte[][] {data}, w, h, signed);
}
/**
* Creates an image from the given single-channel short data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param signed Whether the short values should be treated as signed
* (-32768 to 32767) instead of unsigned (0 to 65535).
*/
public static BufferedImage makeImage(short[] data,
int w, int h, boolean signed)
{
return makeImage(new short[][] {data}, w, h, signed);
}
/**
* Creates an image from the given single-channel int data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param signed Whether the int values should be treated as signed
* (-2^31 to 2^31-1) instead of unsigned (0 to 2^32-1).
*/
public static BufferedImage makeImage(int[] data,
int w, int h, boolean signed)
{
return makeImage(new int[][] {data}, w, h, signed);
}
/**
* Creates an image from the given single-channel float data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
*/
public static BufferedImage makeImage(float[] data, int w, int h) {
return makeImage(new float[][] {data}, w, h);
}
/**
* Creates an image from the given single-channel double data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
*/
public static BufferedImage makeImage(double[] data, int w, int h) {
return makeImage(new double[][] {data}, w, h);
}
// -- Image construction - from 1D (interleaved or banded) data arrays --
/**
* Creates an image from the given byte data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param c Number of channels.
* @param interleaved If set, the channels are assumed to be interleaved;
* otherwise they are assumed to be sequential.
* For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
* while "RRR...GGG...BBB..." is sequential.
* @param signed Whether the byte values should be treated as signed
* (-128 to 127) instead of unsigned (0 to 255).
*/
public static BufferedImage makeImage(byte[] data,
int w, int h, int c, boolean interleaved, boolean signed)
{
if (c == 1) return makeImage(data, w, h, signed);
if (c > 2) return makeRGBImage(data, c, w, h, interleaved);
int dataType;
DataBuffer buffer;
dataType = DataBuffer.TYPE_BYTE;
if (signed) {
buffer = new SignedByteBuffer(data, c * w * h);
}
else {
buffer = new DataBufferByte(data, c * w * h);
}
return constructImage(c, dataType, w, h, interleaved, false, buffer);
}
/**
* Creates an image from the given short data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param c Number of channels.
* @param interleaved If set, the channels are assumed to be interleaved;
* otherwise they are assumed to be sequential.
* For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
* while "RRR...GGG...BBB..." is sequential.
* @param signed Whether the short values should be treated as signed
* (-32768 to 32767) instead of unsigned (0 to 65535).
*/
public static BufferedImage makeImage(short[] data,
int w, int h, int c, boolean interleaved, boolean signed)
{
if (c == 1) return makeImage(data, w, h, signed);
int dataType;
DataBuffer buffer;
if (signed) {
dataType = DataBuffer.TYPE_SHORT;
buffer = new SignedShortBuffer(data, c * w * h);
}
else {
dataType = DataBuffer.TYPE_USHORT;
buffer = new DataBufferUShort(data, c * w * h);
}
return constructImage(c, dataType, w, h, interleaved, false, buffer);
}
/**
* Creates an image from the given int data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param c Number of channels.
* @param interleaved If set, the channels are assumed to be interleaved;
* otherwise they are assumed to be sequential.
* For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
* while "RRR...GGG...BBB..." is sequential.
* @param signed Whether the int values should be treated as signed
* (-2^31 to 2^31-1) instead of unsigned (0 to 2^32-1).
*/
public static BufferedImage makeImage(int[] data,
int w, int h, int c, boolean interleaved, boolean signed)
{
if (c == 1) return makeImage(data, w, h, signed);
int dataType = DataBuffer.TYPE_INT;
DataBuffer buffer;
if (signed) {
buffer = new DataBufferInt(data, c * w * h);
}
else {
buffer = new UnsignedIntBuffer(data, c * w * h);
}
return constructImage(c, dataType, w, h, interleaved, false, buffer);
}
/**
* Creates an image from the given float data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param c Number of channels.
* @param interleaved If set, the channels are assumed to be interleaved;
* otherwise they are assumed to be sequential.
* For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
* while "RRR...GGG...BBB..." is sequential.
*/
public static BufferedImage makeImage(float[] data,
int w, int h, int c, boolean interleaved)
{
if (c == 1) return makeImage(data, w, h);
int dataType = DataBuffer.TYPE_FLOAT;
DataBuffer buffer = new DataBufferFloat(data, c * w * h);
return constructImage(c, dataType, w, h, interleaved, false, buffer);
}
/**
* Creates an image from the given double data.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param c Number of channels.
* @param interleaved If set, the channels are assumed to be interleaved;
* otherwise they are assumed to be sequential.
* For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
* while "RRR...GGG...BBB..." is sequential.
*/
public static BufferedImage makeImage(double[] data,
int w, int h, int c, boolean interleaved)
{
if (c == 1) return makeImage(data, w, h);
int dataType = DataBuffer.TYPE_DOUBLE;
DataBuffer buffer = new DataBufferDouble(data, c * w * h);
return constructImage(c, dataType, w, h, interleaved, false, buffer);
}
// -- Image construction - from 2D (banded) data arrays --
/**
* Creates an image from the given byte data.
*
* @param data Array containing image data.
* It is assumed that each channel corresponds to one element of the array.
* For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
* @param w Width of image plane.
* @param h Height of image plane.
* @param signed Whether the byte values should be treated as signed
* (-128 to 127) instead of unsigned (0 to 255).
*/
public static BufferedImage makeImage(byte[][] data,
int w, int h, boolean signed)
{
if (data.length > 2) return makeRGBImage(data, w, h);
int dataType;
DataBuffer buffer;
dataType = DataBuffer.TYPE_BYTE;
if (signed) {
buffer = new SignedByteBuffer(data, data[0].length);
}
else {
buffer = new DataBufferByte(data, data[0].length);
}
return constructImage(data.length, dataType, w, h, false, true, buffer);
}
/**
* Creates an image from the given short data.
*
* @param data Array containing image data.
* It is assumed that each channel corresponds to one element of the array.
* For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
* @param w Width of image plane.
* @param h Height of image plane.
* @param signed Whether the short values should be treated as signed
* (-32768 to 32767) instead of unsigned (0 to 65535).
*/
public static BufferedImage makeImage(short[][] data,
int w, int h, boolean signed)
{
int dataType;
DataBuffer buffer;
if (signed) {
dataType = DataBuffer.TYPE_SHORT;
buffer = new SignedShortBuffer(data, data[0].length);
}
else {
dataType = DataBuffer.TYPE_USHORT;
buffer = new DataBufferUShort(data, data[0].length);
}
return constructImage(data.length, dataType, w, h, false, true, buffer);
}
/**
* Creates an image from the given int data.
*
* @param data Array containing image data.
* It is assumed that each channel corresponds to one element of the array.
* For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
* @param w Width of image plane.
* @param h Height of image plane.
* @param signed Whether the int values should be treated as signed
* (-2^31 to 2^31-1) instead of unsigned (0 to 2^32-1).
*/
public static BufferedImage makeImage(int[][] data,
int w, int h, boolean signed)
{
int dataType = DataBuffer.TYPE_INT;
DataBuffer buffer;
if (signed) {
buffer = new DataBufferInt(data, data[0].length);
}
else {
buffer = new UnsignedIntBuffer(data, data[0].length);
}
return constructImage(data.length, dataType, w, h, false, true, buffer);
}
/**
* Creates an image from the given single-precision floating point data.
*
* @param data Array containing image data.
* It is assumed that each channel corresponds to one element of the array.
* For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
* @param w Width of image plane.
* @param h Height of image plane.
*/
public static BufferedImage makeImage(float[][] data, int w, int h) {
int dataType = DataBuffer.TYPE_FLOAT;
DataBuffer buffer = new DataBufferFloat(data, data[0].length);
return constructImage(data.length, dataType, w, h, false, true, buffer);
}
/**
* Creates an image from the given double-precision floating point data.
*
* @param data Array containing image data.
* It is assumed that each channel corresponds to one element of the array.
* For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
* @param w Width of image plane.
* @param h Height of image plane.
*/
public static BufferedImage makeImage(double[][] data, int w, int h) {
int dataType = DataBuffer.TYPE_DOUBLE;
DataBuffer buffer = new DataBufferDouble(data, data[0].length);
return constructImage(data.length, dataType, w, h, false, true, buffer);
}
// -- Image construction - with type conversion --
/**
* Creates an image from the given raw byte array,
* performing any necessary type conversions.
*
* @param data Array containing image data.
* @param w Width of image plane.
* @param h Height of image plane.
* @param c Number of channels.
* @param interleaved If set, the channels are assumed to be interleaved;
* otherwise they are assumed to be sequential.
* For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
* while "RRR...GGG...BBB..." is sequential.
* @param bpp Denotes the number of bytes in the returned primitive type
* (e.g. if bpp == 2, we should return an array of type short).
* @param fp If set and bpp == 4 or bpp == 8, then return floats or doubles.
* @param little Whether byte array is in little-endian order.
* @param signed Whether the data values should be treated as signed
* instead of unsigned.
*/
public static BufferedImage makeImage(byte[] data, int w, int h, int c,
boolean interleaved, int bpp, boolean fp, boolean little, boolean signed)
{
Object pixels = DataTools.makeDataArray(data,
bpp % 3 == 0 ? bpp / 3 : bpp, fp, little);
if (pixels instanceof byte[]) {
return makeImage((byte[]) pixels, w, h, c, interleaved, signed);
}
else if (pixels instanceof short[]) {
return makeImage((short[]) pixels, w, h, c, interleaved, signed);
}
else if (pixels instanceof int[]) {
return makeImage((int[]) pixels, w, h, c, interleaved, signed);
}
else if (pixels instanceof float[]) {
return makeImage((float[]) pixels, w, h, c, interleaved);
}
else if (pixels instanceof double[]) {
return makeImage((double[]) pixels, w, h, c, interleaved);
}
return null;
}
/**
* Creates an image from the given raw byte array,
* performing any necessary type conversions.
*
* @param data Array containing image data, one channel per element.
* @param w Width of image plane.
* @param h Height of image plane.
* @param bpp Denotes the number of bytes in the returned primitive type
* (e.g. if bpp == 2, we should return an array of type short).
* @param fp If set and bpp == 4 or bpp == 8, then return floats or doubles.
* @param little Whether byte array is in little-endian order.
* @param signed Whether the data values should be treated as signed
* instead of unsigned.
*/
public static BufferedImage makeImage(byte[][] data,
int w, int h, int bpp, boolean fp, boolean little, boolean signed)
{
int c = data.length;
Object v = null;
for (int i=0; i 4) {
throw new IllegalArgumentException(
"Cannot construct image with " + c + " channels");
}
if (colorModel == null || colorModel instanceof DirectColorModel) {
colorModel = makeColorModel(c, type);
if (colorModel == null) return null;
if (buffer instanceof UnsignedIntBuffer) {
try {
colorModel = new UnsignedIntColorModel(32, type, c);
}
catch (IOException e) {
return null;
}
}
}
SampleModel model;
if (c > 2 && type == DataBuffer.TYPE_INT && buffer.getNumBanks() == 1 &&
!(buffer instanceof UnsignedIntBuffer))
{
int[] bitMasks = new int[c];
for (int i=0; i 2 && type == DataBuffer.TYPE_INT && buffer.getNumBanks() == 1
&& !(buffer instanceof UnsignedIntBuffer))
{
if (c == 3) {
b = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
}
else if (c == 4) {
b = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
if (b != null) b.setData(raster);
}
if (b == null) b = new BufferedImage(colorModel, raster, false, null);
return b;
}
// -- Data extraction --
/**
* Gets the image's pixel data as arrays of primitives, one per channel.
* The returned type will be either byte[][], short[][], int[][], float[][]
* or double[][], depending on the image's transfer type.
*/
public static Object getPixels(BufferedImage image) {
return getPixels(image, 0, 0, image.getWidth(), image.getHeight());
}
/**
*
* Gets the image's pixel data as arrays of primitives, one per channel.
* The returned type will be either byte[][], short[][], int[][], float[][]
* or double[][], depending on the image's transfer type.
*/
public static Object getPixels(BufferedImage image, int x, int y,
int w, int h)
{
WritableRaster raster = image.getRaster();
return getPixels(raster, x, y, w, h);
}
/**
* Gets the raster's pixel data as arrays of primitives, one per channel.
* The returned type will be either byte[][], short[][], int[][], float[][]
* or double[][], depending on the raster's transfer type.
*/
public static Object getPixels(WritableRaster raster) {
return getPixels(raster, 0, 0, raster.getWidth(), raster.getHeight());
}
/**
* Gets the raster's pixel data as arrays of primitives, one per channel.
* The returned type will be either byte[][], short[][], int[][], float[][]
* or double[][], depending on the raster's transfer type.
*/
public static Object getPixels(WritableRaster raster, int x, int y,
int w, int h)
{
int tt = raster.getTransferType();
if (tt == DataBuffer.TYPE_BYTE) return getBytes(raster, x, y, w, h);
else if (tt == DataBuffer.TYPE_USHORT || tt == DataBuffer.TYPE_SHORT) {
return getShorts(raster, x, y, w, h);
}
else if (tt == DataBuffer.TYPE_INT) return getInts(raster, x, y, w, h);
else if (tt == DataBuffer.TYPE_FLOAT) return getFloats(raster, x, y, w, h);
else if (tt == DataBuffer.TYPE_DOUBLE) {
return getDoubles(raster, x, y, w, h);
}
else return null;
}
/** Extracts pixel data as arrays of unsigned bytes, one per channel. */
public static byte[][] getBytes(BufferedImage image) {
WritableRaster r = image.getRaster();
return getBytes(r);
}
/** Extracts pixel data as arrays of unsigned bytes, one per channel. */
public static byte[][] getBytes(WritableRaster r) {
return getBytes(r, 0, 0, r.getWidth(), r.getHeight());
}
/** Extracts pixel data as arrays of unsigned bytes, one per channel. */
public static byte[][] getBytes(WritableRaster r, int x, int y, int w, int h)
{
if (canUseBankDataDirectly(r, DataBuffer.TYPE_BYTE, DataBufferByte.class) &&
x == 0 && y == 0 && w == r.getWidth() && h == r.getHeight())
{
return ((DataBufferByte) r.getDataBuffer()).getBankData();
}
int c = r.getNumBands();
byte[][] samples = new byte[c][w * h];
int[] buf = new int[w * h];
for (int i=0; i dataBufferClass)
{
int tt = r.getTransferType();
if (tt != transferType) return false;
DataBuffer buffer = r.getDataBuffer();
if (!dataBufferClass.isInstance(buffer)) return false;
SampleModel model = r.getSampleModel();
if (!(model instanceof ComponentSampleModel)) return false;
ComponentSampleModel csm = (ComponentSampleModel) model;
int pixelStride = csm.getPixelStride();
if (pixelStride != 1) return false;
int w = r.getWidth();
int scanlineStride = csm.getScanlineStride();
if (scanlineStride != w) return false;
int c = r.getNumBands();
int[] bandOffsets = csm.getBandOffsets();
if (bandOffsets.length != c) return false;
for (int i=0; i