Resized image degrades in quality - java

I resized an image using Java2D Graphics class. But it doesn't look right.
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();
Is it possible to scale an image without introducing artifacts?

Bitmap graphics do not scale well, generally speaking. Degradation is particularly notable when you increase the size of the image, but even scaling down can introduce undesirable artifacts especially if not scaling by integral factors.
The best solution, if you need multiple sizes of a single image for display, is to either use vector* graphics or take the highest fidelity bitmap you have and scale down, and by integral factors.
*Note that vector graphics aren't an option for photographs and the like.

This article by Chris Campbell has lots of detailed information on scaling images with Java2D.
There are a number of options you can use regarding the quality of the scaling, where generally the better the quality the longer the scaling will take (performance versus quality tradeoff).
The information in the article will probably help your scaling look better, but as #Kevin says in his answer, at the end of the day no scaling is going to be absolutely perfect.

You could look into java-image-scaling library. With a quick test it created a better quality down scaled image than using standard Java2D/AWT tools.

Related

Graphics2D Interpolation not playing well with very small BufferedImages

I'm making a small game in Java, and it uses a pixel-graphics style, with many sprites 16x16 or 32x32 pixels. However, when I rotate them, I do not want "jaggies" along the side, so I used the RenderingHint
RenderingHint.KEY_INTERPOLATION
RenderingHint.VALUE_INTERPOLATION_BILINEAR
Unfortunately, because many of the images used are very small (16x16, 32x32) the resulting image is completely unusable. Output:
http://imgur.com/a/roRh4
As you can see, the small graphics are blurred. This is the intended effect for large images, but for small images, it is very bad.
One solution is to increase the resolution of all my graphics, while keeping the "blocky" effect. That would be a pain, so is there another way to tweak the interpolation?
Thank you guys so much.
Upscale your tiny images and use that instead:
BufferedImage tiny = ImageIO.read(new File("..."));
BufferedImage bigger = new BufferedImage(tiny.getWidth()*10, tiny.getHeight()*10, tiny.getType());
Graphics2D g = bigger.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(tiny, 0, 0, bigger.getWidth(), bigger.getHeight(), 0, 0, tiny.getWidth(), tiny.getHeight(), null);
Replace the 10 scaling factor by the smallest value that gives acceptable results.
Do the rest of your rendering with high quality interpolation.

How to resize a BufferedImage in Java

I am looking for the simplest (and still non-problematic) way to resize a BufferedImage in Java.
In some answer to a question, the user coobird suggested the following solution, in his words (very slightly changed by me):
**
The Graphics object has a method to draw an Image while also performing a resize operation:
Graphics.drawImage(Image, int, int, int, int, ImageObserver)
method can be used to specify the location along with the size of the image when drawing.
So, we could use a piece of code like this:
BufferedImage originalImage = // .. created somehow
BufferedImage newImage = new BufferedImage(SMALL_SIZE, SMALL_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = newImage.createGraphics();
g.drawImage(originalImage, 0, 0, SMALL_SIZE, SMALL_SIZE, null);
g.dispose();
This will take originalImage and draw it on the newImage with the width and height of SMALL_SIZE.
**
This solution seems rather simple. I have two questions about it:
Will it also work (using the exact same code), if I want to resize an image to a larger size, not only a smaller one?
Are there any problems with this solution?
If there is a better way to do this, please suggest it.
Thanks
The major problem with single step scaling is they don't generally produce quality output, as they focus on taking the original and squeezing into a smaller space, usually by dropping out a lot of pixel information (different algorithms do different things, so I'm generalizing)
Will drawGraphics scale up and down, yes, will it do it efficiently or produce a quality output? These will come down to implementation, generally speaking, most of the scaling algorithms used by default are focused on speed. You can effect these in a little way, but generally, unless you're scaling over a small range, the quality generally suffers (from my experience).
You can take a look at The Perils of Image.getScaledInstance() for more details and discussions on the topic.
Generally, what is generally recommend is to either use a dedicated library, like imgscalr, which, from the ten minutes I've played with it, does a pretty good job or perform a stepped scale.
A stepped scale basically steps the image up or down by the power of 2 until it reaches it's desired size. Remember, scaling up is nothing more then taking a pixel and enlarging it a little, so quality will always be an issue if you scale up to a very large size.
For example...
Quality of Image after resize very low -- Java
Scale the ImageIcon automatically to label size
Java: JPanel background not scaling
Remember, any scaling is generally an expensive operation (based on the original and target size of the image), so it is generally best to try and do those operations out side of the paint process and in the background where possible.
There is also the question whether you want to maintain the aspect ratio of the image? Based on you example, the image would be scaled in a square manner (stretched to meet to the requirements of the target size), this is generally not desired. You can pass -1 to either the width or height parameter and the underlying algorithm will maintain the aspect ratio of the original image or you could simply take control and make more determinations over whether you want to fill or fit the image to a target area, for example...
Java: maintaining aspect ratio of JPanel background image
In general, I avoid using drawImage or getScaledInstance most of the time (if your scaling only over a small range or want to do a low quality, fast scale, these can work) and rely more on things like fit/fill a target area and stepped scaling. The reason for using my own methods simply comes down to not always being allowed to use outside libraries. Nice not to have to re-invent the wheel where you can
It will enlarge the original if you set the parameters so. But: you should use some smart algorithm which preserves edges because simply enlarging an image will make it blurry and will result in worse perceived quality.
No problems. Theoretically this can even be hardware-accelerated on certain platforms.

