/*
* Copyright (c) 2012 Diamond Light Source Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package uk.ac.diamond.scisoft.analysis.io;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferUShort;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.util.HashMap;
import java.util.Map;
import javax.media.jai.DataBufferFloat;
import javax.media.jai.PlanarImage;
import javax.media.jai.RasterFactory;
import javax.media.jai.TiledImage;
import org.eclipse.january.dataset.Dataset;
import org.eclipse.january.dataset.DatasetFactory;
import org.eclipse.january.dataset.DatasetUtils;
import org.eclipse.january.dataset.FloatDataset;
import org.eclipse.january.dataset.IndexIterator;
import org.eclipse.january.dataset.IntegerDataset;
import org.eclipse.january.dataset.RGBDataset;
import org.eclipse.january.metadata.IMetadata;
import org.eclipse.january.metadata.Metadata;
/**
* Helper methods to convert to/from AWT images and datasets
*/
public class AWTImageUtils {
private AWTImageUtils() {
}
/**
* Create datasets from a Raster
* @param r raster
* @param data array to output datasets
* @param dtype dataset type
*/
static public void createDatasets(Raster r, Dataset[] data, final int dtype) {
final int bands = data.length;
final int height = r.getHeight();
final int width = r.getWidth();
Dataset tmp;
for (int i = 0; i < bands; i++) {
if (dtype == Dataset.FLOAT32) {
tmp = DatasetFactory.createFromObject(r.getSamples(0, 0, width, height, i, (float[]) null), height, width);
} else if (dtype == Dataset.FLOAT64) {
tmp = DatasetFactory.createFromObject(r.getSamples(0, 0, width, height, i, (double[]) null), height, width);
} else if (dtype == Dataset.INT32) {
tmp = DatasetFactory.createFromObject(r.getSamples(0, 0, width, height, i, (int[]) null), height, width);
} else {
tmp = DatasetFactory.createFromObject(dtype, r.getSamples(0, 0, width, height, i, (int[]) null), height, width);
}
data[i] = tmp;
}
}
/**
* Get datasets from an image
* @param image
* @param keepBitWidth if true, then use signed primitives of same bit width for possibly unsigned data
* @return array of datasets
*/
static public Dataset[] makeDatasets(final BufferedImage image, boolean keepBitWidth) {
// make raster from buffered image
final Raster ras = image.getData();
final SampleModel sm = ras.getSampleModel();
int[] dtype = getDTypeFromImage(sm, keepBitWidth);
final int bands = ras.getNumBands();
Dataset[] data = new Dataset[bands];
createDatasets(ras, data, dtype[0]);
if (dtype[1] == 1) {
for (int i = 0; i < bands; i++) {
tagIntForShortDataset(data[i]);
}
}
return data;
}
private static void tagIntForShortDataset(Dataset ret) {
final Map<String,String> map = new HashMap<String, String>(1);
map.put("unsigned.short.data", "true");
IMetadata metadata = new Metadata();
metadata.initialize(map);
ret.setMetadata(metadata);
}
static public int[] getDTypeFromImage(final SampleModel sm, boolean keepBitWidth) {
int dbtype = sm.getDataType();
final int bits = sm.getSampleSize(0);
if (dbtype == DataBuffer.TYPE_INT) {
if (bits <= 8) {
dbtype = DataBuffer.TYPE_BYTE;
} else if (bits <= 16) {
dbtype = DataBuffer.TYPE_SHORT;
}
}
if (dbtype == DataBuffer.TYPE_USHORT) {
if (bits < 8) {
dbtype = DataBuffer.TYPE_BYTE;
} else if (bits < 16) {
dbtype = DataBuffer.TYPE_SHORT;
}
}
if (dbtype == DataBuffer.TYPE_SHORT) {
if (bits <= 8) {
dbtype = DataBuffer.TYPE_BYTE;
}
}
int dtype = -1;
switch (dbtype) {
case DataBuffer.TYPE_BYTE:
dtype = keepBitWidth ? Dataset.INT8 : Dataset.INT16;
break;
case DataBuffer.TYPE_SHORT:
dtype = Dataset.INT16;
break;
case DataBuffer.TYPE_USHORT:
dtype = keepBitWidth ? Dataset.INT16 : Dataset.INT32;
break;
case DataBuffer.TYPE_INT:
dtype = Dataset.INT32;
break;
case DataBuffer.TYPE_DOUBLE:
dtype = Dataset.FLOAT64;
break;
case DataBuffer.TYPE_FLOAT:
dtype = Dataset.FLOAT32;
break;
}
return new int[] {dtype, dbtype == DataBuffer.TYPE_USHORT && !keepBitWidth ? 1 : 0};
}
/**
* Get image from a dataset
* @param data
* @param bits number of bits (<=16 for non-RGB datasets)
* @return buffered image
*/
static public BufferedImage makeBufferedImage(final Dataset data, final int bits) {
final int[] shape = data.getShape();
if (shape.length > 2) {
throw new IllegalArgumentException("Rank of data must be less than or equal to two");
}
final int height = shape[0];
final int width = shape.length == 1 ? 1 : shape[1]; // allow 1D datasets to be saved
final int size = data.getSize();
BufferedImage image = null;
if (data instanceof RGBDataset) {
RGBDataset rgbds = (RGBDataset) data;
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
short maxv = rgbds.max().shortValue();
final IndexIterator iter = rgbds.getIterator(true);
final int[] pos = iter.getPos();
final short[] rgbdata = rgbds.getData();
if (maxv < 256) { // 888
while (iter.hasNext()) {
final int n = iter.index;
final int rgb = ((rgbdata[n] & 0xff) << 16) | ((rgbdata[n + 1] & 0xff) << 8) | (rgbdata[n + 2] & 0xff);
image.setRGB(pos[1], pos[0], rgb);
}
} else {
int shift = 0;
while (maxv >= 256) {
shift++;
maxv >>= 2;
}
while (iter.hasNext()) {
final int n = iter.index;
final int rgb = (((rgbdata[n] >> shift) & 0xff) << 16) | (((rgbdata[n + 1] >> shift) & 0xff) << 8) | ((rgbdata[n + 2] >> shift) & 0xff);
image.setRGB(pos[1], pos[0], rgb);
}
}
} else {
DataBuffer buffer = null;
SampleModel sampleModel = null;
// reconcile data with output format
// populate data buffer using sample model
IntegerDataset tmp = (IntegerDataset) DatasetUtils.cast(data, Dataset.INT32);
if (bits <= 8) {
buffer = new DataBufferByte(size);
sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width,
new int[] { 0 });
image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
sampleModel.setPixels(0, 0, width, height, tmp.getData(), buffer);
} else if (bits <= 16) {
buffer = new DataBufferUShort(size);
sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_USHORT, width, height, 1,
width, new int[] { 0 });
image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
sampleModel.setPixels(0, 0, width, height, tmp.getData(), buffer);
} else {
throw new IllegalArgumentException("Number of bits must be less than or equal to 16");
}
WritableRaster wRas = Raster.createWritableRaster(sampleModel, buffer, null);
image.setData(wRas);
}
return image;
}
/**
* Get image from a dataset
* @param data
* @param bits number of bits (> 32 for float image)
* @return tiled image
*/
static public TiledImage makeTiledImage(final Dataset data, final int bits) {
final int[] shape = data.getShape();
int height = shape[0];
int width = shape.length == 1 ? 1 : shape[1]; // allow 1D datasets to be saved
final int size = data.getSize();
SampleModel sampleModel;
DataBuffer buffer;
if (data instanceof RGBDataset) {
RGBDataset rgbds = (RGBDataset) data;
buffer = new DataBufferInt(size);
sampleModel = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, width, height, new int[] { 0xff0000, 0x00ff00, 0x0000ff} );
short maxv = rgbds.max().shortValue();
final IndexIterator iter = rgbds.getIterator(true);
final int[] pos = iter.getPos();
final short[] rgbdata = rgbds.getData();
if (maxv < 256) { // 888
while (iter.hasNext()) {
final int n = iter.index;
final int rgb = ((rgbdata[n] & 0xff) << 16) | ((rgbdata[n + 1] & 0xff) << 8) | (rgbdata[n + 2] & 0xff);
sampleModel.setSample(pos[1], pos[0], 0, rgb, buffer);
}
} else {
int shift = 0;
while (maxv >= 256) {
shift++;
maxv >>= 2;
}
while (iter.hasNext()) {
final int n = iter.index;
final int rgb = (((rgbdata[n] >> shift) & 0xff) << 16) | (((rgbdata[n + 1] >> shift) & 0xff) << 8) | ((rgbdata[n + 2] >> shift) & 0xff);
sampleModel.setSample(pos[1], pos[0], 0, rgb, buffer);
}
}
} else {
// reconcile data with output format
// populate data buffer using sample model
if (bits <= 32) {
buffer = new DataBufferInt(size);
sampleModel = RasterFactory.createBandedSampleModel(DataBuffer.TYPE_INT,
width, height, 1);
IntegerDataset tmp = (IntegerDataset) data.cast(Dataset.INT32);
sampleModel.setPixels(0, 0, width, height, tmp.getData(), buffer);
} else { // Only TIFF supports floats (so far)
buffer = new DataBufferFloat(size);
sampleModel = RasterFactory.createBandedSampleModel(DataBuffer.TYPE_FLOAT,
width, height, 1);
FloatDataset ftmp = (FloatDataset) data.cast(Dataset.FLOAT32);
sampleModel.setPixels(0, 0, width, height, ftmp.getData(), buffer);
}
}
WritableRaster wRas = Raster.createWritableRaster(sampleModel, buffer, null);
ColorModel cm = PlanarImage.createColorModel(sampleModel);
TiledImage timage = new TiledImage(0, 0, width, height, 0, 0, sampleModel, cm);
timage.setData(wRas);
return timage;
}
}