How to improve the performance of g.drawImage() method for resizing images
I have an application where users are able to upload pictures in albums but naturally the uploaded images need to be resized so there are also thumbs available and the shown pictures also fit in the page (eg. 800x600). The way I do the resize is like this:
Image scaledImage = img.getScaledInstance((int)width, (int)height, Image.SCALE_SMOOTH);
BufferedImage imageBuff = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_RGB);
Graphics g = imageBuff.createGraphics();
g.drawImage(scaledImage, 0, 0, new Color(0,0,0), null);
g.dispose();
And it works okayish. My only problem is that the g.drawImage()
method seems to be awfully slow, and I just cannot imagine the user to be patient enought to wait for an upload of 20 pictures 20*10 secs ~ 3 minutes. In fact, on my computer it takes almost 40 secs for making the 3 different resizes for a single picture.
That's not good enough, and I'm looking for a faster solution. I'm wondering if somebody could te开发者_如何学Cll me about a better one in Java OR by calling a shell script, command, whatever hack you know, it has to be quicker, everything else does not matter this time.
I'm using code similar to the following to scale images, I removed the part that deals with preserving the aspect ratio. The performance was definitely better than 10s per image, but I don't remember any exact numbers. To archive better quality when downscaling you should scale in several steps if the original image is more than twice the size of the wanted thumbnail, each step should scale the previous image to about half its size.
public static BufferedImage getScaledImage(BufferedImage image, int width, int height) throws IOException {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double scaleX = (double)width/imageWidth;
double scaleY = (double)height/imageHeight;
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);
return bilinearScaleOp.filter(
image,
new BufferedImage(width, height, image.getType()));
}
Do you really need the quality that is provided by using Image.SCALE_SMOOTH? If you don't, you can try using Image.SCALE_FAST. You might find this article helpful if you want to stick with something provided by Java.
The main point of the question was about the performance of scaling images in Java. The other answers, showed different approaches, without evaluating them further. I was curious about this as well, so I tried to write a small performance test. However, testing the image scaling performance reliably, sensibly and objectively is difficult. There are far too many infuencing factors to be taken into account:
- The size of the input image
- The size of the output image
- The interpolation (i.e. "quality": Nearest neighbor, bilinear, bicubic)
- The
BufferedImage.TYPE_*
of the input image - The
BufferedImage.TYPE_*
of the output image - The JVM version and operating system
- Finally: The method that is actually used for performing the operation.
I tried to cover those that I considered the most important ones. The setup was:
The input is a simple, "average" photo (particularly, this "Image Of The Day" from Wikipedia, with a size of 2560x1706 pixels)
The main interpolation types are tested - namely, by using
RenderingHints
where theINTERPOLATION
key was set to the valuesNEAREST_NEIGHBOR
,BILINEAR
andBICUBIC
The input image was converted to different types:
BufferedImage.TYPE_INT_RGB
: A type that is commonly used, as it "usually" shows the best performance characteristicsBufferedImage.TYPE_3BTE_BGR
: This is the type that it is read with by default, when just reading it withImageIO
The target image size was varied between a width of 10000 (thus, scaling the image up), and 100 (thus, scaling the image down to thumbnail size)
The tests have been run on a Win64/AMD K10 with 3.7 GHz and JDK 1.8u31, with -Xmx4000m -server
.
The tested methods are:
- Using an
AffineTransformOp
, as in the answer by Jörn Horstmann - Using a
Graphics
, as in the answer by johnstosh - Using
Image#getScaledInstance
The code of the tests is shown here:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Supplier;
import javax.imageio.ImageIO;
import javax.swing.JLabel;
public class ImageScalingPerformance
{
private static int blackHole = 0;
public static void main(String[] args) throws IOException
{
// Image with size 2560 x 1706, from https://upload.wikimedia.org/
// wikipedia/commons/4/41/Pitta_moluccensis_-_Kaeng_Krachan.jpg
BufferedImage image = ImageIO.read(
new File("Pitta_moluccensis_-_Kaeng_Krachan.jpg"));
int types[] =
{
BufferedImage.TYPE_3BYTE_BGR,
BufferedImage.TYPE_INT_RGB,
};
Object interpolationValues[] =
{
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
RenderingHints.VALUE_INTERPOLATION_BILINEAR,
RenderingHints.VALUE_INTERPOLATION_BICUBIC,
};
int widths[] =
{
10000, 5000, 2500, 1000, 500, 100
};
System.out.printf("%10s%22s%6s%18s%10s\n",
"Image type", "Interpolation", "Size", "Method", "Duration (ms)");
for (int type : types)
{
BufferedImage currentImage = convert(image, type);
for (Object interpolationValue : interpolationValues)
{
for (int width : widths)
{
List<Supplier<Image>> tests =
createTests(currentImage, interpolationValue, width);
for (Supplier<Image> test : tests)
{
double durationMs = computeMs(test);
System.out.printf("%10s%22s%6s%18s%10s\n",
stringForBufferedImageType(type),
stringForInterpolationValue(interpolationValue),
String.valueOf(width),
String.valueOf(test),
String.format(Locale.ENGLISH, "%6.3f", durationMs));
}
}
}
}
System.out.println(blackHole);
}
private static List<Supplier<Image>> createTests(
BufferedImage image, Object interpolationValue, int width)
{
RenderingHints renderingHints = new RenderingHints(null);
renderingHints.put(
RenderingHints.KEY_INTERPOLATION,
interpolationValue);
double scale = (double) width / image.getWidth();
int height = (int)(scale * image.getHeight());
Supplier<Image> s0 = new Supplier<Image>()
{
@Override
public BufferedImage get()
{
return scaleWithAffineTransformOp(
image, width, height, renderingHints);
}
@Override
public String toString()
{
return "AffineTransformOp";
}
};
Supplier<Image> s1 = new Supplier<Image>()
{
@Override
public Image get()
{
return scaleWithGraphics(
image, width, height, renderingHints);
}
@Override
public String toString()
{
return "Graphics";
}
};
Supplier<Image> s2 = new Supplier<Image>()
{
@Override
public Image get()
{
return scaleWithGetScaledInstance(
image, width, height, renderingHints);
}
@Override
public String toString()
{
return "GetScaledInstance";
}
};
List<Supplier<Image>> tests = new ArrayList<Supplier<Image>>();
tests.add(s0);
tests.add(s1);
tests.add(s2);
return tests;
}
private static double computeMs(Supplier<Image> supplier)
{
int runs = 5;
long before = System.nanoTime();
for (int i=0; i<runs; i++)
{
Image image0 = supplier.get();
blackHole += image0.hashCode();
}
long after = System.nanoTime();
double durationMs = (after-before) / 1e6 / runs;
return durationMs;
}
private static BufferedImage convert(BufferedImage image, int type)
{
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(), type);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
private static BufferedImage scaleWithAffineTransformOp(
BufferedImage image, int w, int h,
RenderingHints renderingHints)
{
BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
double scaleX = (double) w / image.getWidth();
double scaleY = (double) h / image.getHeight();
AffineTransform affineTransform =
AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp affineTransformOp = new AffineTransformOp(
affineTransform, renderingHints);
return affineTransformOp.filter(
image, scaledImage);
}
private static BufferedImage scaleWithGraphics(
BufferedImage image, int w, int h,
RenderingHints renderingHints)
{
BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
Graphics2D g = scaledImage.createGraphics();
g.setRenderingHints(renderingHints);
g.drawImage(image, 0, 0, w, h, null);
g.dispose();
return scaledImage;
}
private static Image scaleWithGetScaledInstance(
BufferedImage image, int w, int h,
RenderingHints renderingHints)
{
int hint = Image.SCALE_REPLICATE;
if (renderingHints.get(RenderingHints.KEY_ALPHA_INTERPOLATION) !=
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
{
hint = Image.SCALE_AREA_AVERAGING;
}
Image scaledImage = image.getScaledInstance(w, h, hint);
MediaTracker mediaTracker = new MediaTracker(new JLabel());
mediaTracker.addImage(scaledImage, 0);
try
{
mediaTracker.waitForAll();
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
return scaledImage;
}
private static String stringForBufferedImageType(int type)
{
switch (type)
{
case BufferedImage.TYPE_INT_RGB : return "INT_RGB";
case BufferedImage.TYPE_INT_ARGB : return "INT_ARGB";
case BufferedImage.TYPE_INT_ARGB_PRE : return "INT_ARGB_PRE";
case BufferedImage.TYPE_INT_BGR : return "INT_BGR";
case BufferedImage.TYPE_3BYTE_BGR : return "3BYTE_BGR";
case BufferedImage.TYPE_4BYTE_ABGR : return "4BYTE_ABGR";
case BufferedImage.TYPE_4BYTE_ABGR_PRE : return "4BYTE_ABGR_PRE";
case BufferedImage.TYPE_USHORT_565_RGB : return "USHORT_565_RGB";
case BufferedImage.TYPE_USHORT_555_RGB : return "USHORT_555_RGB";
case BufferedImage.TYPE_BYTE_GRAY : return "BYTE_GRAY";
case BufferedImage.TYPE_USHORT_GRAY : return "USHORT_GRAY";
case BufferedImage.TYPE_BYTE_BINARY : return "BYTE_BINARY";
case BufferedImage.TYPE_BYTE_INDEXED : return "BYTE_INDEXED";
}
return "CUSTOM";
}
private static String stringForInterpolationValue(Object value)
{
if (value == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
{
return "NEAREST/REPLICATE";
}
if (value == RenderingHints.VALUE_INTERPOLATION_BILINEAR)
{
return "BILINEAR/AREA_AVG";
}
if (value == RenderingHints.VALUE_INTERPOLATION_BICUBIC)
{
return "BICUBIC/AREA_AVG";
}
return "(unknown)";
}
}
First, regarding getScaledInstance
: As Chris Campbell has pointed out in his (famous) article about The Perils of Image.getScaledInstance() (which was already linked to in other answers), the Image#getScaledInstance
method is somewhat broken, and has a distressingly bad performance for most configurations. Additionally, it has the disadvantage of not having such a fine-grained control about the interpolation type. This should be taken into account in the following performance comparison: The quality of the resulting images may differ, which is not considered here. For example, the "area averaging" method of getScaledInstance
does not yield a good image quality when the image size is increased.
(The most severe drawback of Image#getScaledInstance
is IMHO that it only delivers an Image
, and not a BufferedImage
, but if the image is only supposed to be painted into a Graphics
, this may not be important)
I'll just dump the output of the program here for reference, some details will follow below:
Image type Interpolation Size MethodDuration (ms)
3BYTE_BGR NEAREST/REPLICATE 10000 AffineTransformOp 197.287
3BYTE_BGR NEAREST/REPLICATE 10000 Graphics 184.427
3BYTE_BGR NEAREST/REPLICATE 10000 GetScaledInstance 1869.759
3BYTE_BGR NEAREST/REPLICATE 5000 AffineTransformOp 38.354
3BYTE_BGR NEAREST/REPLICATE 5000 Graphics 40.220
3BYTE_BGR NEAREST/REPLICATE 5000 GetScaledInstance 1088.448
3BYTE_BGR NEAREST/REPLICATE 2500 AffineTransformOp 10.153
3BYTE_BGR NEAREST/REPLICATE 2500 Graphics 9.461
3BYTE_BGR NEAREST/REPLICATE 2500 GetScaledInstance 613.030
3BYTE_BGR NEAREST/REPLICATE 1000 AffineTransformOp 2.137
3BYTE_BGR NEAREST/REPLICATE 1000 Graphics 1.956
3BYTE_BGR NEAREST/REPLICATE 1000 GetScaledInstance 464.989
3BYTE_BGR NEAREST/REPLICATE 500 AffineTransformOp 0.861
3BYTE_BGR NEAREST/REPLICATE 500 Graphics 0.750
3BYTE_BGR NEAREST/REPLICATE 500 GetScaledInstance 407.751
3BYTE_BGR NEAREST/REPLICATE 100 AffineTransformOp 0.206
3BYTE_BGR NEAREST/REPLICATE 100 Graphics 0.153
3BYTE_BGR NEAREST/REPLICATE 100 GetScaledInstance 385.863
3BYTE_BGR BILINEAR/AREA_AVG 10000 AffineTransformOp 830.097
3BYTE_BGR BILINEAR/AREA_AVG 10000 Graphics 1501.290
3BYTE_BGR BILINEAR/AREA_AVG 10000 GetScaledInstance 1627.934
3BYTE_BGR BILINEAR/AREA_AVG 5000 AffineTransformOp 207.816
3BYTE_BGR BILINEAR/AREA_AVG 5000 Graphics 376.789
3BYTE_BGR BILINEAR/AREA_AVG 5000 GetScaledInstance 1063.942
3BYTE_BGR BILINEAR/AREA_AVG 2500 AffineTransformOp 52.362
3BYTE_BGR BILINEAR/AREA_AVG 2500 Graphics 95.041
3BYTE_BGR BILINEAR/AREA_AVG 2500 GetScaledInstance 612.660
3BYTE_BGR BILINEAR/AREA_AVG 1000 AffineTransformOp 9.121
3BYTE_BGR BILINEAR/AREA_AVG 1000 Graphics 15.749
3BYTE_BGR BILINEAR/AREA_AVG 1000 GetScaledInstance 452.578
3BYTE_BGR BILINEAR/AREA_AVG 500 AffineTransformOp 2.593
3BYTE_BGR BILINEAR/AREA_AVG 500 Graphics 4.237
3BYTE_BGR BILINEAR/AREA_AVG 500 GetScaledInstance 407.661
3BYTE_BGR BILINEAR/AREA_AVG 100 AffineTransformOp 0.275
3BYTE_BGR BILINEAR/AREA_AVG 100 Graphics 0.297
3BYTE_BGR BILINEAR/AREA_AVG 100 GetScaledInstance 381.835
3BYTE_BGR BICUBIC/AREA_AVG 10000 AffineTransformOp 3015.943
3BYTE_BGR BICUBIC/AREA_AVG 10000 Graphics 5431.703
3BYTE_BGR BICUBIC/AREA_AVG 10000 GetScaledInstance 1654.424
3BYTE_BGR BICUBIC/AREA_AVG 5000 AffineTransformOp 756.136
3BYTE_BGR BICUBIC/AREA_AVG 5000 Graphics 1359.288
3BYTE_BGR BICUBIC/AREA_AVG 5000 GetScaledInstance 1063.467
3BYTE_BGR BICUBIC/AREA_AVG 2500 AffineTransformOp 189.953
3BYTE_BGR BICUBIC/AREA_AVG 2500 Graphics 341.039
3BYTE_BGR BICUBIC/AREA_AVG 2500 GetScaledInstance 615.807
3BYTE_BGR BICUBIC/AREA_AVG 1000 AffineTransformOp 31.351
3BYTE_BGR BICUBIC/AREA_AVG 1000 Graphics 55.914
3BYTE_BGR BICUBIC/AREA_AVG 1000 GetScaledInstance 451.808
3BYTE_BGR BICUBIC/AREA_AVG 500 AffineTransformOp 8.422
3BYTE_BGR BICUBIC/AREA_AVG 500 Graphics 15.028
3BYTE_BGR BICUBIC/AREA_AVG 500 GetScaledInstance 408.626
3BYTE_BGR BICUBIC/AREA_AVG 100 AffineTransformOp 0.703
3BYTE_BGR BICUBIC/AREA_AVG 100 Graphics 0.825
3BYTE_BGR BICUBIC/AREA_AVG 100 GetScaledInstance 382.610
INT_RGB NEAREST/REPLICATE 10000 AffineTransformOp 330.445
INT_RGB NEAREST/REPLICATE 10000 Graphics 114.656
INT_RGB NEAREST/REPLICATE 10000 GetScaledInstance 2784.542
INT_RGB NEAREST/REPLICATE 5000 AffineTransformOp 83.081
INT_RGB NEAREST/REPLICATE 5000 Graphics 29.148
INT_RGB NEAREST/REPLICATE 5000 GetScaledInstance 1117.136
INT_RGB NEAREST/REPLICATE 2500 AffineTransformOp 22.296
INT_RGB NEAREST/REPLICATE 2500 Graphics 7.735
INT_RGB NEAREST/REPLICATE 2500 GetScaledInstance 436.779
INT_RGB NEAREST/REPLICATE 1000 AffineTransformOp 3.859
INT_RGB NEAREST/REPLICATE 1000 Graphics 2.542
INT_RGB NEAREST/REPLICATE 1000 GetScaledInstance 205.863
INT_RGB NEAREST/REPLICATE 500 AffineTransformOp 1.413
INT_RGB NEAREST/REPLICATE 500 Graphics 0.963
INT_RGB NEAREST/REPLICATE 500 GetScaledInstance 156.537
INT_RGB NEAREST/REPLICATE 100 AffineTransformOp 0.160
INT_RGB NEAREST/REPLICATE 100 Graphics 0.074
INT_RGB NEAREST/REPLICATE 100 GetScaledInstance 126.159
INT_RGB BILINEAR/AREA_AVG 10000 AffineTransformOp 1019.438
INT_RGB BILINEAR/AREA_AVG 10000 Graphics 1230.621
INT_RGB BILINEAR/AREA_AVG 10000 GetScaledInstance 2721.918
INT_RGB BILINEAR/AREA_AVG 5000 AffineTransformOp 254.616
INT_RGB BILINEAR/AREA_AVG 5000 Graphics 308.374
INT_RGB BILINEAR/AREA_AVG 5000 GetScaledInstance 1269.898
INT_RGB BILINEAR/AREA_AVG 2500 AffineTransformOp 68.137
INT_RGB BILINEAR/AREA_AVG 2500 Graphics 80.163
INT_RGB BILINEAR/AREA_AVG 2500 GetScaledInstance 444.968
INT_RGB BILINEAR/AREA_AVG 1000 AffineTransformOp 13.093
INT_RGB BILINEAR/AREA_AVG 1000 Graphics 15.396
INT_RGB BILINEAR/AREA_AVG 1000 GetScaledInstance 211.929
INT_RGB BILINEAR/AREA_AVG 500 AffineTransformOp 3.238
INT_RGB BILINEAR/AREA_AVG 500 Graphics 3.689
INT_RGB BILINEAR/AREA_AVG 500 GetScaledInstance 159.688
INT_RGB BILINEAR/AREA_AVG 100 AffineTransformOp 0.329
INT_RGB BILINEAR/AREA_AVG 100 Graphics 0.277
INT_RGB BILINEAR/AREA_AVG 100 GetScaledInstance 127.905
INT_RGB BICUBIC/AREA_AVG 10000 AffineTransformOp 4211.287
INT_RGB BICUBIC/AREA_AVG 10000 Graphics 4712.587
INT_RGB BICUBIC/AREA_AVG 10000 GetScaledInstance 2830.749
INT_RGB BICUBIC/AREA_AVG 5000 AffineTransformOp 1069.088
INT_RGB BICUBIC/AREA_AVG 5000 Graphics 1182.285
INT_RGB BICUBIC/AREA_AVG 5000 GetScaledInstance 1155.663
INT_RGB BICUBIC/AREA_AVG 2500 AffineTransformOp 263.003
INT_RGB BICUBIC/AREA_AVG 2500 Graphics 297.663
INT_RGB BICUBIC/AREA_AVG 2500 GetScaledInstance 444.497
INT_RGB BICUBIC/AREA_AVG 1000 AffineTransformOp 42.841
INT_RGB BICUBIC/AREA_AVG 1000 Graphics 48.605
INT_RGB BICUBIC/AREA_AVG 1000 GetScaledInstance 209.261
INT_RGB BICUBIC/AREA_AVG 500 AffineTransformOp 11.004
INT_RGB BICUBIC/AREA_AVG 500 Graphics 12.407
INT_RGB BICUBIC/AREA_AVG 500 GetScaledInstance 156.794
INT_RGB BICUBIC/AREA_AVG 100 AffineTransformOp 0.817
INT_RGB BICUBIC/AREA_AVG 100 Graphics 0.790
INT_RGB BICUBIC/AREA_AVG 100 GetScaledInstance 128.700
It can be seen that for almost all cases, getScaledInstance
performs poorly compared to the other approaches (and the few cases where it seems to perform better can be explained by the lower quality when scaling up).
The AffineTransformOp
based approach seems to perform best on average, with the only notable exception being a NEAREST_NEIGHBOR
scaling of TYPE_INT_RGB
images, where the Graphics
-based approach seems to be consistently faster.
The bottom line is: The method using AffineTransformOp
, as in the answer by Jörn Horstmann, seems to be the one that offers the best performance for most application cases.
Well, Jacob and I wanted to resize an Image, not a BufferedImage. So we ended up with this code:
/**
* we want the x and o to be resized when the JFrame is resized
*
* @param originalImage an x or an o. Use cross or oh fields.
*
* @param biggerWidth
* @param biggerHeight
*/
private Image resizeToBig(Image originalImage, int biggerWidth, int biggerHeight) {
int type = BufferedImage.TYPE_INT_ARGB;
BufferedImage resizedImage = new BufferedImage(biggerWidth, biggerHeight, type);
Graphics2D g = resizedImage.createGraphics();
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);
g.drawImage(originalImage, 0, 0, biggerWidth, biggerHeight, this);
g.dispose();
return resizedImage;
}
You can use ImageMagick to create thumbnails.
convert -define jpeg:size=500x180 hatching_orig.jpg -auto-orient \
-thumbnail 250x90 -unsharp 0x.5 thumbnail.gif
To use it from Java you can try JMagick which provides a Java (JNI) interface to ImageMagick. Or you can simply invoke the ImageMagick commands directly using Runtime.exec
or ProcessBuilder
.
this works for me:
private BufferedImage getScaledImage(BufferedImage src, int w, int h){
int original_width = src.getWidth();
int original_height = src.getHeight();
int bound_width = w;
int bound_height = h;
int new_width = original_width;
int new_height = original_height;
// first check if we need to scale width
if (original_width > bound_width) {
//scale width to fit
new_width = bound_width;
//scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}
// then check if we need to scale even with the new height
if (new_height > bound_height) {
//scale height to fit instead
new_height = bound_height;
//scale width to maintain aspect ratio
new_width = (new_height * original_width) / original_height;
}
BufferedImage resizedImg = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setBackground(Color.WHITE);
g2.clearRect(0,0,new_width, new_height);
g2.drawImage(src, 0, 0, new_width, new_height, null);
g2.dispose();
return resizedImg;
}
also i added white background for png
The fastest way to scale an image in java without loosing image quality is to use Bilinear scaling. Bilinear is only good if you scale the image by 50% at a time because of the way it works. The following code is from 'Filthy rich clients' by Chet Haase. He explains multiple techniques in the book, but this one has the highest performance to quality trade-off.
It supports all types of BufferedImages so don't worry about compatability. It also lets java2D hardware accelerate your image because the calculations are done by Java2D. Don't worry if you don't understand that last part. The most important thing is that this is the fastest way to do it.
public static BufferedImage getFasterScaledInstance(BufferedImage img, int targetWidth, int targetHeight, boolean progressiveBilinear)
{
int type = (img.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
BufferedImage scratchImage = null;
Graphics2D g2 = null;
int w, h;
int prevW = ret.getWidth();
int prevH = ret.getHeight();
if(progressiveBilinear) {
w = img.getWidth();
h = img.getHeight();
}else{
w = targetWidth;
h = targetHeight;
}
do {
if (progressiveBilinear && w > targetWidth) {
w /= 2;
if(w < targetWidth) {
w = targetWidth;
}
}
if (progressiveBilinear && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
if(scratchImage == null) {
scratchImage = new BufferedImage(w, h, type);
g2 = scratchImage.createGraphics();
}
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null);
prevW = w;
prevH = h;
ret = scratchImage;
} while (w != targetWidth || h != targetHeight);
if (g2 != null) {
g2.dispose();
}
if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) {
scratchImage = new BufferedImage(targetWidth, targetHeight, type);
g2 = scratchImage.createGraphics();
g2.drawImage(ret, 0, 0, null);
g2.dispose();
ret = scratchImage;
}
System.out.println("ret is "+ret);
return ret;
}
You will ever have a trade off between the speed of the resizing and the quality of the resulting picture. You might try another scaling algorithm of the JDK.
The best and most flexible tool for image editing AFAIK is ImageMagick.
There are two interfaces for the Java Language:
- JMagick - is a JNI Interface to ImageMagick. See the projects Wiki to get more information.
- im4java - is a command line interface for ImageMagick. It is not, like JMagick, based on JNI.
You should prefer im4java before using the command line directly to call ImageMagick.
Old question but in case someone else hits this issue: I profiled your code and your biggest bottleneck is the call to:
Image.getScaledInstance()
That call is well known to be horribly slow. Please be convinced by reading this document:
The Perils of Image.getScaledInstance()
The simplest/best solution for a dramatic performance improvement would be to replace that call. You could use the method from dpineda's answer (see his answer/code above):
private BufferedImage getScaledImage(BufferedImage src, int w, int h){
I tested his method and it works really well. In my testing, his implementation (which avoids the slow Image.getScaledInstance()) shaved 80% of the processing time!
Some improvement in performance (perhaps small, perhaps negligible, perhaps at the expense of quality) can be attained by tweaking the rendering hints. E.g.
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
If you want something fast, you're probably better with some native code, if you can give up on portability.
But if you want a pure Java solution, you can try some other solutions as well, like Graphics2D.scale and Image.getScaledInstance. I've used them in the past, but can't remember which had better performance or better looking results, sorry.
Try them out, and see which one best fits your needs.
I used im4java
with GraphicsMagick in order to have really faster results (faster than ImageIO).
Used that sort of code :
public static void createFilePreview(final File originalFile, final String originalFileMimeType, final File destinationPreviewFile, final Integer maxWidth, final Integer maxHeight) throws IOException, InterruptedException, IM4JavaException {
runThumbnail(new ConvertCmd(), originalFile.getAbsolutePath(), originalFileMimeType, destinationPreviewFile.getAbsolutePath(), maxWidth, maxHeight);
}
public static void createFilePreview(final InputStream originalFileInputStream, final String originalFileMimeType, final File destinationPreviewFile, final Integer maxWidth, final Integer maxHeight) throws IOException, InterruptedException, IM4JavaException {
final ConvertCmd cmd = new ConvertCmd();
cmd.setInputProvider(new Pipe(originalFileInputStream, null));
runThumbnail(cmd, "-", originalFileMimeType, destinationPreviewFile.getAbsolutePath(), maxWidth, maxHeight);
}
private static void runThumbnail(final ConvertCmd cmd, final String originalFile, final String originalFileMimeType, final String destinationPreviewFile, final Integer maxWidth, final Integer maxHeight) throws IOException, InterruptedException, IM4JavaException {
final IMOperation operation = new IMOperation();
// if it is a PDF, will add some optional parameters to get nicer results
if (originalFileMimeType.startsWith("application/pdf")) {
operation.define("pdf:use-trimbox=true"); // as it is said here http://www.prepressure.com/pdf/basics/page_boxes "The imposition programs and workflows that I know all use the TrimBox as the basis for positioning pages on a press sheet."
operation.density(300, 300); // augment the rendering from 75 (screen size) to 300 dpi in order to create big preview with good quality
}
operation.addImage("[0]"); // if it is a PDF or other multiple image source, will extract the first page / image, else it is ignored
operation.autoOrient(); // Auto-orient the image if it contains some orientation information (typically JPEG with EXIF header)
operation.thumbnail(maxWidth, maxHeight);
operation.addImage();
cmd.run(operation, originalFile, destinationPreviewFile);
}
精彩评论