 |
Java 2D Trickery: Antialiased Image Transforms
Posted by campbell on March 10, 2007 at 04:31 PM | Comments (15)
Note: The code snippets in this blog entry are intended to be used with Scott Violet's nifty Interactive Graphics Editor. Just cut and paste the code into that application, et voila: instant gratification. This allows you to tinker with the code and immediately see how your changes affect the rendering.
It's been a while since I posted a "Trickery" blog (previous installments in this series include "Soft Clipping" and "Light and Shadow"). This week at the Desktop Matters conference (which was great, by the way) someone asked if there was a way to antialias the edges of an image when rotating it in Java 2D. The kind gentleman noted that on Mac OS X with Apple's JDK, they do see antialiased edges if they enable RenderingHints.VALUE_ANTIALIAS_ON, but they don't see the same behavior on Sun's JDK. The reason for that is Quartz: as I mentioned in the "Soft Clipping" entry, Apple's Java 2D implementation uses Quartz under the hood, which does antialias image edges in this particular case. (But as I mentioned there as well, Apple is moving away from their Quartz renderer by default in their JDK 6 implementation, so it would be nice if there was a solution for all platforms.)
It is true that Sun's Java 2D implementation does not automatically antialias image edges when rendering with Graphics.drawImage(). However, there is a simple workaround: use TexturePaint and render a transformed/antialiased fillRect(). The following code demonstrates this technique:
import java.awt.geom.*;
import java.awt.image.*;
// Clear the background to black
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
// Create the source image
BufferedImage srcImage =
new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = srcImage.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, 50, 50);
g2.setColor(Color.YELLOW);
g2.fillOval(5, 5, 40, 40);
g2.setColor(Color.BLACK);
g2.fillOval(15, 15, 5, 5);
g2.fillOval(30, 15, 5, 5);
g2.drawOval(20, 30, 10, 7);
g2.dispose();
// Render the image untransformed
g.drawImage(srcImage, 15, 7, null);
g.setColor(Color.WHITE);
g.drawString("Untransformed", 80, 25);
// Render the image rotated (with NEAREST_NEIGHBOR)
AffineTransform xform = new AffineTransform();
xform.setToIdentity();
xform.translate(15, 70);
xform.rotate(Math.PI/8, 25, 25);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(srcImage, xform, null);
g.setColor(Color.WHITE);
g.drawString("Nearest Neighbor", 80, 85);
// Render the image rotated (with BILINEAR)
xform.setToIdentity();
xform.translate(15, 130);
xform.rotate(Math.PI/8, 25, 25);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(srcImage, xform, null);
g.setColor(Color.WHITE);
g.drawString("Bilinear", 80, 145);
// Render the image rotated (with BILINEAR and antialiased edges)
g.setColor(Color.WHITE);
g.drawString("Bilinear, Antialiased", 80, 205);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setPaint(new TexturePaint(srcImage,
new Rectangle2D.Float(0, 0, srcImage.getWidth(), srcImage.getHeight())));
xform.setToIdentity();
xform.translate(15, 190);
xform.rotate(Math.PI/8, srcImage.getWidth()/2, srcImage.getHeight()/2);
g.transform(xform);
g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight());
Here's the resulting screenshot showing the different options:
Note that there will likely be some performance hit from transforming the image in this manner, but if quality is of utmost importance (as it was in this developer's case), then the cost is probably acceptable. Hope this helps!
In my ears: The Fall, "Perverted By Language"
In my eyes: Yoshio Tsuchiya, "The Fine Art Of Japanese Food Arrangement"
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
It would be nice to have some performance analysis on small, medium and large images for these three approaches...
Posted by: kirillcool on March 10, 2007 at 09:45 PM
-
Hey Chris, great stuff as usual. What are the chances that this AA edges image rotation thing get enabled by default (if you've set the rendering hint) on the Sun Java2D implementation?
Also, I'd like to put my vote that the defaults for Java2D gets pumped up a notch for 7.0. Today you'd expect AA and S-AA just about anything as jagged edges looks so 1990. Maybe this can be realized with a Compatibility API as explained here: http://www.javalobby.org/java/forums/t88549.html
Posted by: mikaelgrev on March 11, 2007 at 01:13 AM
-
It's a puzzle as to why Apple is moving away from the Quartz renderer. It's much faster and looks better. They do say that it isn't a pixel perfect copy of the Sun renderer, so maybe that's why, but you can set the default to the Quartz renderer if you need it.
As mikaelgrev notes, anything without AA looks so 90s (or from my perspective, looks so Windows.)
Posted by: goron on March 11, 2007 at 04:43 AM
-
@kirill: I'll leave that as an exercise for the reader :)
@mikael: Antialiasing isn't a magic bullet for visual quality. I'd personally prefer to continue to leave it off by default and let developers opt-in for those situations where it makes most sense. As for enabling by default the antialiased edge behavior discussed above, it's really not what the KEY_ANTIALIASING rendering hint was created for (it was intended for geometry only, not image rendering; another reason why the Apple behavior is a bit surprising). I suppose we could consider adding another hint to control this behavior, but it's not high on our list at the moment. Feel free to file an RFE.
@goron: Developers today are free to choose AA when it makes sense (and it's already very easy to enable/disable), so I don't think changing the default at this point would automatically bring apps into the new century; it goes way beyond AA to make an app look good. I don't speak for Apple, but their Quartz renderer is terrible at aliased rendering performance, which is one of the primary reasons they (used to) enable the Quartz renderer and AA by default. AA performance is something we (Sun) are working on, for both the hardware and software pipelines, and Apple should benefit from this going forward as well.
Thanks, Chris.
Posted by: campbell on March 12, 2007 at 08:55 AM
-
Chris, I think KEY_ANTIALIASING fits nicely with AA on the edges in the context of drawing an image.
You are very good at what you do (this) but think about all the other developers that can't make rotated images look good, and changes that they will find this article are slim.
If Java is ever going to make it on the desktop these kind of things needs to be fairly simple to do, if not enabled by default.
Posted by: mikaelgrev on March 12, 2007 at 11:06 AM
-
Nice trick.
I'm getting the same results by
- creating a new argb buffered image of w+4, h+4
- clearing it with 0x00000000
- copying the original image in the center of the new image (x=2,y=2 upper left corner)
- setting the transform in the dest graphics.
- drawing the new image with antialiasing on.
this approach is quite fast, but you need an extra image (nice to cache it somewhere, i.e. prepare the images before using them).
cheers!
Posted by: mikofclassx on March 13, 2007 at 01:40 AM
-
@mikofclassx: Thanks, Mik. That was actually my first suggestion when asked about it at Desktop Matters, but then I forgot to include it in favor of that other TexturePaint "trick".
@mikael: I'm not disagreeing with you; all I'm saying is that it's not a super high priority right now, but we will try our best to get around to it. It all begins with filing an RFE.
Thanks, Chris.
Posted by: campbell on March 13, 2007 at 10:04 AM
-
> It all begins with filing an RFE.
And break my new years resolution of 2005 to never touch that crappy tool again? ;)
Posted by: mikaelgrev on March 13, 2007 at 03:13 PM
-
Chris--
Saw your "Soft Clipping" article and could really use help with the *opposite* trick.
I'm clipping to a polygon and then drawing a block-like image, repeatedly, to create a pile of blocks. I *want* hard clipping, but can't seem to get it on OS X 10.4, under Java 1.4.2.
It works fine in OS X 10.3 under Java 1.4.2, and in Windows, but in 10.4 the blocks' edges are all composited with the background. RenderingHints.VALUE_ANTIALIAS_OFF has no effect. Any ideas?
Posted by: blockard on March 19, 2007 at 01:52 AM
-
Hey Chris. Question for you related to this. Is there any difference between:
g.transform(xform);
g.drawImage(img, 0, 0, null);
and
g.drawImage(img, xform, null);
from either a performance or quality standpoint, assuming the Graphics has a non-identity transform to begin with. do they render exactly the same way?
Also, say we have just a scale & translate transform that causes an image to land on non-integer pixel co-ordinates, I assume it just rounds them off, and then does the interpolation. Could an AA-Edges hint provide sub-pixel positioning accuracy for images?
Posted by: benloud on March 27, 2007 at 07:08 PM
-
@benloud: No, there should be no difference between the two. As for non-integral translations, we do respect this case as of JDK 5 (see 4916948), which means if you do something like:
g2.translate(10.7, 32.35);
g2.setRenderingHint(... BILINEAR);
g2.drawImage(img, 0, 0, null);
we will use sub-pixel positioning (we do not simply round in this case). Providing antialiased edges for arbitrary transforms is a separate issue, and is not something we do today, but perhaps in the future.
Thanks, Chris.
Posted by: campbell on March 28, 2007 at 10:48 AM
-
Any idea how to anti-alias image edges when doing PerspectiveTransform warps in JAI (instead of the AffineTransformations shown here)?
"
WarpPerspective warp = new WarpPerspective(myPerspectiveTransform);
ParameterBlock pb = new ParameterBlock();
pb.addSource(imageRect.image);
pb.add(warp);
pb.add(Interpolation.getInstance(Interpolation.INTERP_BILINEAR));
RenderedOp renderedOp = JAI.create("warp", pb, renderingHintsWithAAOn);
RenderedImage rendered = renderedOp.createInstance();
g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
g.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
g.drawRenderedImage(rendered, new AffineTransformation());
"
... does not seem to do it, nor does the TexturePaint trick above.
Posted by: chrisfjones on March 12, 2008 at 03:14 PM
-
I ended up using the technique described above by mikofclassx and it works great!
Posted by: chrisfjones on March 12, 2008 at 04:21 PM
-
Electronic Cigarette-china travel-lapel pin-Plasma LCD StandWholesale Electronicsmp4 playeriPhone Skin |mp4 speakers |mp4 watch |mp4 earphones |Portable mp4 player |wholesale mp4 players |mp4 players |
mp4 digital player |cheap mp4 players |iPod Nano Screen Protector |iPod Touch Screen Protector |Nintendo Wii skin网上购物
Posted by: goodbye on April 18, 2008 at 12:24 AM
-
Thanks so much
Oyun
Oyun Cambazı
Oyunlar
Bedava Oyunlar
Net Gazetesi
Posted by: cicicocuk on May 27, 2008 at 04:16 PM
|