0

I am trying to clip my Graphics2D canvas using RoundRectangle2D.Double, but the clipping is very jagged and not smooth. I have the following code to anti-alias:

Graphics2D g = (Graphics2D)graphics;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

I know it is working because when I draw a RoundRectangle2D.Double using Graphics2D.fill() the smoothing is fine. How do I make the clipping smooth?

Note: I am aware of this post, but this pertains to JPanels and images, but I am not dealing with either of those. I am just trying to smoothly clip a section of the drawing area.

Thanks in advance for any help.

Example.java

import java.awt.*;
import java.awt.geom.RoundRectangle2D;

import javax.swing.*;

public class Example extends JPanel {
    public Example() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocation(10, 10);
        setPreferredSize(new Dimension(400, 400));
        setBackground(Color.BLACK);
        Container container = frame.getContentPane();
        container.add(this);
        frame.pack();
        frame.setVisible(true);
    }

    public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        Graphics2D g = (Graphics2D)graphics;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        RoundRectangle2D clippingArea = new RoundRectangle2D.Double(50, 50, getWidth() - 100, getHeight() -100, 40, 40);
        g.setClip(clippingArea);

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(Color.BLACK);
        String s = "area for me to draw on where";
        g.drawString(s, getWidth()/2 - g.getFontMetrics().stringWidth(s)/2, getHeight()/2 - g.getFontMetrics().getHeight());
        s = "the roundrectangle should be anti-aliased";
        g.drawString(s, getWidth()/2 - g.getFontMetrics().stringWidth(s)/2, getHeight()/2);
    }

    public static void main(String[] args) {
        new Example();
    }
}
  • The term you're after is "soft clipping", `setClip` DOES NOT provide support antialiasing, which is generally why I don't use and instead, lean towards masking techniques instead, while more complicated, they allow for soft clipping. [For example](https://stackoverflow.com/questions/25394366/custom-round-skin-gui/25395267#25395267) and [example](https://stackoverflow.com/questions/31423130/how-to-make-circle-image-label-in-java/31424601#31424601) and [example](https://stackoverflow.com/questions/17644900/how-to-add-imagepanel-in-jpanel-inside-jframe/17645036#17645036) – MadProgrammer Feb 18 '18 at 07:00
  • *"but this pertains to JPanels and images, but I am not dealing with either of those"* - Then where are you getting the `Graphics` context from? – MadProgrammer Feb 18 '18 at 07:03
  • [A more advanced concept](https://stackoverflow.com/questions/34423155/applying-colour-to-the-transparent-area-of-a-jbutton-image-but-not-that-of-its/34425760#34425760) – MadProgrammer Feb 18 '18 at 07:05
  • @MadProgrammer I took a look at those examples, but I am still unsure how to apply them. And I meant I wasn't dealing with `JPanels` or `Images` in the sense that I'm not trying to clip or change the shape of panels or images. The only panel I'm using is a single panel that takes the space of the `JFrame` in order for me to draw on. So I don't understand how I would use masking (from my understanding is achieved by changing the shapes of `JPanels` in addition to drawing around them?) in only a `Graphics` context where I'm not using any additional `JPanels` or `Images` from. – soggytoast Feb 18 '18 at 10:07
  • Here's the problem, the question lacks context, there's no way that we can surmise from you question how you are obtaining information, how you are manipulating. I've now provided no less then 4 runnable examples which demonstrate a "soft clipping" approach which would solve the core issue. The problem now is, you need to figure out how best to incorporate those ideas into your problem - The basic answer to your question is "use a soft clipping approach" - beyond that, it's impossible for us to provide you with any additional support – MadProgrammer Feb 18 '18 at 20:02
  • @MadProgrammer what context would you like me to provide? I've been looking into soft clipping but I still don't know how I would use it for just a single `Graphics` and single `JPanel`. – soggytoast Feb 18 '18 at 23:59
  • A [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) which demonstrated the issue would be a good start – MadProgrammer Feb 19 '18 at 00:02
  • @MadProgrammer added – soggytoast Feb 19 '18 at 21:18

2 Answers2

0

So, based on your example, the "simple" answer is, cheat.

Soft clipping

RoundRectangle2D clippingArea = new RoundRectangle2D.Double(50, 50, getWidth() - 100, getHeight() - 100, 40, 40);
// Make the clipping space rectangular
g.setClip(clippingArea.getBounds2D());

g.setColor(Color.WHITE);
g.fill(clippingArea);
g.setColor(Color.BLACK);

Basically all this does it makes the clipping area rectangular and then fills it using the RoundRectangle2D shape.

Because most of your issues revolve around the edges, this eliminates the core problem, the round edges.

There are different ways you "might" solve the issue, using intermediate BufferedImages is one (so you can constraint the content to the specified area, but still apply "soft clipping"), but for this context, this would be the immediately simplest solution available to you

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Wouldn't this create a problem around the corners though? A small amount of things would be drawn that should've been clipped. – soggytoast Feb 19 '18 at 21:46
  • @soggytoast This would depend on your requirements, if you are clever, you will render WITHIN the confines of the rounded edges (or at least I would). The alternative, as I stated, is to use another `BufferedImage`, which would act as the primary rendering surface which you would clip, you'd then paint the background (as I have in the question) behind it, either compounding multiple buffered images or simply within the `paintComponent` method using a combination of `fill` and `drawImage` – MadProgrammer Feb 19 '18 at 21:49
0

There are two ideas for the problem you raised.

The first idea
Set the clip with the setClip() method and draw in a specific range using the methods of the Graphics class. This was an idea that you also proposed.

The second idea
Create a BufferedImage object that has a graphicsObj field of type Graphics. Set a Rendering Hint for the Graphics object like below:

graphicsObj.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

Draw an oval with the fillOval() method. Then, using the setComposite() method, set an AlphaComponent object for the Graphics object as follows:

AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_IN);
graphicsObj.setComposite(alphaComposite);

Then draw something using a method like drawImage or any other method. this BufferedImage is what you want. Just don't forget to use the following Rendering Hint wherever you want to draw the BufferedImage:

setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

Example:

public void paint(Graphics g) {
            super.paint(g);

            BufferedImage bufferedImage = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);

            Graphics2D bg2 = bufferedImage.createGraphics();

            bg2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            bg2.fillOval(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());

            AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_IN);
            bg2.setComposite(alphaComposite);

            Image img = null;
            
            try {
                img = ImageIO.read(new File("some-image.png"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            if (img == null) {
                // TODO something
            } else {
                bg2.drawImage(img, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), null);
            }

            Graphics2D g2 = (Graphics2D) g;
            
            g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

                g2.drawImage(bufferedImage, 0, 0, getWidth(), getHeight(), null);
        }
    }

The second idea result: The second idea result

mort kh
  • 1
  • 1