Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Graphics2D drawImage() and clip(): how to apply antialiasing?

Tags:

java

The BufferedImage drawn by the drawImage and clip method of Java Graphics2D have jagged edges, how to apply antialiasing?

enter image description here

The code:

BufferedImage img = ImageIO.read(new File("D:\\Pictures\\U\\U\\3306231465660486.jpg"));

JFrame frame = new JFrame();
frame.add(new JPanel() {
    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setColor(Color.RED);
        g2d.drawLine(10, 10, 300, 100);


        g2d.translate(50, 200);
        g2d.rotate(Math.toRadians(30), getWidth() / 2.0, getHeight() / 2.0);
        g2d.drawImage(img, 0, 0, this);
        g2d.clip(new Rectangle(-110, 110, 80, 110));
        g2d.fill(new Rectangle(-100, 100, 100, 100));
    }
});

frame.setSize(600, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
like image 762
Yii. Guxing Avatar asked Oct 15 '18 14:10

Yii. Guxing


2 Answers

For the image, it is sufficient to paint the image into another image that is 2 pixels larger, and then draw the resulting image with bilinear interpolation. So you can just pass your image through a method like this one:

private static BufferedImage addBorder(BufferedImage image)
{
    BufferedImage result = new BufferedImage(
        image.getWidth() + 2, image.getHeight() + 2, 
        BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = result.createGraphics();
    g.drawImage(image, 1, 1, null);
    g.dispose();
    return result;
}

The result will be this:

RotatedImageBorder

Here is the MCVE, including the line that sets the ..._INTERPOLATION_BILINEAR rendering hint:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ImageBorderAntialiasing
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui()
    {
        //BufferedImage img = loadUnchecked("7bI1Y.jpg");
        BufferedImage img = addBorder(loadUnchecked("7bI1Y.jpg"));

        JFrame frame = new JFrame();
        frame.add(new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

                g2d.setRenderingHint(
                    RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);

                g2d.setColor(Color.RED);
                g2d.drawLine(10, 10, 300, 100);

                g2d.translate(50, 200);
                g2d.rotate(Math.toRadians(30), 
                    getWidth() / 2.0, getHeight() / 2.0);
                g2d.drawImage(img, 0, 0, this);
                g2d.fill(new Rectangle(-100, 100, 100, 100));
            }
        });

        frame.setSize(600, 600);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static BufferedImage addBorder(BufferedImage image)
    {
        BufferedImage result = new BufferedImage(
            image.getWidth() + 2, image.getHeight() + 2, 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = result.createGraphics();
        g.drawImage(image, 1, 1, null);
        g.dispose();
        return result;
    }

    private static BufferedImage loadUnchecked(String fileName)
    {
        try
        {
            return ImageIO.read(new File(fileName));
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return null;
        }
    }

}

After this was answered, the question was updated to also ask about the clip method


Antialiasing the result of a clip operation may be more difficult. The clip operation is very hard "by nature" (and I assume that it eventually will be handled by something like a Stencil Buffer in hardware).

One approach to solve this could be do do the clipping manually. So instead of doing

g2d.clip(new Rectangle(-110, 110, 80, 110));
g2d.fill(new Rectangle(-100, 100, 100, 100));

you could do something like

Shape clip = new Rectangle(-110, 110, 80, 110);
Shape rectangleA = new Rectangle(-100, 100, 100, 100);  
g2d.fill(clip(clip, rectangleA));            

where the clip method is implemented to to manually compute the intersection of the shapes.

Note: Computing the intersection of two shapes can be rather expensive. If this becomes an issue, one might have to revise the approach. But on another note: I've heavily been doing Swing programming for ~20 years now, and cannot remember to ever have used the Graphics2D#clip method at all....

The difference between using Graphics2D#clip and the manual clipping is shown here:

Clipper

and a closeup:

ClipperZoom

And there is the code:

(It does no longer contain the image part, because the problems are fairly unrelated...)

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ClippedDrawingAntialiasing
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui()
    {
        JFrame frame = new JFrame();
        frame.getContentPane().setLayout(new GridLayout(1,2));
        frame.getContentPane().add(new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

                g2d.setColor(Color.RED);
                g2d.drawLine(10, 10, 300, 100);

                g2d.translate(50, 200);
                g2d.rotate(Math.toRadians(30), 
                    getWidth() / 2.0, getHeight() / 2.0);

                g2d.clip(new Rectangle(-110, 110, 80, 110));

                g2d.fill(new Rectangle(-100, 100, 100, 100));            

                g2d.setColor(Color.BLUE);
                g2d.fill(new Rectangle(-60, 120, 60, 170));            
            }
        });
        frame.getContentPane().add(new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

                g2d.setColor(Color.RED);
                g2d.drawLine(10, 10, 300, 100);

                g2d.translate(50, 200);
                g2d.rotate(Math.toRadians(30), 
                    getWidth() / 2.0, getHeight() / 2.0);

                Clipper clipper = 
                    new Clipper(new Rectangle(-110, 110, 80, 110));

                g2d.fill(clipper.clip(new Rectangle(-100, 100, 100, 100)));            

                g2d.setColor(Color.BLUE);
                g2d.fill(clipper.clip(new Rectangle(-60, 120, 60, 170)));            
            }
        });

        frame.setSize(1200, 600);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static class Clipper 
    {
        private final Shape shape;
        Clipper(Shape shape)
        {
            this.shape = shape;
        }

        Shape clip(Shape other)
        {
            Area a = new Area(shape);
            a.intersect(new Area(other));
            return a;
        }
    }

}
like image 195
Marco13 Avatar answered Sep 18 '22 04:09

