I am trying to port some graphics code that is written using java.awt.* library to instead use the android.graphics.* library. However, I don't have much experience with graphics.
Here is the java.awt.* code (which works):
/**
* Converts the given <code>MyImageBitmap</code> to the specified image format and returns it as byte array.
*
* #param myImageBitmapthe given MyImageBitmap, not null
* #param format the given target image format ("png", "gif", "jpg"), not null
* #return the converted data in byte array format, not null
*/
private byte[] convert(MyImageBitmap myImageBitmap, String format) {
final int width = myImageBitmap.getWidth();
final int height = myImageBitmap.getHeight();
final int[] MASKS = {0x000000ff, 0x000000ff, 0x000000ff};
DataBuffer buffer = new DataBufferByte(myImageBitmap.getPixels(), myImageBitmap.getLength());
WritableRaster writableRaster = Raster.createPackedRaster(buffer, width, height, width, MASKS, null);
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
bufferedImage.setData(writableRaster);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ImageIO.write(bufferedImage, format, outputStream);
outputStream.close();
return outputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
This is the MyImageBitmap class:
/**
* A <code>MyImageBitmap</code> instance contains an image information in bitmap format.
*/
public class MyImageBitmap implements Serializable {
//...member variables
/**
* Creates an instance of <code>MyImageBitmap</code> with specified data.
*
* #param pixels the image pixes, not null
* #param width the image width, not null
* #param height the image height, not null
* #param ppi pixel per inch, not null
* #param depth the image depth, not null
* #param lossyFlag lossy flag, not null
*/
public MyImageBitmap(byte[] pixels, int width, int height, int ppi, int depth, int lossyFlag) {
this.pixels = pixels;
this.width = width;
this.height = height;
this.ppi = ppi;
this.depth = depth;
this.lossyFlag = lossyFlag;
this.length = pixels != null ? pixels.length : 0;
}
//...getters
}
This is what I have tried (with no success):
private byte[] convert(MyImageBitmap myImageBitmap, String format) {
int width = myImageBitmap.getWidth();
int height = myImageBitmap.getHeight();
byte[] imgRGB888 = myImageBitmap.getPixels();
Bitmap bmp2 = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int[] colors = new int[width * height];
int r,g,b;
for (int ci = 0; ci < colors.length; ci++)
{
r = (int)(0xFF & imgRGB888[3*ci]);
g = (int)(0xFF & imgRGB888[3*ci+1]);
b = (int)(0xFF & imgRGB888[3*ci+2]);
colors[ci] = Color.rgb(r, g, b);
}
bmp2.setPixels(colors, 0, width, 0, 0, width, height);
Bitmap.CompressFormat compressFormat;
if (format.equals("jpeg")){
compressFormat = android.graphics.Bitmap.CompressFormat.JPEG;
}else if (format.equals("png")){
compressFormat = android.graphics.Bitmap.CompressFormat.PNG;
}else {//must be gif...try to convert to png
compressFormat = android.graphics.Bitmap.CompressFormat.PNG;
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
bmp2.compress(compressFormat, 100, outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
When I run the above code (my attempt at porting over the awt code) I get an ArrayIndexOutOfBoundsException on this line r = (int)(0xFF & imgRGB888[3*ci]);.
I ended up figuring out what the issue was. My algorithm for converting from
byte array to int color array was wrong. Below is the correct implementation. The images are now being correctly displayed in the Android ImageView!
/**
* Converts the given <code>MyImageBitmap</code> to the specified image format and returns it as byte array.
*
* #param myImageBitmap the given bitmap, not null
* #param format the given target image format, not null
* #return the converted data in byte array format, not null
*/
private byte[] convert(MyImageBitmap myImageBitmap, String format) {
int width = myImageBitmap.getWidth();
int height = myImageBitmap.getHeight();
byte[] imgRGB888 = myImageBitmap.getPixels();
Bitmap bmp2 = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int[] colors = new int[width * height];
//We need to convert the image from a byte array to a
// int color array so we can create the Android Bitmap
int r,g,b;
for (int ci = 0; ci < colors.length; ci++) {
r = (int)(0x000000ff & imgRGB888[ci]);
g = (int)(0x000000ff & imgRGB888[ci]);
b = (int)(0x000000ff & imgRGB888[ci]);
colors[ci] = Color.rgb(r, g, b);
}
bmp2.setPixels(colors, 0, width, 0, 0, width, height);
Bitmap.CompressFormat compressFormat;
if (format.equals("jpeg")){
compressFormat = android.graphics.Bitmap.CompressFormat.JPEG;
}else{//must be png
compressFormat = android.graphics.Bitmap.CompressFormat.PNG;
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
bmp2.compress(compressFormat, 100, outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Related
I want to use the function below at Android but ImageIO.read function can not be use at Android.
DecodedImage decode(byte[] image) {
return Exceptions.sneak().get(() -> {
BufferedImage buffered = ImageIO.read(new ByteArrayInputStream(image));
if (buffered == null)
throw new IllegalArgumentException("Unsupported image format.");
int width = buffered.getWidth();
int height = buffered.getHeight();
int[] pixels = new int[width * height];
buffered.getRGB(0, 0, width, height, pixels, 0, width);
return new DecodedImage(width, height, pixels);
});
}
Is there any equivalent function to do this operation in Java for Android?
I have an org.eclipse.swt.graphics.Image, loaded from a PNG, and want to scale it in high quality (antialiasing, interpolation). But I do not want to lose transparency and get just a white background. (I need this Image to put it on an org.eclipse.swt.widgets.Label .)
Does anybody know how to do that?
Thank you!
Based on Mark's answer I found a better solution without the "hacky bit": first copy the alphaData from the origin then use GC to scale the image.
public static Image scaleImage(final Device device, final Image orig, final int scaledWidth, final int scaledHeight) {
final Rectangle origBounds = orig.getBounds();
if (origBounds.width == scaledWidth && origBounds.height == scaledHeight) {
return orig;
}
final ImageData origData = orig.getImageData();
final ImageData destData = new ImageData(scaledWidth, scaledHeight, origData.depth, origData.palette);
if (origData.alphaData != null) {
destData.alphaData = new byte[destData.width * destData.height];
for (int destRow = 0; destRow < destData.height; destRow++) {
for (int destCol = 0; destCol < destData.width; destCol++) {
final int origRow = destRow * origData.height / destData.height;
final int origCol = destCol * origData.width / destData.width;
final int o = origRow * origData.width + origCol;
final int d = destRow * destData.width + destCol;
destData.alphaData[d] = origData.alphaData[o];
}
}
}
final Image dest = new Image(device, destData);
final GC gc = new GC(dest);
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
gc.drawImage(orig, 0, 0, origBounds.width, origBounds.height, 0, 0, scaledWidth, scaledHeight);
gc.dispose();
return dest;
}
This way we don't have to make assumptions about the underlying ImageData.
Using a method described by Sean Bright here: https://stackoverflow.com/a/15685473/6245535, we can extract the alpha information from the image and use it to fill the ImageData.alphaData array which is responsible for the transparency:
public static Image resizeImage(Display display, Image image, int width, int height) {
Image scaled = new Image(display, width, height);
GC gc = new GC(scaled);
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
gc.drawImage(image, 0, 0, image.getBounds().width, image.getBounds().height, 0, 0, width, height);
gc.dispose();
ImageData canvasData = scaled.getImageData();
canvasData.alphaData = new byte[width * height];
// This is the hacky bit that is making assumptions about
// the underlying ImageData. In my case it is 32 bit data
// so every 4th byte in the data array is the alpha for that
// pixel...
for (int idx = 0; idx < (width * height); idx++) {
int coord = (idx * 4) + 3;
canvasData.alphaData[idx] = canvasData.data[coord];
}
// Now that we've set the alphaData, we can create our
// final image
Image finalImage = new Image(display, canvasData);
scaled.dispose();
return finalImage;
}
Note that this method assumes that you are working with 32 bit depth of color; it won't work otherwise.
I've got a byte array storing 16-bit pixel data from an already-deconstructed DICOM file. What I need to do now is convert/export that pixel data somehow into a TIFF file format. I'm using the imageio-tiff-3.3.2.jar plugin to handle the tiff conversion/header data. But now I need to pack that image data array into a BufferedImage of the original image dimensions so it can be exported to TIFF. But it seems that BufferedImage doesn't support 16-bit images. Is there a way around this problem, such as an external library? Is there another way I can pack that image data into a TIFF image of the original DICOM dimensions? Keep in mind, this process has to be completely lossless. I've looked around and tried out some things for the last few days, but so far nothing has worked for me.
Let me know if you have any questions or if there's anything I can do to clear up any confusion.
EDIT: Intended and Current image
Given your input data of a raw byte array, containing unsigned 16 bit image data, here's two ways to create a BufferedImage.
The first one will be slower, as it involves copying the byte array into a short array. It will also need twice the amount of memory. The upside is that it creates a standard TYPE_USHORT_GRAY BufferedImage, which may be faster to display and may be more compatible.
private static BufferedImage createCopyUsingByteBuffer(int w, int h, byte[] rawBytes) {
short[] rawShorts = new short[rawBytes.length / 2];
ByteBuffer.wrap(rawBytes)
// .order(ByteOrder.LITTLE_ENDIAN) // Depending on the data's endianness
.asShortBuffer()
.get(rawShorts);
DataBuffer dataBuffer = new DataBufferUShort(rawShorts, rawShorts.length);
int stride = 1;
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, w, h, w * stride, stride, new int[] {0}, null);
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}
A variant that is much faster (previous version takes 4-5x more time) to create, but results in a TYPE_CUSTOM image, that might be slower to display (it does seem to perform reasonable though, in my tests). It's much faster, and uses very little extra memory, as it does no copying/conversion of the input data at creation time.
Instead, it uses a custom sample model, that has DataBuffer.TYPE_USHORT as transfer type, but uses DataBufferByte as data buffer.
private static BufferedImage createNoCopy(int w, int h, byte[] rawBytes) {
DataBuffer dataBuffer = new DataBufferByte(rawBytes, rawBytes.length);
int stride = 2;
SampleModel sampleModel = new MyComponentSampleModel(w, h, stride);
WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null);
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}
private static class MyComponentSampleModel extends ComponentSampleModel {
public MyComponentSampleModel(int w, int h, int stride) {
super(DataBuffer.TYPE_USHORT, w, h, stride, w * stride, new int[] {0});
}
#Override
public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
throw new ArrayIndexOutOfBoundsException("Coordinate out of bounds!");
}
// Simplified, as we only support TYPE_USHORT
int numDataElems = getNumDataElements();
int pixelOffset = y * scanlineStride + x * pixelStride;
short[] sdata;
if (obj == null) {
sdata = new short[numDataElems];
}
else {
sdata = (short[]) obj;
}
for (int i = 0; i < numDataElems; i++) {
sdata[i] = (short) (data.getElem(0, pixelOffset) << 8 | data.getElem(0, pixelOffset + 1));
// If little endian, swap the element order, like this:
// sdata[i] = (short) (data.getElem(0, pixelOffset + 1) << 8 | data.getElem(0, pixelOffset));
}
return sdata;
}
}
If your image looks strange after this conversion, try flipping the endianness, as commented in the code.
And finally, some code to exercise the above:
public static void main(String[] args) {
int w = 1760;
int h = 2140;
byte[] rawBytes = new byte[w * h * 2]; // This will be your input array, 7532800 bytes
ShortBuffer buffer = ByteBuffer.wrap(rawBytes)
// .order(ByteOrder.LITTLE_ENDIAN) // Try swapping the byte order to see sharp edges
.asShortBuffer();
// Let's make a simple gradient, from black UL to white BR
int max = 65535; // Unsigned short max value
for (int y = 0; y < h; y++) {
double v = max * y / (double) h;
for (int x = 0; x < w; x++) {
buffer.put((short) Math.round((v + max * x / (double) w) / 2.0));
}
}
final BufferedImage image = createNoCopy(w, h, rawBytes);
// final BufferedImage image = createCopyUsingByteBuffer(w, h, rawBytes);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new JScrollPane(new JLabel(new ImageIcon(image))));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
Here's what the output should look like (scaled down to 1/10th):
The easiest thing to do is to create a BufferedImage of type TYPE_USHORT_GRAY, which is type to use for 16 bits encoding.
public BufferedImage Convert(short[] array, final int width, final int height)
{
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY) ;
short[] sb = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData() ;
System.arraycopy(array, 0, sb, 0, array.length) ;
return image ;
}
Then you can use Java.imageio to save your image as a TIFF or a PNG. I think that the Twelve Monkey Project allows a better TIFF support for imageio, but you have to check first.
[EDIT] In your case because you deal with huge DICOM images that cannot be stored into a regular BufferedImage, you have to create your own type using the Unsafe class to allocated the DataBuffer.
Create a new class DataBufferLongShort that will allocate the needed array/DataBuffer using the Unsafe class. Then you can use Long indexes instead of Integer
Create a new class DataBuffer that extends the classical DataBuffer in order to add a type TYPE_LONG_USHORT
Then you can create the ColorModel with the new DataBuffer.
In my Java Spring MVC Web Application, I have options to upload images of any size. Currently I have an API that reduces the size of any uploaded image that is above a specified width. I use the following code for that:
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.imageio.ImageIO;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedOp;
public class ImageRescale
{
private static RenderingHints hints;
static
{
hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
hints.put(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
/*
* this block is to silence the warning that we're not using JAI
* native acceleration but are using the pure java implementation.
*/
Properties p = new Properties(System.getProperties());
p.put("com.sun.media.jai.disableMediaLib", "true");
System.setProperties(p);
}
public static byte[] getScaledInstance(byte[] image, int maxWidth)
{
InputStream in = new ByteArrayInputStream(image);
BufferedImage img = null;
try
{
img = ImageIO.read(in);
double scale = (double) maxWidth / img.getWidth();
if ( scale > 1.0d )
{
return image;
}
else if (scale > 0.5d && scale < 1.0d)
{
return getByteArray(getScaledDownByGraphics(img, scale));
}
else if (scale <= 0.5d)
{
return getByteArray(getScaledDownByJAI(img, scale));
}
}
catch (IOException e)
{
e.printStackTrace();
}
return image;
}
public static byte[] getByteArray(BufferedImage img)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] imageInByte = null;
try
{
ImageIO.write( img, "jpg", baos );
baos.flush();
imageInByte = baos.toByteArray();
baos.close();
}
catch (IOException e)
{
e.printStackTrace();
}
return imageInByte;
}
/**
* See http://www.digitalsanctuary.com/tech-blog/java/how-to-resize-uploaded-images-using-java-better-way.html
* This instance seems to produce quality images ONLY when you are
* scaling down to something less than 50% of the original size.
* #param img
* #param scale
* #return the scaled image
*/
private static BufferedImage getScaledDownByJAI(BufferedImage img, double scale)
{
if(scale > 1.0d)
{
throw new RuntimeException("Can't scale according to " + scale + " : This method only scales down.");
}
PlanarImage originalImage = PlanarImage.wrapRenderedImage(img);
// now resize the image
ParameterBlock paramBlock = new ParameterBlock();
paramBlock.addSource(originalImage); // The source image
paramBlock.add(scale); // The xScale
paramBlock.add(scale); // The yScale
paramBlock.add(0.0); // The x translation
paramBlock.add(0.0); // The y translation
RenderedOp resizedImage = JAI.create("SubsampleAverage", paramBlock, hints);
return resizedImage.getAsBufferedImage();
}
/**
* This method produces high quality images when target scale is greater
* than 50% of the original.
* #param img
* #param scale
* #return the scaled image
*/
private static BufferedImage getScaledDownByGraphics(BufferedImage img, double scale)
{
final float scaleFactor = 0.8f;
BufferedImage ret = (BufferedImage)img;
int w = img.getWidth();
int h = img.getHeight();
int targetWidth = (int)(img.getWidth() * scale);
int targetHeight = (int)(img.getHeight() * scale);
int loopCount = 0;
int maxLoopCount = 20;
BufferedImage tmp;
do {
if (w > targetWidth) {
w *= scaleFactor;
if (w < targetWidth) {
w = targetWidth;
}
}
if (h > targetHeight) {
h *= scaleFactor;
if (h < targetHeight) {
h = targetHeight;
}
}
tmp = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = tmp.createGraphics();
g2.addRenderingHints(hints);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
if(++loopCount > maxLoopCount) {
throw new RuntimeException("Hit maximum loop count " + maxLoopCount);
}
} while (w != targetWidth || h != targetHeight);
return ret;
}
}
But what I want now is an API that compresses all uploaded images and bring down its resolution to around 72 dpi which I believe is the web standard for images. Is there any way I can achieve this.
Could you check if this would help you?
/**
*
* #param bi image
* #param mimeType type
* #param quality quality
* #return compressed image
* #throws IOException on error
*/
private static BufferedImage compressImage(BufferedImage bi, String mimeType
, float quality) throws IOException {
// converting buffered image to byte array
// do not compress GIFs
if (mimeType.equalsIgnoreCase("image/gif")) {
return bi;
}
ImageWriter writer = ImageIO.getImageWritersByMIMEType(mimeType).next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
if (!mimeType.equalsIgnoreCase("image/png") && !mimeType.equalsIgnoreCase("image/bmp")) {
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(quality);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream bos = new java.io.BufferedOutputStream(baos);
ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
writer.setOutput(ios);
IIOImage optimizedImage = new IIOImage(bi, null, null);
writer.write(null, optimizedImage, iwp);
writer.dispose();
baos.flush();
ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray());
return ImageIO.read(in);
}
I'm trying to export a PNG quicklook of an ALOS AVNIR-2 product using the BEAM java APIs. The following picture shows the RGB preview of the prduct, as it appears in the GUI of beam.
As you can see, the image is not upright, due to its geocoding. I've developed a very simple java program to export the quicklook of the product.
public static void main(String[] args) {
String[] rgbBandNames = new String[3];
rgbBandNames[0] = "radiance_3";
rgbBandNames[1] = "radiance_2";
rgbBandNames[2] = "radiance_1";
try {
//Product inputProduct = ProductIO.readProduct(args[0]);
Product inputProduct = ProductIO.readProduct("C:\\nfsdata\\VOL-ALAV2A152763430-O1B2R_U");
Band[] produtBands = inputProduct.getBands();
Band[] rgbBands = new Band[3];
int n = 0;
for (Band band : produtBands) {
if (band.getName().equals(rgbBandNames[0])) {
rgbBands[0] = band;
} else if (band.getName().equals(rgbBandNames[1])) {
rgbBands[1] = band;
} else if (band.getName().equals(rgbBandNames[2])) {
rgbBands[2] = band;
}
n = n + 1;
}
ImageInfo outImageInfo = ProductUtils.createImageInfo(rgbBands, true, ProgressMonitor.NULL);
BufferedImage outImage = ProductUtils.createRgbImage(rgbBands, outImageInfo, ProgressMonitor.NULL);
ImageIO.write(outImage, "PNG", new File(inputProduct.getName() + ".png"));
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
}
The program works, but every PNG image i get from it is an upright PNG image, like the following.
Now, I know that it is not possible to have geocoding information inside a PNG image. I need only to reproduce the same "rotation" of the image.
Any idea?
I managed to solve my problem. In other words, I managed to extract the quicklook from an ALOS AV2 O1B2R_U product, rotated according to the geocoding information of the product (see the image below).
The reason for this is that the ALOS AV2 O1B2R_U products have the geocoding rotation already applied to the raster. As a consequence, in order to successfulyl export a quicklook, the rotation must be retrieved from the native raster and applied to the output image.
For future reference, I'd like to recap and share my solution with the community. This is my main class:
import com.bc.ceres.core.ProgressMonitor;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.esa.beam.framework.dataio.ProductIO;
import org.esa.beam.framework.datamodel.Band;
import org.esa.beam.framework.datamodel.ImageInfo;
import org.esa.beam.framework.datamodel.MapGeoCoding;
import org.esa.beam.framework.datamodel.Product;
import org.esa.beam.util.ProductUtils;
public static void main(String[] args) throws IOException {
String inputProductPath = "path\to\input\product";
String outputProductPath = "path\to\output\image";
// Read the source product.
Product inputProduct = ProductIO.readProduct(inputProductPath);
// Extract the RGB bands.
String[] bandNames = new String[3];
Band[] bandData = new Band[3];
bandNames[0] = "radiance_3";
bandNames[1] = "radiance_2";
bandNames[2] = "radiance_1";
for (Band band : inputProduct.getBands()) {
for (int i = 0; i < bandNames.length; i++) {
if (band.getName().equalsIgnoreCase(bandNames[ i ])) {
bandData[ i ] = band;
}
}
}
// Generate quicklook image.
ImageInfo outImageInfo = ProductUtils.createImageInfo(bandData, true, ProgressMonitor.NULL);
BufferedImage outImage = ProductUtils.createRgbImage(bandData, outImageInfo, ProgressMonitor.NULL);
outImage = resize(outImage, WIDTH, 1200);
// Extract the orientation.
double orientation;
if (inputProduct.getGeoCoding() != null) {
orientation = -((MapGeoCoding) inputProduct.getGeoCoding()).getMapInfo().getOrientation();
} else {
orientation = 0.0;
}
outImage = rotate(outImage, orientation);
// Write image.
ImageIO.write(outImage, "PNG", new File(outputProductPath));
}
Once the rotation angle of the quicklook has been extracted from the source product (see the above code), it must be applied to the output image (BufferedImage). In the above code, two simple image manipulation functions are employed: resize(...) and rotate(...), see below for their definition.
/**
* Resizes the image {#code tgtImage} by setting one of its dimensions
* (width or height, specified via {#code tgtDimension}) to {#code tgtSize}
* and dynamically calculating the other one in order to preserve the aspect
* ratio.
*
* #param tgtImage The image to be resized.
* #param tgtDimension The selected dimension: {#code ImageUtil.WIDTH} or
* {#code ImageUtil.WIDTH}.
* #param tgtSize The new value for the selected dimension.
*
* #return The resized image.
*/
public static BufferedImage resize(BufferedImage tgtImage, short tgtDimension, int tgtSize) {
int newWidth = 0, newHeight = 0;
if (HEIGHT == tgtDimension) {
newHeight = tgtSize;
newWidth = (tgtImage.getWidth() * tgtSize) / tgtImage.getHeight();
} else {
newHeight = (tgtImage.getHeight() * tgtSize) / tgtImage.getWidth();
newWidth = tgtSize;
}
Image tmp = tgtImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
BufferedImage outImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGemoticon;
Graphics2D g2d = outImage.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return outImage;
}
/**
* Rotates the image {#code tgtImage} by {#code tgtAngle} degrees clockwise.
*
* #param tgtImage The image to be rotated.
* #param tgtAngle The rotation angle (expressed in degrees).
*
* #return The resized image.
*/
public static BufferedImage rotate(BufferedImage tgtImage, double tgtAngle) {
int w = tgtImage.getWidth();
int h = tgtImage.getHeight();
AffineTransform t = new AffineTransform();
t.setToRotation(Math.toRadians(tgtAngle), w / 2d, h / 2d);
Point[] points = {
new Point(0, 0),
new Point(w, 0),
new Point(w, h),
new Point(0, h)
};
// Transform to destination rectangle.
t.transform(points, 0, points, 0, 4);
// Get destination rectangle bounding box
Point min = new Point(points[0]);
Point max = new Point(points[0]);
for (int i = 1, n = points.length; i < n; i++) {
Point p = points[ i ];
double pX = p.getX(), pY = p.getY();
// Update min/max x
if (pX < min.getX()) {
min.setLocation(pX, min.getY());
}
if (pX > max.getX()) {
max.setLocation(pX, max.getY());
}
// Update min/max y
if (pY < min.getY()) {
min.setLocation(min.getX(), pY);
}
if (pY > max.getY()) {
max.setLocation(max.getX(), pY);
}
}
// Determine new width, height
w = (int) (max.getX() - min.getX());
h = (int) (max.getY() - min.getY());
// Determine required translation
double tx = min.getX();
double ty = min.getY();
// Append required translation
AffineTransform translation = new AffineTransform();
translation.translate(-tx, -ty);
t.preConcatenate(translation);
AffineTransformOp op = new AffineTransformOp(t, null);
BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGemoticon;
op.filter(tgtImage, outImage);
return outImage;
}