I have 10,000 photos that need to be resized so I have a Java program to do that. Unfortunately, the quality of the image is poorly lost and I don't have access to the uncompressed images.
import java.awt.Graphics;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* This class will resize all the images in a given folder
* #author
*
*/
public class JavaImageResizer {
public static void main(String[] args) throws IOException {
File folder = new File("/Users/me/Desktop/images/");
File[] listOfFiles = folder.listFiles();
System.out.println("Total No of Files:"+listOfFiles.length);
BufferedImage img = null;
BufferedImage tempPNG = null;
BufferedImage tempJPG = null;
File newFilePNG = null;
File newFileJPG = null;
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
System.out.println("File " + listOfFiles[i].getName());
img = ImageIO.read(new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()));
tempJPG = resizeImage(img, img.getWidth(), img.getHeight());
newFileJPG = new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()+"_New");
ImageIO.write(tempJPG, "jpg", newFileJPG);
}
}
System.out.println("DONE");
}
/**
* This function resize the image file and returns the BufferedImage object that can be saved to file system.
*/
public static BufferedImage resizeImage(final Image image, int width, int height) {
int targetw = 0;
int targeth = 75;
if (width > height)targetw = 112;
else targetw = 50;
do {
if (width > targetw) {
width /= 2;
if (width < targetw) width = targetw;
}
if (height > targeth) {
height /= 2;
if (height < targeth) height = targeth;
}
} while (width != targetw || height != targeth);
final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setComposite(AlphaComposite.Src);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.drawImage(image, 0, 0, width, height, null);
graphics2D.dispose();
return bufferedImage;
}
An image I am working with is this:
This is the manual resizing I've done in Microsoft Paint:
and this is the output from my program [bilinear]:
UPDATE: No significant difference using BICUBIC
and this is the output from my program [bicubic]:
is there anyway to increase the quality of the program output so I don't have to manually resize all photos?
Thank you in advance!
Unfortunately, there is no recommended out-of-the-box scaling in Java that provides visually good results. Among others, here are the methods I recommend for scaling:
Lanczos3 Resampling (usually visually better, but slower)
Progressive Down Scaling (usually visually fine, can be quite fast)
One-Step scaling for up scaling (with Graphics2d bicubic fast and good results, usually not as good as Lanczos3)
Examples for every method can be found in this answer.
Visual Comparison
Here is your image scaled to 96x140 with different methods/libs. Click on the image to get the full size:
Morten Nobel's lib Lanczos3
Thumbnailator Bilinear Progressive Scaling
Imgscalr ULTRA_QUALTY (1/7 step Bicubic Progressive Scaling)
Imgscalr QUALTY (1/2 step Bicubic Progressive Scaling)
Morten Nobel's lib Bilinear Progressive Scaling
Graphics2d Bicubic interpolation
Graphics2d Nearest Neighbor interpolation
Photoshop CS5 bicubic as reference
Unfortunately a single image is not enough to judge a scaling algorithm, you should test icons with sharp edges, photos with text, etc.
Lanczos Resampling
Is said to be good for up- and especially downscaling. Unfortunately there is no native implementation in current JDK so you either implement it yourself and use a lib like Morten Nobel's lib. A simple example using said lib:
ResampleOp resizeOp = new ResampleOp(dWidth, dHeight);
resizeOp.setFilter(ResampleFilters.getLanczos3Filter());
BufferedImage scaledImage = resizeOp.filter(imageToScale, null);
The lib is published on maven-central which is not mentioned unfortunately. The downside is that it usually is very slow without any highly optimized or hardware accelerated implementations known to me. Nobel's implementation is about 8 times slower than a 1/2 step progressive scaling algorithm with Graphics2d. Read more about this lib on his blog.
Progressive Scaling
Mentioned in Chris Campbell's blog about scaling in Java, progressive scaling is basically incrementally scaling an image in smaller steps until the final dimensions are reached. Campbell describes it as halving width/height until you reach target. This produces good results and can be used with Graphics2D which can be hardware accelerated, therefore usually having very good performance with acceptable results in most cases. The major downside of this is if downscaled less than half using Graphics2D provides the same mediocre results since it is only scaled once.
Here is a simple example on how it works:
The following libs incorporate forms of progressive scaling based on Graphics2d:
Thumbnailator v0.4.8
Uses the progressive bilinear algorithm if the target is at least half of every dimension, otherwise it uses simple Graphics2d bilinear scaling and bicubic for upscaling.
Resizer resizer = DefaultResizerFactory.getInstance().getResizer(
new Dimension(imageToScale.getWidth(), imageToScale.getHeight()),
new Dimension(dWidth, dHeight))
BufferedImage scaledImage = new FixedSizeThumbnailMaker(
dWidth, dHeight, false, true).resizer(resizer).make(imageToScale);
It is as fast or slightly faster than one-step scaling with Graphics2d scoring an average of 6.9 sec in my benchmark.
Imgscalr v4.2
Uses progressive bicubic scaling. In the QUALITY setting it uses Campbell style algorithm with halving the dimensions every step while the ULTRA_QUALITY has finer steps, reducing the size every increment by 1/7 which generates generally softer images but minimizes the instances where only 1 iteration is used.
BufferedImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray);
The major downside is performance. ULTRA_QUALITY is considerably slower than the other libs. Even QUALITY a bit slower than Thumbnailator's implementation. My simple benchmark resulted in 26.2 sec and 11.1 sec average respectively.
Morten Nobel's lib v0.8.6
Has also implementations for progressive scaling for all basic Graphics2d (bilinear, bicubic & nearest neighbor)
BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);
A word on JDK Scaling Methods
Current jdk way to scale an image would be something like this
scaledImage = new BufferedImage(dWidth, dHeight, imageType);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
but most are very disappointed with the result of downscaling no matter what interpolation or other RenderHints are used. On the other hand upscaling seems to produce acceptable images (best would be bicubic). In previous JDK version (we talking 90s v1.1) Image.getScaledInstance() was introduced which provided good visual results with parameter SCALE_AREA_AVERAGING but you are discouraged to use it - read the full explanation here.
Thumbnailator is a library that was written to create high-quality thumbnails in a simple manner, and doing a batch conversion of existing images is one of its use cases.
Performing batch resizing
For example, to adapt your example using Thumbnailator, you should be able to achieve similar results with the following code:
File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
.size(112, 75)
.outputFormat("jpg")
.toFiles(Rename.PREFIX_DOT_THUMBNAIL);
This will go ahead and takes all files in your images directory and proceed to process them one by one, try to resize them to fit in the dimensions of 112 x 75, and it will attempt to preserve the aspect ratio of the original image to prevent "warping" of the image.
Thumbnailator will go ahead and read all files, regardless of image types (as long as the Java Image IO supports the format, Thumbnailator will process it), perform the resizing operation and output the thumbnails as JPEG files, while tacking on a thumbnail. to the beginning of the file name.
The following is an illustration of how the file name of the original will be used in the file name of the thumbnail if the above code is executed.
images/fireworks.jpg -> images/thumbnail.fireworks.jpg
images/illustration.png -> images/thumbnail.illustration.png
images/mountains.jpg -> images/thumbnail.mountains.jpg
Generating high-quality thumbnails
In terms of image quality, as mentioned in Marco13's answer, the technique described by Chris Campbell in his The Perils of Image.getScaledInstance() is implemented in Thumbnailator, resulting in high-quality thumbnails without requiring any complicated processing.
The following is the thumbnail generated when resizing the fireworks image shown in the original question using Thumbnailator:
The above image was created with the following code:
BufferedImage thumbnail =
Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg"))
.height(75)
.asBufferedImage();
ImageIO.write(thumbnail, "png", new File("24745147.png"));
The code shows that it can also accept URLs as input, and that Thumbnailator is also capable of creating BufferedImages as well.
Disclaimer: I am the maintainer of the Thumbnailator library.
Given your input image, the method from the answer in the first link in the comments (kudos to Chris Campbell) produces one of the following thumbnails:
(The other one is the thumbnail that you created with MS Paint. It's hard to call one of them "better" than the other...)
EDIT: Just to point this out as well: The main problem with your original code was that you did not really scale the image in multiple steps. You just used a strange loop to "compute" the target size. The key point is that you actually perform the scaling in multiple steps.
Just for completeness, the MVCE
(Edit: I mentioned Chris Campbell and referred to the source via the comments, but to make this more clear here: The following is based on the article The Perils of Image.getScaledInstance() )
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
public class ResizeQuality
{
public static void main(String[] args) throws IOException
{
BufferedImage image = ImageIO.read(new File("X0aPT.jpg"));
BufferedImage scaled = getScaledInstance(
image, 51, 75, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
writeJPG(scaled, new FileOutputStream("X0aPT_tn.jpg"), 0.85f);
}
public static 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;
}
public static void writeJPG(
BufferedImage bufferedImage,
OutputStream outputStream,
float quality) throws IOException
{
Iterator<ImageWriter> iterator =
ImageIO.getImageWritersByFormatName("jpg");
ImageWriter imageWriter = iterator.next();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
imageWriteParam.setCompressionQuality(quality);
ImageOutputStream imageOutputStream =
new MemoryCacheImageOutputStream(outputStream);
imageWriter.setOutput(imageOutputStream);
IIOImage iioimage = new IIOImage(bufferedImage, null, null);
imageWriter.write(null, iioimage, imageWriteParam);
imageOutputStream.flush();
}
}
After days of research i would prefer javaxt.
use Thejavaxt.io.Image class has a constructor like:
public Image(java.awt.image.BufferedImage bufferedImage)
so you can do (another example):
javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);
Here's the output:
We should not forget a TwelveMonkeys Library
It contains a really impressive filter collection.
Usage example:
BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS);
BufferedImage output = resampler.filter(input, null);
Below are my own implementation of Progressive Scaling, without using any external library. Hope this help.
private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
if (before != null) {
Integer w = before.getWidth();
Integer h = before.getHeight();
Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
//Multi Step Rescale operation
//This technique is describen in Chris Campbell’s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
while (ratio < 0.5) {
BufferedImage tmp = scale(before, 0.5);
before = tmp;
w = before.getWidth();
h = before.getHeight();
ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
}
BufferedImage after = scale(before, ratio);
return after;
}
return null;
}
private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
return scaledImage;
}
The result seems to be better (than the result of your program), if you apply Gaussian blur before resizing:
This is the result I get, with sigma * (scale factor) = 0.3:
With ImageJ the code to do this is quite short:
import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;
public class Resizer {
public static void main(String[] args) {
processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3);
}
public static void processPicture(String inputFile, String outputFilePath, double scaleFactor, int interpolationMethod, double sigmaFactor) {
Opener opener = new Opener();
ImageProcessor ip = opener.openImage(inputFile).getProcessor();
ip.blurGaussian(sigmaFactor / scaleFactor);
ip.setInterpolationMethod(interpolationMethod);
ImageProcessor outputProcessor = ip.resize((int)(ip.getWidth() * scaleFactor), (int)(ip.getHeight()*scaleFactor));
IJ.saveAs(new ImagePlus("", outputProcessor), outputFilePath.substring(outputFilePath.lastIndexOf('.')+1), outputFilePath);
}
}
BTW: You only need ij-1.49d.jar (or equivalent for other version); there's no need to install ImageJ.
Related
I'm trying to code a class to seam carve images in x and y direction. The x direction is working, and to reduce the y direction I thought about simply rotating the image 90° and run the same code over the already rescaled image (in x direction only) and after that, rotate it back to its initial state.
I found something with AffineTransform and tried it. It actually produced a rotated image, but messed up the colors and I don't know why.
This is all the code:
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.io.File;
import java.io.IOException;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
public class example {
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException {
// TODO code application logic here
BufferedImage imgIn = ImageIO.read(new File("landscape.jpg"));
BufferedImage imgIn2 = imgIn;
AffineTransform tx = new AffineTransform();
tx.rotate(Math.PI/2, imgIn2.getWidth() / 2, imgIn2.getHeight() / 2);//(radian,arbit_X,arbit_Y)
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
BufferedImage last = op.filter(imgIn2, null);//(sourse,destination)
ImageIO.write(last, "JPEG", new File("distortedColors.jpg"));
}
}
Just alter the filename in
BufferedImage imgIn = ImageIO.read(new File("landscape.jpg")); and try it.
When executed, you get 4 images: a heatmap, an image with seams in it and a rescaled image. The last image is a test to see if the rotation worked and it should show a rotated image but with distorted colors...
Help would be greatly appreciated!
EDIT:
The problem is with the AffineTransformOp You need :
AffineTransformOp.TYPE_NEAREST_NEIGHBOR
instead of the BILINEAR you have now.
Second paragraph from documentation hints towards this.
This class uses an affine transform to perform a linear mapping from
2D coordinates in the source image or Raster to 2D coordinates in the
destination image or Raster. The type of interpolation that is used is
specified through a constructor, either by a RenderingHints object or
by one of the integer interpolation types defined in this class. If a
RenderingHints object is specified in the constructor, the
interpolation hint and the rendering quality hint are used to set the
interpolation type for this operation.
The color rendering hint and
the dithering hint can be used when color conversion is required. Note
that the following constraints have to be met: The source and
destination must be different. For Raster objects, the number of bands
in the source must be equal to the number of bands in the destination.
So this works
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
It seems like there's a color conversion happening due to passing null to op.filter(imgIn2, null);.
If you change it like that it should work:
BufferedImage last = new BufferedImage( imgIn2.getWidth(), imgIn2.getHeight(), imgIn2.getType() );
op.filter(imgIn2, last );
Building on what bhavya said...
Keep it simple and you should use the dimensions expected from the operation:
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
BufferedImage destinationImage = op.filter(bImage, op.createCompatibleDestImage(bImage, null));
i have a java.util.List filled with java.io.File and i wan't to list them in a gui. To display them i wan't to show their name which is accessable by the method getName() of java.io.File. And i want to display their icon/image with which they are displayed for example on the desktop. To build this i am using the newest javaversion (Java8/1.8). So far i found one way to do get the image of any file which looks like this:
Icon icon = FileSystemView.getFileSystemView().getSystemIcon(pFile);
The problem about that is, that the returned icon is in a very small resolution(16x16) and i d like to display it in a bigger size. 80x80 would be perfect but doesn't have to be exactly it. 64x64 or smth like that would be fine, too. So i managed to resize the icon and stretch it but streatching 16x16 to 80x80 is not cool as you can imagine. There are to less pixels to get a good result.
I also found this tutorial but the first method shown in this tutorial doesn't work with Java8: http://www.rgagnon.com/javadetails/java-0439.html The method i am using right now is copied from there.
So is there any way to get a bigger sized icon like the one which is shown on the desktop of a specific file?
Thanks
Baschdi
private BufferedImage getBufferedImage(final File pFile)
throws FileNotFoundException {
Image icon = ShellFolder.getShellFolder(pFile).getIcon(true);
BufferedImage im = new BufferedImage(icon.getWidth(null),
icon.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = im.createGraphics();
g.drawImage(icon, 0, 0, null);
g.dispose();
int width = im.getWidth();
int height = im.getHeight();
System.out.println(width);
System.out.println(height);
final int maxHeigh = 79;
double scaleValue = 0;
if (height > width)
scaleValue = maxHeigh / height;
else
scaleValue = maxHeigh / width;
final int scaledWidth = (int) (im.getWidth() * scaleValue);
final int scaledHeigh = (int) (im.getHeight() * scaleValue);
BufferedImage resized = new BufferedImage(scaledWidth, scaledHeigh,
im.getType());
g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(im, 0, 0, scaledWidth, scaledHeigh, 0, 0, im.getWidth(),
im.getHeight(), null);
g.dispose();
return resized;
}
I imported the jdk7 and used the older function with ShellFolder. Works fine, even when running on java8. Thanks for the help :)
I have created a library called JIconExtractReloaded which can extract all icons sizes not only 32x32. Here is the link https://github.com/MrMarnic/JIconExtractReloaded.
Just write:
BufferedImage image = JIconExtract.getIconForFile(128,128,"C:\\Windows\\explorer.exe");
And you have the icon.
I am trying to use JAI to perform a rotate task on an image. I can get this working no problem. However, there is severe loss of midtones in the image. The image can be rotated in photoshop without this lack of contrast.
Please see the following 3 images stacked next to each other here, to see what I mean;
http://imgur.com/SYPhZ.jpg
The top image is the original, the middle is rotated in photoshop to prove that it can be done, and the bottom is from the result of my code.
To see the actual images, please see here;
Before rotate: http://imgur.com/eiAOO.jpg
After rotate : http://imgur.com/TTUKS.jpg
You can see the issue most clearly if you load the images in two different tabs, and flick between them.
In terms of code, I load the image as follows;
public void testIt() throws Exception {
File source = new File("c:\\STRIP.jpg");
FileInputStream fis = new FileInputStream(source);
BufferedImage sourceImage = ImageIO.read(fis);
fis.close();
BufferedImage rotatedImage = doRotate(sourceImage, 15);
FileOutputStream output = new FileOutputStream("c:\\STRIP_ROTATED.jpg");
ImageIO.write(rotatedImage, "JPEG", output);
}
and then here is the rotate function;
public BufferedImage doRotate(BufferedImage input, int angle) {
int width = input.getWidth();
int height = input.getHeight();
double radians = Math.toRadians(angle / 10.0);
// Rotate about the input image's centre
AffineTransform rotate = AffineTransform.getRotateInstance(radians, width / 2.0, height / 2.0);
Shape rect = new Rectangle(width, height);
// Work out how big the rotated image would be..
Rectangle bounds = rotate.createTransformedShape(rect).getBounds();
// Shift the rotated image into the centre of the new bounds
rotate.preConcatenate(
AffineTransform.getTranslateInstance((bounds.width - width) / 2.0, (bounds.height - height) / 2.0));
BufferedImage output = new BufferedImage(bounds.width, bounds.height, input.getType());
Graphics2D g2d = (Graphics2D) output.getGraphics();
// Fill the background with white
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle(width, height));
RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
g2d.drawImage(input, rotate, null);
return output;
}
This is apparently a bug in JAI that has existed for a while:
The earliest mention that I was able to find of this issue appears here. That original article points to an old jai-core issue here. Having read that resolution, it appears that there is a root bug that is still open and described here.
Whether or not all of that detective work is relevant to your application, it may be possible to construct a color space that is more tolerant than the default that JAI is using for your test code.
In the absolute worst case, you could write the pixel traversal yourself to create a rotated image. That isn't the optimal solution but I mention it for completeness if you absolutely need a solution to this problem today.
I am using Java AWT for scaling a JPEG image, to create thumbnails. The code works fine when the image has a normal sampling factor ( 2x2,1x1,1x1 )
However, an image which has this sampling factor ( 1x1, 1x1, 1x1 ) creates problem when scaled. The colors get corrupted though the features are recognizable.
The original and the thumbnail:
alt text http://otherplace.in/thumb1.jpg
The code I am using is roughly equivalent to:
static BufferedImage awtScaleImage(BufferedImage image,
int maxSize, int hint) {
// We use AWT Image scaling because it has far superior quality
// compared to JAI scaling. It also performs better (speed)!
System.out.println("AWT Scaling image to: " + maxSize);
int w = image.getWidth();
int h = image.getHeight();
float scaleFactor = 1.0f;
if (w > h)
scaleFactor = ((float) maxSize / (float) w);
else
scaleFactor = ((float) maxSize / (float) h);
w = (int)(w * scaleFactor);
h = (int)(h * scaleFactor);
// since this code can run both headless and in a graphics context
// we will just create a standard rgb image here and take the
// performance hit in a non-compatible image format if any
Image i = image.getScaledInstance(w, h, hint);
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(i, null, null);
g.dispose();
i.flush();
return image;
}
(Code courtesy of this page )
Is there a better way to do this?
Here's a test image with sampling factor of [ 1x1, 1x1, 1x1 ].
I believe the problem is not the scaling, but your use of an incompatible color model ("image type") when constructing your BufferedImage.
Creating decent thumbnails in Java is surprisingly hard. Here's a detailed discussion.
I have some old Java 2D code I want to reuse, but was wondering, is this the best way to get the highest quality images?
public static BufferedImage getScaled(BufferedImage imgSrc, Dimension dim) {
// This code ensures that all the pixels in the image are loaded.
Image scaled = imgSrc.getScaledInstance(
dim.width, dim.height, Image.SCALE_SMOOTH);
// This code ensures that all the pixels in the image are loaded.
Image temp = new ImageIcon(scaled).getImage();
// Create the buffered image.
BufferedImage bufferedImage = new BufferedImage(temp.getWidth(null),
temp.getHeight(null), BufferedImage.TYPE_INT_RGB);
// Copy image to buffered image.
Graphics g = bufferedImage.createGraphics();
// Clear background and paint the image.
g.setColor(Color.white);
g.fillRect(0, 0, temp.getWidth(null),temp.getHeight(null));
g.drawImage(temp, 0, 0, null);
g.dispose();
// j2d's image scaling quality is rather poor, especially when
// scaling down an image to a much smaller size. We'll post filter
// our images using a trick found at
// http://blogs.cocoondev.org/mpo/archives/003584.html
// to increase the perceived quality....
float origArea = imgSrc.getWidth() * imgSrc.getHeight();
float newArea = dim.width * dim.height;
if (newArea <= (origArea / 2.)) {
bufferedImage = blurImg(bufferedImage);
}
return bufferedImage;
}
public static BufferedImage blurImg(BufferedImage src) {
// soften factor - increase to increase blur strength
float softenFactor = 0.010f;
// convolution kernel (blur)
float[] softenArray = {
0, softenFactor, 0,
softenFactor, 1-(softenFactor*4), softenFactor,
0, softenFactor, 0};
Kernel kernel = new Kernel(3, 3, softenArray);
ConvolveOp cOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
return cOp.filter(src, null);
}
Chris Campbell has an excellent and detailed write-up on scaling images - see this article.
Chet Haase and Romain Guy also have a detailed and very informative write-up of image scaling in their book, Filthy Rich Clients.
Adding some clarifying information here.
No, that isn't the best way to get a good looking scaled image in Java. Use of getScaledInstance and the underlying AreaAveragingScaleFilter are deprecated by the Java2D team in favor of some more advanced methods.
If you are just trying to get a good-looking thumbnail, using Chris Campbell's method as suggested by David is the way to go. For what it's worth, I have implemented that algorithm along with 2 other faster methods in a Java image-scaling library called imgscalr (Apache 2 license). The point of the library was to specifically address this question in a highly tuned library that is easy to use:
BufferedImage thumbnail = Scalr.resize(srcImg, 150);
To get the best-looking scaled instance possible in Java, the method call would look something like this:
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY,
150, 100, Scalr.OP_ANTIALIAS);
The library will scale the original image using the incremental-scaling approach recommended by the Java2D team and then to make it look even nicer a very mild convolveop is applied to the image, effectively anti-aliasing it slightly. This is really nice for small thumbnails, not so important for huge images.
If you haven't worked with convolveops before, it's a LOT of work just to get the perfect looking kernel for the op to look good in all use-cases. The OP constant defined on the Scalr class is the result of a week of collaboration with a social networking site in Brazil that had rolled out imgscalr to process profile pictures for it's members. We went back and forth and tried something like 10 different kernels until we found one that was subtle enough not to make the image look soft or fuzzy but still smooth out the transitions between pixel values so the image didn't look "sharp" and noisey at small sizes.
If you want the best looking scaled image regardless of speed, go with Juha's suggestion of using the java-image-scaling library. It is a very comprehensive collection of Java2D Ops and includes support for the Lanczsos algorithm which will give you the best-looking result.
I would stay away from JAI, not because it's bad, but because it is just a different/broader tool than what you are trying to solve. Any of the previous 3 approaches mentioned will give you great looking thumbnails without needing to add a whole new imaging platform to your project in fewer lines of code.
You can use JAI (Java Advanced Imaging) to get more sophisticated image resizing options. See https://jai.dev.java.net/. These allow you much more flexibility than the java.awt.image package.
You could also look into java-image-scaling library.
You can Resize Image using a Open Source Library
enter link description here
I have done with Large Image to Small and result excellent, keeping the aspect ratio fine.
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import org.imgscalr.*;
import org.imgscalr.Scalr.Method;
public class ImageScaller {
static String SRC_FILES_PATH = "I:\\BigGreen\\";
static String IMAGE_FILE_PATH = "I:\\Resized\\";
public ImageScaller() {
}
public static void main(String[] args) {
// TODO Auto-generated method stub
try
{
ResizeLoad(SRC_FILES_PATH);
}
catch(Exception ex)
{
System.out.println(ex.toString());
}
}
public static int ResizeLoad(String path)
{
String file;
File folder ;
File[] listOfFiles = null;
listOfFiles = null;
try
{
folder = new File(path);
listOfFiles = folder.listFiles();
for (int i = 0; i < listOfFiles.length; i++)
{
if (listOfFiles[i].isFile())
{
file = listOfFiles[i].getName();
ScalledImageWrite(listOfFiles[i].getPath(),file);
//System.out.println(file);
}
}
System.out.println("All Resized");
}
catch (Exception e)
{
JOptionPane.showMessageDialog(null,e.toString(),"Resize & Load :Exception",JOptionPane.WARNING_MESSAGE);
}
return listOfFiles.length;
}
private static File ScalledImageWrite(String path,String fileName)
{
try
{
BufferedImage img = ImageIO.read(new File(path));
BufferedImage scaledImg = Scalr.resize(img, Method.AUTOMATIC, 24, 24);
File destFile = new File(IMAGE_FILE_PATH + fileName);
ImageIO.write(scaledImg, "png", destFile);
//System.out.println("Done resizing");
return destFile;
}
catch (Exception e)
{
JOptionPane.showMessageDialog(null,e.toString(),"Scalled Images Write: Exception",JOptionPane.WARNING_MESSAGE);
return null;
}
}
}
Here is the output in pictorial format of this code.