25

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:
Example of blurred rotations
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:
Example of hand without RenderingHints
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. :)

Community
  • 1
  • 1
rtheunissen
  • 7,347
  • 5
  • 34
  • 65
  • 1
    I don't see where you're setting the `RenderingHints`. – Moonbeam Jul 17 '11 at 13:32
  • @Moonbeam: RenderingHints won't help with images, but rather only with shapes that we draw. If you look at the API, it states: "*The ANTIALIASING hint controls whether or not the geometry rendering methods of a Graphics2D object will attempt to reduce aliasing artifacts along the edges of shapes.*" So he is right not to even try to use them here. – Hovercraft Full Of Eels Jul 17 '11 at 13:38
  • @Hovercraft Full Of Eeels, I'm not sure if that's true. I'm pretty sure that `drawImage(...)` uses `RenderingHints.KEY_INTERPOLATION`, with `NEAREST_NEIGHBOR` being the default hint. – Moonbeam Jul 17 '11 at 13:54
  • @HoverCraft Full Of Eels: RenderingHints definitely affect images too. Although I'm starting to see a difference between filling a shape with a TexturePaint, and actually just drawing an image. What that difference might be I do not know, and would like to find out. – rtheunissen Jul 17 '11 at 14:11
  • @Rudi: I should clarify, and rather than make a blanket statement about all RenderingHints, say instead that as far as I know `RenderingHints.VALUE_ANTIALIAS_ON` does not result in anti-aliasing of images. – Hovercraft Full Of Eels Jul 17 '11 at 14:15
  • @Moonbeam: Edited the question to show how I'm implementing RenderingHints. I "create" the images in my ImageLoader, and grab them from the array when I want them in my game's main panel. In this panel I'm using the ImageLoader's setRenderingHints() method. If that makes sense? – rtheunissen Jul 17 '11 at 14:15
  • possible duplicate of [How can you detect a mouse-click event on an Image object in Java?](http://stackoverflow.com/questions/6577227/how-can-you-detect-a-mouse-click-event-on-an-image-object-in-java) – trashgod Jul 17 '11 at 14:16
  • doesn't too much blur mean too much antialiasing? – guido Jul 17 '11 at 14:18
  • @Hovercraft: Okay I'll take your word for it. :) Although interpolation hints would affect images? And what about when you fill shapes - surely then AA would affect the filled shape? – rtheunissen Jul 17 '11 at 14:18
  • @guido Yes it would, in which case the reason for my images blurring isn't AA or the lack of it, but instead how I'm using BufferedImage. At first I thought it could be upscaling, but that's definitely not the case either. – rtheunissen Jul 17 '11 at 14:20
  • @Rudi, I think you're overriding your rendering hint, such that the only rendering hint set is `KEY_ANTIALIASING`. You should be invoking `addRenderingHints(...)`, if you want multiple rendering hints. – Moonbeam Jul 17 '11 at 14:20
  • @Moonbeam: That would actually make a ridiculous amount of sense. I'll check it out. – rtheunissen Jul 17 '11 at 14:23
  • @Moonbeam: to my knowledge hints work like a map, so you set a different value for each key – guido Jul 17 '11 at 14:34
  • 2
    @Rudi: what if you keep bigger in memory images, and downscale after rotating just before visualization? – guido Jul 17 '11 at 14:37
  • @guido: That's correct it seems; that there's no difference between setting it three times the way I did and adding a new RenderingHints object the another and then setting that as the g2d's RH object. – rtheunissen Jul 17 '11 at 14:37
  • @guido: That could be something I could try, but surely this can be done without the need to do up- and downscaling? – rtheunissen Jul 17 '11 at 14:38
  • @guido, You're right. I misinterpreted the method. But regardless, before you do any image drawing, just set the `KEY_INTERPOLATION` rendering hint. – Moonbeam Jul 17 '11 at 14:46
  • @Rudi: don't take my word on it as this is according to my understanding of things but is certainly not the gospel. I'm awaiting verification by the Java graphics gurus. – Hovercraft Full Of Eels Jul 17 '11 at 14:46
  • @trashgod: I disagree strongly -- this post is not close to being a duplicate of the one linked to in your comment. I'd protect this post if I could and will offer a bounty if no decent answer has been provided within the requisite period. – Hovercraft Full Of Eels Jul 17 '11 at 14:48
  • @Hovercraft Full Of Eels: The only thing in common with my other question was the fact that it's still the same game/similar screenshot. Looking forward to figuring out what's going on here. Can share more code if I need to. – rtheunissen Jul 17 '11 at 14:52
  • @paranoid: I agree. My comment was to trashgod who voted to close this thread as a possible duplicate. – Hovercraft Full Of Eels Jul 17 '11 at 15:07
  • @Hovercraft: Haha I know, was agreeing with you. :) Cheers. – rtheunissen Jul 17 '11 at 15:12
  • @Hovercraft Full Of Eels: Your point is well taken, but I'm unsure how [protection](http://meta.stackoverflow.com/questions/52764/what-is-a-protected-question) would help. I would encourage @paranoid-android to edit this question to cite the previous question for reference. – trashgod Jul 17 '11 at 18:36
  • @guido: At the moment downscaling seems to be working quite well. :) – rtheunissen Jul 26 '11 at 23:29
  • 1
    @paranoid-android rotating an image by an arbitrary angle means destroying data; the bigger the image the less data you lose, so it could be the only option if you cannot afford switching to vector graphics – guido Jul 27 '11 at 14:03
  • @guido The only reason why I can't seem to afford vector graphics is because I couldn't get it to not "render" every time I want to paint it. I suppose I could render it to a BufferedImage first and then paint that, but with animation and constant angle changes, this simply wouldn't be efficient. If you want, hit me up with an answer suggesting up- and downscaling and I'll accept it as an answer seeing as it solved my problem better than any other suggestion has. – rtheunissen Jul 28 '11 at 01:06
  • 1
    @paranoid-android as i only gave an hint and no implementation, it's better if you answer yourself with some sample code and accept that. Note i didn't suggest upscaling (it's lossy), only to keep bigger images and scale down just before visualization – guido Jul 28 '11 at 06:49
  • @guido: That's what I meant by upscaling, which I now realise is the wrong term to be using. I'll type up an answer for it, thanks again. – rtheunissen Jul 28 '11 at 06:51

3 Answers3

9

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.

rtheunissen
  • 7,347
  • 5
  • 34
  • 65
5

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

mbarnes
  • 455
  • 2
  • 7
  • Thanks for pointing that out. I'll consider that approach next time. In actual fact my game is scalable and all the widths and heights and positions are relative to the panel size. I have checked them and the cards are being drawn at the right size. In the example above I just hardcoded the sizes so as to fit 1280x720, the default panel size. – rtheunissen Jul 18 '11 at 01:45
  • 2
    @paranoid: I think I've seen public domain SVG image playing cards somewhere, so there are appropriate images already available for you. Let me see if I can find them again. **Edit:** here are some: [svg-cards](http://svg-cards.sourceforge.net/) – Hovercraft Full Of Eels Jul 18 '11 at 02:29
  • @Hovercraft Full Of Eels: Due to the lack of support/solid answers, I'm going to redesign my deck of cards at paths and export to SVG files, then use Batik to transcode/render. If this approach works well, I will accept your suggestion as the answer. Give me a few days. – rtheunissen Jul 19 '11 at 01:28
  • @paranoid: it's not my suggestion but mbarnes, but regardless, yes, accept it if it works. Best of luck! – Hovercraft Full Of Eels Jul 19 '11 at 02:40
  • @mbarnes I'm finding Batik quite tricky to use. I may ask a new question regarding its use. – rtheunissen Jul 19 '11 at 12:53
4

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.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I'll create an sscce today, because AffineTransformOp didn't seem to do anything differently - assuming I used it right. Where and how do I post/share the sscce? Thanks. – rtheunissen Jul 18 '11 at 01:52
  • You can edit your question, paste the code, select it, click the `{}` icon, and save the edited question. There's an icon for images, too. As the behavior of `RenderingHints` is suggestive in nature, your host OS and Java platform versions may be relevant. – trashgod Jul 18 '11 at 03:44
  • Ah hopefully what I did isn't too far off? See edits. Thanks. – rtheunissen Jul 18 '11 at 06:55
  • I posted a screenshot [here](http://i56.tinypic.com/oaao43.png). I see a clear benefit to anti-aliasing or bicubic interpolation, although platforms may vary. – trashgod Jul 18 '11 at 20:50
  • Thanks for that. I can see a slight improvement as well with bicubic, but the rendering is so slow that it's really not worth it. What I might do is consider making the image files larger and downscale before a paint them. – rtheunissen Jul 18 '11 at 22:48
  • @paranoid-android, You should look into [Progressive Bilinear Scaling](http://books.google.com/books?id=FRbVIYAimp0C&pg=PT155&lpg=PT155&dq=java,+progressive+bilinear+scaling,+filthy+rich+clients&source=bl&ots=Yo-25qLzLO&sig=4vFng7x_23lScewqQYqGT6OiqCc&hl=en&ei=cRYoTs6vBcWBgAf4pbRc&sa=X&oi=book_result&ct=result&resnum=1&ved=0CBUQ6AEwAA#v=onepage&q&f=false) – Moonbeam Jul 21 '11 at 12:07