views:

732

answers:

2

I need to draw a line using java.awt.Graphis, but only the portion of the line that lies outside a rectangle should be rendered.

Is it possible to use the Graphics clipping support?

Or do I need to calculate the intersection and clip the line myself?

+2  A: 

You can do this with an AWT clip. You'll need to know the bounds of the rectangle you want to exclude, and the outer bounds of your drawing area.

The following demo code opens a frame and displays a single panel in it. The panel's paint method sets up an example clip which looks like a rectangle with a rectangular hole in the middle, when in fact it's a polygon that describes the area around the area we want to exclude. The clip rectangle should be composed of the bounds of the excluded rectangle, and the outer edge of the drawing area, but I've left hard-coded values in to keep it simple and illustrate the workings better (I hope!)

+-------------------+
| clip drawing area |
+---+-----------+   |
|   | excluded  |   |
|   |   area    |   |
|   +-----------+   |
|                   |
+-------------------+

This method has the benefit over calculating the line intersection manually in that it prevents all AWT painting going into the excluded area. I don't know if that's useful to you or not.

My demo then paints a black rectangle over the whole area, and a single white diagonal line running through it, to illustrate the clip working.

public class StackOverflow extends JFrame {
    public static void main(String[] args) {
        new StackOverflow();
    }

    private StackOverflow() {
        setTitle( "Clip with a hole" );
        setSize( 320,300 );
        getContentPane().add( new ClipPanel() );
        setVisible( true );
    }
}

class ClipPanel extends JPanel {
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Polygon clip = new Polygon(
                new int[]{ 0, 100, 100,  0,  0, 20, 20, 80, 80,  0 },
                new int[]{ 0,   0,  60, 60, 20, 20, 40, 40, 20, 20 },
                10
            );
        g.setClip(clip);
        g.setColor( Color.BLACK );
        g.fillRect( 0,0,100,60 );
        g.setColor( Color.WHITE );
        g.drawLine( 0,0,100,60 );
    }
}
banjollity
Also correct. But the other is easier because uses subtract. Thanks.
tuler
Yeah Savvas Dalkitsis' answer is much better.
banjollity
+1. This answer almost works, and uses API's supported by J2ME PP. I found I had to start the shape at 0,20 to create an enclosed polygon. The complete working code is in my question: http://stackoverflow.com/questions/1273688/is-there-any-way-to-have-an-inverted-clip-region-for-painting-in-java
Software Monkey
@Banjolity; if you want to put an answer on my question which refers to this answer, I will accept it as correct - I think you should get the credit.
Software Monkey
+4  A: 

You need to use the Area class. This example will demonstrate how to do what you ask:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;

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


public class Test extends JPanel {

    public static void main(String[] args) {
     JFrame f = new JFrame();
     Test t = new Test();
     f.getContentPane().setLayout(new BorderLayout());
     f.getContentPane().add(t,BorderLayout.CENTER);
     f.pack();
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     f.setVisible(true);
    }

    public Test() {
     setPreferredSize(new Dimension(300, 300));
    }

    public void paintComponent(Graphics g) {
     Graphics2D g2 = (Graphics2D)g.create();
     Rectangle2D rectangleNotToDrawIn = new Rectangle2D.Double(100, 100, 20, 30);
     Area outside = calculateRectOutside(rectangleNotToDrawIn);
     g2.setPaint(Color.white);
     g2.fillRect(0, 0, getWidth(), getHeight());
     g2.setPaint(Color.black);
     g2.setClip(outside);
     g2.drawLine(0, 0, getWidth(), getHeight());

    }


    private Area calculateRectOutside(Rectangle2D r) {
     Area outside = new Area(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
     outside.subtract(new Area(r));
     return outside;
    }

}
Savvas Dalkitsis
Thanks, perfect!
tuler
+1 - Much better than mine :)
banjollity
The Area method is better in terms of readability and extensibility (you can create any shape and clip to its outside) but i think it might have some performance issues (the Area class is a bit over the top maybe). You should benchmark it if you need to draw on the clip many times and see if the other method is faster.
Savvas Dalkitsis
Yes, I'm considering performance, as the operation is done on mouse move events. But it's good so far.I may optimize it even more because I need to clip only when the end of the line is inside the rectangle, so I don't need to clip the whole outside of the rectangle, only two sides of it at most ("right and bottom" or "right and top" or "just right", etc).Thanks again
tuler
This is a great answer for J2SE; for J2ME the other answer by banjollity works.
Software Monkey