How can you produce sharp paint results when rotating a BufferedImage?

One attempted approach was to use TexturePaint and g.fillRect() to paint the image. This however requires you to create a new TexturePaint and Rectangle2D object each time you paint an image, which isn't ideal - and doesn't help anyway.
When I use g.drawImage(BufferedImage,...), the rotated images appear to be blurred/soft.
I'm familiar with RenderingHints and double-buffering (which is what I'm doing, I think), I just find it difficult to believe that you can't easily and efficiently rotate an image in Java that produces sharp results.
Code for using TexturePaint looks something like this.
Grahics2D g2d = (Graphics2D)g;
g2d.setPaint(new TexturePaint(bufferedImage, new Rectangle2D.Float(0,0,50,50)));
g2d.fillRect(0,0,50,50);
I'm using AffineTransform to rotate a hand of cards into a fan.
What would be the best approach to paint good-looking images quickly?
Here is a screenshot:
The 9 is crisp but the rest of the cards are definitely not as sharp.
It could be possible that the problem lies in when I create each card image and store it in an array.
Here's how I'm doing it at the moment:
// i from 0 to 52, card codes.
...
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
BufferedImage img = gc.createCompatibleImage(86, 126, Transparency.TRANSLUCENT);
Graphics2D g = img.createGraphics();
setRenderingHints(g);
g.drawImage(shadow, 0, 0, 86, 126, null);
g.drawImage(white, 3, 3, 80, 120, null);
g.drawImage(suit, 3, 3, 80, 120, null);
g.drawImage(value, 3, 3, 80, 120, null);
g.dispose();
cardImages[i] = img;
}
private void setRenderingHints(Graphics2D g){
g.setRenderingHint(KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
}
How should I approach this differently?
Thanks.
Edit:
Without RenderingHints
Setting AA hints made no difference. Also, setting RenderingHints when creating the images makes no difference either. It's only when they are being rotated with AffineTransform and painted using g.drawImage(...) that they seem to blur. The image above shows the difference between default (nearest neighbor) and bilinear interpolation.
Here is how I'm currently painting them (much faster than TexturePaint):
// GamePanel.java
private void paintCard(Graphics2D g, int code, int x, int y){
g.drawImage(imageLoader.getCard(code), x, y, 86, 126, null);
}
// ImageLoader.java
public BufferedImage getCard(int code){
return cardImages[code];
}
All my cards are 80x120 and the shadow .png is 86x126, so as to leave 3px semi-transparent shadow around the card. It's not a realistic shadow I know, but it looks okay.
And so the question becomes... How can you produce sharp paint results when rotating a BufferedImage?
Reference to a previous question also regarding a fanned card hand:
How can you detect a mouse-click event on an Image object in Java?
Bounty-Edit:
Okay so after much discussion I made a few test .svg cards to see how SVG Salamander would go about rendering them. Unfortunately, the performance is terrible. My implementation is clean enough, seeing as with double-buffered BufferedImage's the painting was incredibly fast. Which means I have come full circle and I'm back to my original problem.
I'll give the 50 bounty to whoever can give me a solution to get sharp BufferedImage rotations. Suggestions have been to make the images bigger than they need to be and downscale before painting, and to use bicubic interpolation. If these are the only possible solutions, then I really don't know where to go from here and I may just have to deal with the blurred rotations - because both of those impose performance setbacks.
I can finish my game if I can find a way to do this well.
Thanks to everyone. :)
When you rotate a rasterized image (such as a BufferedImage), you lose data. The best solution is to save your images larger than you'll need them, and downscale on the fly when you paint them. I've found that 1.5x the size you need is a good starting point.
Then, when you're painting the image, resize on the fly:
g.drawImage(bufferedImage, x, y, desiredWidth, desiredHeight, observer);
Rotations using bilinear interpolation is recommended.
Credit for suggestion goes to guido.
This advice is probably a little late in your design, but may be worth mentioning.
Rasterized images is probably the wrong technology to use if a lot of rotations and animations are a part of your UI; especially with complicated images with lots of curves. Just wait until you try and scale your canvass. I might suggest looking at a vector based graphical library. They will render the sorts of effects you want with less potential for artifacts.
http://xmlgraphics.apache.org/batik/using/swing.htm
Setting the interpolation type, as well as anti-aliasing value, in an AffineTransformOp may offer some improvement. Type TYPE_BICUBIC, while slower, is typically the best quality; an example is outlined here. Note that you can supply multiple RenderingHints. Another pitfall arises from failing to apply the hints each time the image is rendered. You may also need to adjust the transparency of the background, as suggested here. Finally, consider creating an sscce that includes one of your actual images.

