4

Is there a way to smooth the jagged edges of a transformed (translated and rotated) BufferedImage?

A zoomed in view of a test image:

Zoomed in view of the jagged edges (Note that this is not the actual BufferedImage that will be used, only for demonstrating here).

Bilinear interpolation, quality rendering and on antialiasing RenderHints have been used, but the antialiasing only appears to work for Java drawn shapes. Clearly the white background and the black edges of the shape are not blended together like the interpolation of the grey and black.

What I want can be achieved with a 1px transparent border around the image and let the interpolation do the work, but this feels redundant. Is there not a better way to do this?

Harald K
  • 26,314
  • 7
  • 65
  • 111
H3katonkheir
  • 664
  • 1
  • 7
  • 21
  • 1
    Actually, I like your 1 pixel transparent border trick as well. :-) I believe it will need quite a lot of memory (`(w+2) * (h+2) * pixelSize` bytes) for large images though. Chris Campbell's approach might be faster as well, but I haven't tested. – Harald K Mar 18 '15 at 10:16

1 Answers1

4

Yes, it's a known issue, but it's possible to work around this, using a clever trick I once found in a blog post by Chris Campbell:

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().

This is the code I use, adapted from the code in his blog:

// For multiples of 90 degrees, use the much faster drawImage approach
boolean fast = ((Math.abs(Math.toDegrees(angle)) % 90) == 0.0);

int w = source.getWidth();
int h = source.getHeight();

// Compute new width and height
double sin = Math.abs(Math.sin(angle));
double cos = Math.abs(Math.cos(angle));

int newW = (int) Math.floor(w * cos + h * sin);
int newH = (int) Math.floor(h * cos + w * sin);

// Create destination image for painting onto
BufferedImage dest = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);

// Set up transformation around center
AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0);
transform.rotate(angle, w / 2.0, h / 2.0);

Graphics2D g = dest.createGraphics();

try {
    g.transform(transform);

    if (!fast) {
        // Max quality
        g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
                           RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                           RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON);
        // Here's the trick:
        g.setPaint(new TexturePaint(source,
                                    new Rectangle2D.Float(0, 0, source.getWidth(), source.getHeight())));
        g.fillRect(0, 0, source.getWidth(), source.getHeight());
    }
    else {
        // Multiple of 90 degrees:
        g.drawImage(source, 0, 0, null);
    }
}
finally {
    g.dispose();
}
Harald K
  • 26,314
  • 7
  • 65
  • 111