Marco13


One solution is to blur the border of the image once you load it. You should also use the RenderingHints.KEY_RENDERING with RenderingHints.VALUE_RENDER_QUALITY.

This is the final result:

enter image description here

The full code is available below. Note that is uses a blur method described by Marco13 in this answer: https://stackoverflow.com/a/22744303/4289700.

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MultipleGradientPaint;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Main {

    private static BufferedImage blurImageBorder(BufferedImage input, double border) {
        int w = input.getWidth();
        int h = input.getHeight();
        BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = output.createGraphics();
        g.drawImage(input, 0, 0, null);

        g.setComposite(AlphaComposite.DstOut);
        Color c0 = new Color(0x000000FF);

        // Left
        g.setPaint(new GradientPaint(new Point2D.Double(0, border), c0, new Point2D.Double(border, border), c0));
        g.fill(new Rectangle2D.Double(0, border, border, h- border - border));

        // Right
        g.setPaint(new GradientPaint(new Point2D.Double(w - border, border), c0, new Point2D.Double(w, border), c0));
        g.fill(new Rectangle2D.Double(w- border, border, border, h- border - border));

        // Top
        g.setPaint(new GradientPaint(new Point2D.Double(border, 0), c0, new Point2D.Double(border, border), c0));
        g.fill(new Rectangle2D.Double(border, 0, w - border - border, border));

        // Bottom
        g.setPaint(new GradientPaint(new Point2D.Double(border, h - border), c0, new Point2D.Double(border, h), c0));
        g.fill(new Rectangle2D.Double(border, h - border, w - border - border, border));

        final float[] floatArray = new float[]{ 0, 1 };
        final Color[] colorArray = new Color[]{ c0, c0 };

        // Top Left
        g.setPaint(new RadialGradientPaint(new Rectangle2D.Double(0, 0, border + border, border + border),
                floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
        g.fill(new Rectangle2D.Double(0, 0, border, border));

        // Top Right
        g.setPaint(new RadialGradientPaint(
                new Rectangle2D.Double(w - border - border, 0, border + border, border + border),
                floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
        g.fill(new Rectangle2D.Double(w - border, 0, border, border));

        // Bottom Left
        g.setPaint(new RadialGradientPaint(
                new Rectangle2D.Double(0, h - border - border, border + border, border + border),
                floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
        g.fill(new Rectangle2D.Double(0, h - border, border, border));

        // Bottom Right
        g.setPaint(new RadialGradientPaint(
                new Rectangle2D.Double(w - border - border, h - border - border, border + border, border + border),
                floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
        g.fill(new Rectangle2D.Double(w - border, h - border, border, border));

        g.dispose();

        return output;
    }

    public static void main(String[] args) throws IOException {
        BufferedImage raw = ImageIO.read(new File("/path/to/picture.jpg"));
        BufferedImage img = blurImageBorder(raw, 1);

        JFrame frame = new JFrame();
        frame.add(new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                Graphics2D g2d = (Graphics2D) g;

                g2d.setColor(Color.RED);
                g2d.drawLine(10, 10, 300, 100);

                g2d.translate(50, 200);
                g2d.rotate(Math.toRadians(30), getWidth() / 2.0, getHeight() / 2.0);

                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

                g2d.drawImage(img, 0, 0, this);
                g2d.fill(new Rectangle(-100, 100, 100, 100));
            }
        });

        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(600, 600);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }    
}
like image 28
HugoTeixeira Avatar answered Sep 17 '22 04:09

HugoTeixeira