Getting low quality thumbnail from a high quality image in Java

I have tried the below code to generate the high quality thumbnail image but got the thumb nail blurred and not that much clarity.
BufferedImage thumbImage = new BufferedImage(thumbWidth, thumbHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = thumbImage.createGraphics();
graphics2D.setBackground(Color.WHITE);
graphics2D.setPaint(Color.WHITE);
graphics2D.fillRect(0, 0, thumbWidth, thumbHeight);
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, thumbWidth, thumbHeight, null);
graphics2D.dispose();
File file = new File(thumbnailFile);
if (javax.imageio.ImageIO.write(thumbImage, "JPG", file))
return file;
}
The original image is a high quality image. Why am I getting the thumbnail image distorted and low in quality?
There are two possibilities from looking at the code:
The original image is very large, and the thumbnail is very small, resulting in image quality being degraded by the simple bilinear interpolation.
The compression artifacts resulting from the JPEG encoding is degrading the quality of the thumbnail.
Using a simple bilinear interpolation can be adequate if the original image and the thumbnail doesn't differ in size by a whole lot, for example, going from a 200x200 to 100x100.
However, when it comes to resizing images that are large (such as 1600x1200) to a thumbnail size image, bilinear interpolation (and bicubic interpolation, for that matter), so alternative techniques such as multi-step resizing should be used.
The article The Perils of Image.getScaledInstance() by Chris Campbell goes into more details about how and why downscaling large images can lead to degradation of image quality.
The book Filthy Rich Clients by Chet Haase and Romain Guy also goes into some details about creating high-quality thumbnails.
I maintain a thumbnail generation library called Thumbnailator, which uses techniques such as multi-step resizing to create high-quality thumbnails with an easy-to-use API.
For example, your example code could be written using Thumbnailator like this:
Thumbnails.of(image)
.size(thumbWidth, thumbHeight)
.outputFormat("JPG")
.toFile(file);
It's also possible to specify the compression quality settings if compression artifacts are causing image quality degradation:
Thumbnails.of(image)
.size(thumbWidth, thumbHeight)
.outputFormat("JPG")
.outoutQuality(0.9)
.toFile(file);
Thumbnails4j (I'm a maintainer, but it's owned by Elastic) is a java library that can be used to create thumbnails from image files, as well as from other file types.
File input = new File("/path/to/my_file.jpeg");
Thumbnailer thumbnailer = new ImageThumbnailer("png"); // or "jpg", whichever output format you want
List<Dimensions> outputDimensions = Collections.singletonList(new Dimensions(100, 100));
BufferedImage output = thumbnailer.getThumbnails(input, outputDimensions).get(0);
One of the approaches it takes is to iteratively resize the image, instead of jumping straight to the desired size, as this helps to preserve image quality. But this library allows you to avoid having to worry about the details of the image processing algorithm.

Is there a good alternative to javax.imageio out there?

I'm looking for a good alternative to the javax.imageio package, that lets me do simple rotating, cutting and scaling operations on images. For example, I would like to do
int angle, height, width;
image.rotateRight(angle).scale(height, width);
in order to obtain an image that is rotated angle degrees to the right and scaled down to height x width pixels.
Using Graphics2D and BufferedImages, I will have to do this, which is neither readable, nor easy to write:
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = result.createGraphics();
graphics.translate(height/2, width/2);
graphics.rotate(angle);
graphics.translate(-width/2, -height/2);
graphics.drawImage(image, 0, 0, width, height, null);
(Actually, that code doesn't even account for non-square images, which will require me to do even more magic with the translating).
There's the Java Advanced Imaging API which contains useful stuff like a RotatorDescriptor.
But I confess I find your above example pretty readable, so I'm not sure you'll get something more to your liking :-)
I agree with Brian: JAI is very good option for you. You may need to write some delegator object to get such readable code as you need and use it instead of JAI API.
Also you may use Processing (http://processing.org). It's API is simpler than JAI API. And as result of using Processing you'll get better quality for scaling and rotating operations by default.

Categories

Resources