Related
I wrote a code which splits a gif-image into frames.
Then, they are scaled-down into smaller ones, the resolution is 128 * 128 pixels. Then they combine a full frame with a specific resolution.
The getFrames (String path) method gets the GIF file along the path from the jar and creates image frames.
The getResizedimages (…) method cuts the frame into smaller 128 by 128 images.
The getPixels (Bufferedlmage image) method gets an array of pixels from an image.
The problem is that during the execution of this code (via the test method), a very very large amount of memory is used even at the stage of execution of the getFrames (…) method or the getFramesRaw (…) method.
import com.sun.imageio.plugins.gif.GIFImageReader;
import com.sun.imageio.plugins.gif.GIFImageReaderSpi;
import org.apache.commons.io.FileUtils;
import org.bukkit.map.MapPalette;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class Gif {
static void test(String path, int height, int width) {
List<BufferedImage> images = getFrames(path);
for (int x = 0; x < width; x ++) for (int y = 0; y < height; y ++)
getResizedFrames(images, width, height, x, y).forEach(image -> getPixels(image));
}
#SuppressWarnings("deprecation")
public static byte[] getPixels(BufferedImage image) {
int pixelCount = image.getWidth() * image.getHeight();
int[] pixels = new int[pixelCount];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
byte[] colors = new byte[pixelCount];
for (int i = 0; i < pixelCount; i++)
colors[i] = MapPalette.matchColor(new Color(pixels[i], true));
return colors;
}
public static BufferedImage getScaledImage(BufferedImage image) {
BufferedImage newImg = new BufferedImage(128, 128, 3);
Graphics2D g2 = newImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, 128, 128, null);
g2.dispose();
return newImg;
}
public static BufferedImage cropImage(BufferedImage completeImg, int width, int height, int xCoord, int yCoord) {
int coordWidth = completeImg.getWidth() / width;
int coordHeight = completeImg.getHeight() / height;
BufferedImage croppedImg = new BufferedImage(completeImg.getWidth(), completeImg.getHeight(), 1);
Graphics2D g2 = (Graphics2D)croppedImg.getGraphics();
g2.drawImage(completeImg, 0, 0, completeImg.getWidth(), completeImg.getHeight(), null);
g2.dispose();
BufferedImage image = croppedImg.getSubimage(coordWidth * xCoord, coordHeight * yCoord, coordWidth, coordHeight);
return getScaledImage(image);
}
public static java.util.List<BufferedImage> getFramesRaw(String path) {
java.util.List<BufferedImage> frames = new ArrayList<>();
try {
File file = File.createTempFile("data", ".gif");
InputStream stream = Main.class.getResourceAsStream(path);
FileUtils.copyInputStreamToFile(stream, file);
stream.close();
ImageInputStream inputStream = ImageIO.createImageInputStream(file);
ImageReader ir = new GIFImageReader(new GIFImageReaderSpi());
ir.setInput(inputStream);
int number = ir.getNumImages(true);
for (int i = 0; i < number; i++) frames.add(ir.read(i));
inputStream.close();
System.gc();
} catch (Exception e) {
e.printStackTrace();
}
return frames;
}
public static java.util.List<BufferedImage> getFrames(String path) {
java.util.List<BufferedImage> copies = new ArrayList<>();
java.util.List<BufferedImage> frames = getFramesRaw(path);
copies.add(frames.remove(0));
int width = copies.get(0).getWidth(), height = copies.get(0).getHeight();
for (BufferedImage frame : frames) {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.drawImage(copies.get(copies.size()-1),0,0,null);
g.drawImage(frame,0,0,null);
copies.add(img);
}
return copies;
}
public static java.util.List<BufferedImage> getResizedFrames(java.util.List<BufferedImage> frames, int width, int height, int x, int y) {
List<BufferedImage> copies = new ArrayList<>();
for (BufferedImage image : frames)
copies.add(cropImage(image, width, height, width - 1 - x, height - 1 - y));
return copies;
}
}
I need your help.
The reason your program uses a lot of memory, is because your code operates at all the frames at each step (in many small loops). All the decoded image data for all the frames will be held in memory at the same time, resulting in unnecessary memory usage. While this may feel logical, it is for sure not efficient.
Instead, create one loop that does all the needed operations for a single frame, before moving on to the next.
Something like this:
public static void test(String path) throws IOException {
List<BufferedImage> thumbnails = new ArrayList<>();
readFrames(path, image -> thumbnails.add(getScaledImage(image)));
}
private static void readFrames(String path, Consumer<BufferedImage> forEachFrame) throws IOException {
try (ImageInputStream inputStream = ImageIO.createImageInputStream(GifSplitter.class.getResourceAsStream(path))) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream); // ImageIO detects format, no need to hardcode GIF plugin
if (!readers.hasNext()) {
return;
}
ImageReader ir = readers.next();
ir.setInput(inputStream);
// For GIF format (which does not contain frame count in header),
// it's more efficient to just read each frame until IOOBE occurs,
// instead of invoking getNumImages(true)
int i = 0;
while (i >= 0) {
try {
BufferedImage image = ir.read(i++);
// crop, scale, etc, for a SINGLE image
forEachFrame.accept(image);
}
catch (IndexOutOfBoundsException endOfGif) {
// No more frames
break;
}
}
ir.dispose();
}
}
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);
}
}
I made this code to resize images with two factors. It works, but the quality of image is very bad after it is resized! Can you help me?
This is the code
public class ImageTest {
private static final int factor1 = 3;
private static final int factor2 = 4;
public static void main(String [] args){
JFileChooser cs = new JFileChooser();
cs.setFileSelectionMode(cs.DIRECTORIES_ONLY);
int i = cs.showOpenDialog(null);
if(i==cs.APPROVE_OPTION){
File f = cs.getSelectedFile();
File[] ff = f.listFiles();
for(int j=0;j<ff.length;j++){
String end = ff[j].getName().substring(ff[j].getName().indexOf(".")+1);
System.out.println(end);
try{
BufferedImage originalImage = ImageIO.read(ff[j]);
int type = originalImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
BufferedImage resizeImageJpg = resizeImageWithHint(originalImage, type);
ImageIO.write(resizeImageJpg, end, new File("pr/"+ff[j].getName()));
}catch(IOException e){
e.printStackTrace();
}
}
}
}
private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type){
int IMG_WIDTH = (originalImage.getWidth()*factor1)/factor2;
int IMG_HEIGHT = (originalImage.getHeight()*factor1)/factor2;
BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
g.dispose();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
return resizedImage;
}
}
I saw on web that resizeImageWithHint is done within the scope so as not to lose quality.. but it does! why? can you help me with this?
The best article I have ever read on this topic is The Perils of Image.getScaledInstance() (web archive).
In short: You need to use several resizing steps in order to get a good image. Helper method from the article:
public BufferedImage getScaledInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality)
{
int type = (img.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage)img;
int w, h;
if (higherQuality) {
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
} else {
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
return ret;
}
The following code produced me highest quality resize with aspect ratio preserved.
Tried few things and read several entries presented here in other answers. Lost two days and in the end I got the best result with plain Java method (tried also ImageMagick and java-image-scaling libraries):
public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
if (width < 1) {
width = (int) (height * ratio + 0.4);
} else if (height < 1) {
height = (int) (width /ratio + 0.4);
}
Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedScaled.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.drawImage(scaled, 0, 0, width, height, null);
dest.createNewFile();
writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
return true;
}
/**
* Write a JPEG file setting the compression quality.
*
* #param image a BufferedImage to be saved
* #param destFile destination file (absolute or relative path)
* #param quality a float between 0 and 1, where 1 means uncompressed.
* #throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
throws IOException {
ImageWriter writer = null;
FileImageOutputStream output = null;
try {
writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
output = new FileImageOutputStream(new File(destFile));
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, null);
writer.write(null, iioImage, param);
} catch (IOException ex) {
throw ex;
} finally {
if (writer != null) {
writer.dispose();
}
if (output != null) {
output.close();
}
}
}
Know question is old... I've tried different solutions surfing then web, I got the best result using getScaledInstance(), supplying Image.SCALE_SMOOTH as argument. In fact the resulting image quality was really better.
My code below:
final int THUMB_SIDE = 140;
try {
BufferedImage masterImage = ImageIO.read(startingImage);
BufferedImage thumbImage = new BufferedImage(THUMB_SIDE, THUMB_SIDE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = thumbImage.createGraphics();
g2d.drawImage(masterImage.getScaledInstance(THUMB_SIDE, THUMB_SIDE, Image.SCALE_SMOOTH), 0, 0, THUMB_SIDE, THUMB_SIDE, null);
g2d.dispose();
String thumb_path = path.substring(0, path.indexOf(".png")) + "_thumb.png";
ImageIO.write(thumbImage, "png", new File(thumb_path));
} catch (IOException e) {
e.printStackTrace();
}
If your image source is a png then use like this:
Image imgSmall = imgBig.getScaledInstance(
targetWidth, targetHeight, Image.SCALE_SMOOTH);
If you want to resize jpeg or gif without loose too much quality, I made a library in 2010 for this: beautylib on github that uses internally this other library: java-image-scaling. You can see directly the source code to find something useful: https://github.com/felipelalli/beautylib/blob/master/src/br/eti/fml/beautylib/ResizeImage.java
None of the answers will help you to get real quality you desire. Include thumbailator.jar in your project (download it form here):
https://code.google.com/p/thumbnailator/
https://code.google.com/p/thumbnailator/wiki/Downloads?tm=2
Then upload the image first (as file, without Thumbnailator - it's use is to create thumbs, but you can create large images with it of course), and resize it to every dimensions you want (with Thumbnailator 800x600 for example). Quality will be very good. I was searching for this long time, this .jar helped me to achieve what i want.
Yes, I had the same problems and solved them, please read my question (answer is embedded in the question). I tried imgscalr and java-image-scaling libraries and found the second much better quality. Get close to the monitor to appreciate the difference between the thumbnail examples.
Despite my initial thoughts, resizing an image seems a very complicate thing, you don't want to do it yourself. For example I tell java-image-scaling to use ResampleFilters.getLanczos3Filter() to have better result.
It also addresses how to save a JPG with a quality higher than the standard 75, which produces a bad result especially for a thumbnail.
I also wrote a small class, called MyImage to help with common tasks, such as reading an image from a byte array, from a file, scaling by specifying only width or only height, scaling by specifying a bounding box, scaling by specifying width and height and adding a white band to make the image not distorted and writing to JPG file.
Answer: Remove the hints VALUE_INTERPOLATION_BILINEAR, and VALUE_RENDERING_QUALITY.
Example:
public static BufferedImage resizeImage(BufferedImage image, int width, int height) {
// Temporary image
BufferedImage tmp = image;
// Result image
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Graphics object
Graphics2D graphics = (Graphics2D)result.createGraphics();
// Add rendering hints
graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
// Draw tmp
graphics.drawImage(tmp, 0, 0, width, height, null);
// Dispose of graphics object
graphics.dispose();
// Return image result
return result;
}
Note: For some reason, the hints VALUE_INTERPOLATION_BILINEAR and VALUE_RENDERING_QUALITY blur the image being resized.
All the methods posted does'nt work for me i have to reduce QrCode size, but with above methods the quality is poor and scanner doesn't work , if i take original picture and resize it in paint the scanner is working.
The do while loop used in The Perils of Image.getScaledInstance() will run into an infinite loop, given those values,
w = 606; h = 505, targetWidth = 677, targetHeight = 505
Here is a simplied testing code, you can try it.
public class LoopTest {
public static void main(String[] args) {
new LoopTest(true, 606, 505, 677, 505);
}
public LoopTest(boolean higherQuality, int w, int h, int targetWidth, int targetHeight) {
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
} while (w != targetWidth || h != targetHeight); // TODO Auto-generated constructor stub
}
}
A quick work around: define an index for loop count. If the index is >=10, break out of loop.
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;
}
I made this code to resize images with two factors. It works, but the quality of image is very bad after it is resized! Can you help me?
This is the code
public class ImageTest {
private static final int factor1 = 3;
private static final int factor2 = 4;
public static void main(String [] args){
JFileChooser cs = new JFileChooser();
cs.setFileSelectionMode(cs.DIRECTORIES_ONLY);
int i = cs.showOpenDialog(null);
if(i==cs.APPROVE_OPTION){
File f = cs.getSelectedFile();
File[] ff = f.listFiles();
for(int j=0;j<ff.length;j++){
String end = ff[j].getName().substring(ff[j].getName().indexOf(".")+1);
System.out.println(end);
try{
BufferedImage originalImage = ImageIO.read(ff[j]);
int type = originalImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
BufferedImage resizeImageJpg = resizeImageWithHint(originalImage, type);
ImageIO.write(resizeImageJpg, end, new File("pr/"+ff[j].getName()));
}catch(IOException e){
e.printStackTrace();
}
}
}
}
private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type){
int IMG_WIDTH = (originalImage.getWidth()*factor1)/factor2;
int IMG_HEIGHT = (originalImage.getHeight()*factor1)/factor2;
BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
g.dispose();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
return resizedImage;
}
}
I saw on web that resizeImageWithHint is done within the scope so as not to lose quality.. but it does! why? can you help me with this?
The best article I have ever read on this topic is The Perils of Image.getScaledInstance() (web archive).
In short: You need to use several resizing steps in order to get a good image. Helper method from the article:
public BufferedImage getScaledInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality)
{
int type = (img.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage)img;
int w, h;
if (higherQuality) {
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
} else {
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
return ret;
}
The following code produced me highest quality resize with aspect ratio preserved.
Tried few things and read several entries presented here in other answers. Lost two days and in the end I got the best result with plain Java method (tried also ImageMagick and java-image-scaling libraries):
public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
if (width < 1) {
width = (int) (height * ratio + 0.4);
} else if (height < 1) {
height = (int) (width /ratio + 0.4);
}
Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedScaled.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.drawImage(scaled, 0, 0, width, height, null);
dest.createNewFile();
writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
return true;
}
/**
* Write a JPEG file setting the compression quality.
*
* #param image a BufferedImage to be saved
* #param destFile destination file (absolute or relative path)
* #param quality a float between 0 and 1, where 1 means uncompressed.
* #throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
throws IOException {
ImageWriter writer = null;
FileImageOutputStream output = null;
try {
writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
output = new FileImageOutputStream(new File(destFile));
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, null);
writer.write(null, iioImage, param);
} catch (IOException ex) {
throw ex;
} finally {
if (writer != null) {
writer.dispose();
}
if (output != null) {
output.close();
}
}
}
Know question is old... I've tried different solutions surfing then web, I got the best result using getScaledInstance(), supplying Image.SCALE_SMOOTH as argument. In fact the resulting image quality was really better.
My code below:
final int THUMB_SIDE = 140;
try {
BufferedImage masterImage = ImageIO.read(startingImage);
BufferedImage thumbImage = new BufferedImage(THUMB_SIDE, THUMB_SIDE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = thumbImage.createGraphics();
g2d.drawImage(masterImage.getScaledInstance(THUMB_SIDE, THUMB_SIDE, Image.SCALE_SMOOTH), 0, 0, THUMB_SIDE, THUMB_SIDE, null);
g2d.dispose();
String thumb_path = path.substring(0, path.indexOf(".png")) + "_thumb.png";
ImageIO.write(thumbImage, "png", new File(thumb_path));
} catch (IOException e) {
e.printStackTrace();
}
If your image source is a png then use like this:
Image imgSmall = imgBig.getScaledInstance(
targetWidth, targetHeight, Image.SCALE_SMOOTH);
If you want to resize jpeg or gif without loose too much quality, I made a library in 2010 for this: beautylib on github that uses internally this other library: java-image-scaling. You can see directly the source code to find something useful: https://github.com/felipelalli/beautylib/blob/master/src/br/eti/fml/beautylib/ResizeImage.java
None of the answers will help you to get real quality you desire. Include thumbailator.jar in your project (download it form here):
https://code.google.com/p/thumbnailator/
https://code.google.com/p/thumbnailator/wiki/Downloads?tm=2
Then upload the image first (as file, without Thumbnailator - it's use is to create thumbs, but you can create large images with it of course), and resize it to every dimensions you want (with Thumbnailator 800x600 for example). Quality will be very good. I was searching for this long time, this .jar helped me to achieve what i want.
Yes, I had the same problems and solved them, please read my question (answer is embedded in the question). I tried imgscalr and java-image-scaling libraries and found the second much better quality. Get close to the monitor to appreciate the difference between the thumbnail examples.
Despite my initial thoughts, resizing an image seems a very complicate thing, you don't want to do it yourself. For example I tell java-image-scaling to use ResampleFilters.getLanczos3Filter() to have better result.
It also addresses how to save a JPG with a quality higher than the standard 75, which produces a bad result especially for a thumbnail.
I also wrote a small class, called MyImage to help with common tasks, such as reading an image from a byte array, from a file, scaling by specifying only width or only height, scaling by specifying a bounding box, scaling by specifying width and height and adding a white band to make the image not distorted and writing to JPG file.
Answer: Remove the hints VALUE_INTERPOLATION_BILINEAR, and VALUE_RENDERING_QUALITY.
Example:
public static BufferedImage resizeImage(BufferedImage image, int width, int height) {
// Temporary image
BufferedImage tmp = image;
// Result image
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Graphics object
Graphics2D graphics = (Graphics2D)result.createGraphics();
// Add rendering hints
graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
// Draw tmp
graphics.drawImage(tmp, 0, 0, width, height, null);
// Dispose of graphics object
graphics.dispose();
// Return image result
return result;
}
Note: For some reason, the hints VALUE_INTERPOLATION_BILINEAR and VALUE_RENDERING_QUALITY blur the image being resized.
All the methods posted does'nt work for me i have to reduce QrCode size, but with above methods the quality is poor and scanner doesn't work , if i take original picture and resize it in paint the scanner is working.
The do while loop used in The Perils of Image.getScaledInstance() will run into an infinite loop, given those values,
w = 606; h = 505, targetWidth = 677, targetHeight = 505
Here is a simplied testing code, you can try it.
public class LoopTest {
public static void main(String[] args) {
new LoopTest(true, 606, 505, 677, 505);
}
public LoopTest(boolean higherQuality, int w, int h, int targetWidth, int targetHeight) {
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
} while (w != targetWidth || h != targetHeight); // TODO Auto-generated constructor stub
}
}
A quick work around: define an index for loop count. If the index is >=10, break out of loop.