Related
class MyPanel extends JPanel implements Observer, MouseMotionListener, MouseListener {
private MyModel model;
private View view;
private String mode;
private Rectangle rectangle;
private Square square;
public MyPanel(MyModel model, View view) {
this.setBackground(Color.black);
this.setPreferredSize(new Dimension(300, 300));
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.model = model;
this.model.addObserver(this);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
ArrayList<Rectangle> rectangles = this.model.getRectangles();
for (Rectangle r : getRectangles()) {
int width = Math.abs(r.getStartPoint().getX() - r.getEndPoint().getX());
int height = Math.abs(r.getStartPoint().getY() - r.getEndPoint().getY());
}
ArrayList<Square> squares = this.model.getSquares();
for (Square sqr : getSquares()) {
int xPosition = Math.min(sqr.getStartPoint().getX(), sqr.getEndPoint().getX());
int yPosition = Math.min(sqr.getStartPoint().getY(), sqr.getEndPoint().getY());
int width = Math.abs(sqr.getStartPoint().getX() - sqr.getEndPoint().getX());
int height = Math.abs(sqr.getStartPoint().getY() - sqr.getEndPoint().getY());
}
g2d.dispose();
}
public void update(Observable o, Object arg) {
this.repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
if (this.mode.equals("Rectangle")) {
this.rectangle.setEndPoint(e.getX(), e.getY());
this.model.addRectangle(this.rectangle);
}
else if (this.mode.equals("Square")) {
// What code should I add here?
this.model.addSquare(this.square);
}
}
// MouseListener below
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
if (this.mode.equals("Rectangle")) {
this.rectangle = new Rectangle(e.getX(), e.getY());
}
else if (this.mode.equals("Square")) {
this.square = new Square(e.getX(), e.getY());
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (this.mode.equals("Rectangle")) {
this.rectangle.setEndPoint(e.getX(), e.getY());
this.model.addRectangle(this.rectangle);
this.rectangle = null;
}
else if (this.mode.equals("Square")) {
this.model.addSquare(this.square);
this.square = null;
}
}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
}
The user chooses a mode, rectangle or square. Then they can draw a square or a rectangle with their mouse (live feedback is shown). Here is my drawing panel class. I was successfully able to implement the rectangle mode. The user can draw a rectangle and as they move their mouse, the rectangle is shown in mid construction. I want to do the same for the square mode. For some reason, I'm having a hard time doing this. How would I show a perfect square in mid construction when the user is moving their mouse and how would I draw it once released? What code should I add to my paintComponent method, mouseDragged, mousePressed and mouseReleased method to do this? It was easy for a rectangle because there was no constraint but I'm not sure how to do it for a square with my current implementation.
int width = Math.abs(r.getStartPoint().getX() - r.getEndPoint().getX());
int height = Math.abs(r.getStartPoint().getY() - r.getEndPoint().getY());
I would guess that the "size" of the square would be the maximum of the above two values.
Then I would think you would just use:
r.drawStyle(g2d, xPosition, yPosition, size, size);
I am trying to create a simple drawing application using swing and java2D.
The aim is to achieve a smooth zoom, always relative to the mouse cursor point.
The application consists of two classes: CanvasPane and Canvas.
CanvasPane class is a simple container with BorderLayout and JScrollPane in the center.
Canvas class is a drawing component added to a JScrollPane in CanvasPane.
Canvas draws a simple rectangle[800x600], and dispatches it's mouse events (wheel and drag).
When rectangle is smaller then visibleRect, canvas size is equal to visibleRect and I call AffineTransform.translate to follow mouse
(thanks to this question)
When rectangle grows bigger then canvas, canvas size grows too and became scrollable. Then I call scrollRectToVisible on it to follow mouse.
The question is:
How to use translate and scrollRectToVisible together, to smooth scale without graphics jumps. May be there is some known decision?
What I want is perfectly realized in YED Graph Editor, but it's code is closed.
I have tried with many examples but there were only zoom or scroll without complex usage of them.
Full code follows.
Class CanvasPane:
import javax.swing.*;
import java.awt.*;
public class CanvasPane extends JPanel {
private Canvas canvas;
public CanvasPane(boolean isDoubleBuffered) {
super(isDoubleBuffered);
setLayout(new BorderLayout());
canvas = new Canvas(1.0);
JScrollPane pane = new JScrollPane(canvas);
pane.getViewport().setBackground(Color.DARK_GRAY);
add(pane, BorderLayout.CENTER);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Test Graphics");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new CanvasPane(true), BorderLayout.CENTER);
frame.setSize(new Dimension(1000, 800));
frame.setVisible(true);
}
}
Class Canvas:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
private double zoom = 1.0;
public static final double SCALE_STEP = 0.1d;
private Dimension initialSize;
private Point origin;
private double previousZoom = zoom;
AffineTransform tx = new AffineTransform();
private double scrollX = 0d;
private double scrollY = 0d;
private Rectangle2D rect = new Rectangle2D.Double(0,0, 800, 600);
public Canvas(double zoom) {
this.zoom = zoom;
addMouseWheelListener(this);
addMouseMotionListener(this);
addMouseListener(this);
setAutoscrolls(true);
}
public Dimension getInitialSize() {
return initialSize;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Graphics2D g2d = (Graphics2D) g.create();
g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.transform(tx);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(rect);
g2d.setColor(Color.GRAY);
g2d.setStroke(new BasicStroke(5.0f));
g2d.draw(rect);
g2d.dispose();
}
#Override
public void setSize(Dimension size) {
super.setSize(size);
if (initialSize == null) {
this.initialSize = size;
}
}
#Override
public void setPreferredSize(Dimension preferredSize) {
super.setPreferredSize(preferredSize);
if (initialSize == null) {
this.initialSize = preferredSize;
}
}
public void mouseWheelMoved(MouseWheelEvent e) {
double zoomFactor = - SCALE_STEP*e.getPreciseWheelRotation()*zoom;
zoom = Math.abs(zoom + zoomFactor);
//Here we calculate new size of canvas relative to zoom.
Rectangle realView = getVisibleRect();
Dimension d = new Dimension(
(int)(initialSize.width*zoom),
(int)(initialSize.height*zoom));
// if (d.getWidth() >= realView.getWidth() && d.getHeight() >= realView.getHeight()) {
setPreferredSize(d);
setSize(d);
validate();
followMouseOrCenter(e);
// }
//Here we calculate transform for the canvas graphics to scale relative to mouse
translate(e);
repaint();
previousZoom = zoom;
}
private void translate(MouseWheelEvent e) {
Rectangle realView = getVisibleRect();
Point2D p1 = e.getPoint();
Point2D p2 = null;
try {
p2 = tx.inverseTransform(p1, null);
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
return;
}
Dimension d = getSize();
if (d.getWidth() <= realView.getWidth() && d.getHeight() <= realView.getHeight()) {
//Zooming and translating relative to the mouse position
tx.setToIdentity();
tx.translate(p1.getX(), p1.getY());
tx.scale(zoom, zoom);
tx.translate(-p2.getX(), -p2.getY());
} else {
//Only zooming, translate is not needed because scrollRectToVisible works;
tx.setToIdentity();
tx.scale(zoom, zoom);
}
// What to do next?
// The only translation works when rect is smaller then canvas size.
// Rect bigger then canvas must be scrollable, but relative to mouse position as before.
// But when the rect gets bigger than canvas, there is a terrible jump of a graphics.
//So there must be some combination of translation ans scroll to achieve a smooth scale.
//... brain explosion(((
}
public void followMouseOrCenter(MouseWheelEvent e) {
Point2D point = e.getPoint();
Rectangle visibleRect = getVisibleRect();
scrollX = point.getX()/previousZoom*zoom - (point.getX()-visibleRect.getX());
scrollY = point.getY()/previousZoom*zoom - (point.getY()-visibleRect.getY());
visibleRect.setRect(scrollX, scrollY, visibleRect.getWidth(), visibleRect.getHeight());
scrollRectToVisible(visibleRect);
}
public void mouseDragged(MouseEvent e) {
if (origin != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
Rectangle view = getVisibleRect();
Dimension size = getSize();
view.x += deltaX;
view.y += deltaY;
scrollRectToVisible(view);
}
}
public void mouseMoved(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
}
I have finally achieved the enlightenment=)
We can simply make the canvas size much bigger than size of a drawing object, and so forget about calculating of any unintelligible transforms.
I initially make the canvas 100x bigger than drawing rectangle.
Then I zoom Graphics2D and translate zoomed graphics to the center of the canvas while painting. Next, I calculate a new visibleRect to follow mouse point and scroll to it.
When canvas became unscrollable, it's unreasonable to follow mouse because the drawing object is too small (100x smaller then its initial size), so I only center it to be always visible.
It works exactly as I needed.
So we have a working example with zoom following mouse and drag by mouse.
Code follows.
Class CanvasPane:
import javax.swing.*;
import java.awt.*;
public class CanvasPane extends JPanel {
private static Canvas canvas;
public CanvasPane(boolean isDoubleBuffered) {
super(isDoubleBuffered);
setLayout(new BorderLayout());
canvas = new Canvas(1.0);
JScrollPane pane = new JScrollPane(canvas);
pane.getViewport().setBackground(Color.DARK_GRAY);
add(pane, BorderLayout.CENTER);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Test Graphics");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new CanvasPane(true), BorderLayout.CENTER);
frame.setSize(new Dimension(1000, 800));
frame.pack();
frame.setVisible(true);
//Initial scrolling of the canvas to its center
Rectangle rect = canvas.getBounds();
Rectangle visibleRect = canvas.getVisibleRect();
double tx = (rect.getWidth() - visibleRect.getWidth())/2;
double ty = (rect.getHeight() - visibleRect.getHeight())/2;
visibleRect.setBounds((int)tx, (int)ty, visibleRect.width, visibleRect.height);
canvas.scrollRectToVisible(visibleRect);
}
}
Class Canvas:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
private double zoom = 1.0;
public static final double SCALE_STEP = 0.1d;
private Dimension initialSize;
private Point origin;
private double previousZoom = zoom;
private double scrollX = 0d;
private double scrollY = 0d;
private Rectangle2D rect = new Rectangle2D.Double(0,0, 800, 600);
private float hexSize = 3f;
public Canvas(double zoom) {
this.zoom = zoom;
addMouseWheelListener(this);
addMouseMotionListener(this);
addMouseListener(this);
setAutoscrolls(true);
//Set preferred size to be 100x bigger then drawing object
//So the canvas will be scrollable until our drawing object gets 100x smaller then its natural size.
//When the drawing object became so small, it is unnecessary to follow mouse on it,
//and we only center it on the canvas
setPreferredSize(new Dimension((int)(rect.getWidth()*100), (int)(rect.getHeight()*100)));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//Obtain a copy of graphics object without any transforms
Graphics2D g2d = (Graphics2D) g.create();
g2d.clearRect(0, 0, getWidth(), getHeight());
//Zoom graphics
g2d.scale(zoom, zoom);
//translate graphics to be always in center of the canvas
Rectangle size = getBounds();
double tx = ((size.getWidth() - rect.getWidth() * zoom) / 2) / zoom;
double ty = ((size.getHeight() - rect.getHeight() * zoom) / 2) / zoom;
g2d.translate(tx, ty);
//Draw
g2d.setColor(Color.LIGHT_GRAY);
g2d.fill(rect);
g2d.setColor(Color.DARK_GRAY);
g2d.setStroke(new BasicStroke(5.0f));
g2d.draw(rect);
//Forget all transforms
g2d.dispose();
}
#Override
public void setSize(Dimension size) {
super.setSize(size);
if (initialSize == null) {
this.initialSize = size;
}
}
#Override
public void setPreferredSize(Dimension preferredSize) {
super.setPreferredSize(preferredSize);
if (initialSize == null) {
this.initialSize = preferredSize;
}
}
public void mouseWheelMoved(MouseWheelEvent e) {
double zoomFactor = - SCALE_STEP*e.getPreciseWheelRotation()*zoom;
zoom = Math.abs(zoom + zoomFactor);
//Here we calculate new size of canvas relative to zoom.
Dimension d = new Dimension(
(int)(initialSize.width*zoom),
(int)(initialSize.height*zoom));
setPreferredSize(d);
setSize(d);
validate();
followMouseOrCenter(e.getPoint());
previousZoom = zoom;
}
public void followMouseOrCenter(Point2D point) {
Rectangle size = getBounds();
Rectangle visibleRect = getVisibleRect();
scrollX = size.getCenterX();
scrollY = size.getCenterY();
if (point != null) {
scrollX = point.getX()/previousZoom*zoom - (point.getX()-visibleRect.getX());
scrollY = point.getY()/previousZoom*zoom - (point.getY()-visibleRect.getY());
}
visibleRect.setRect(scrollX, scrollY, visibleRect.getWidth(), visibleRect.getHeight());
scrollRectToVisible(visibleRect);
}
public void mouseDragged(MouseEvent e) {
if (origin != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
Rectangle view = getVisibleRect();
view.x += deltaX;
view.y += deltaY;
scrollRectToVisible(view);
}
}
public void mouseMoved(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
}
I have a 3x3 check board-like image rendered on a JPanel which is added onto a JFrame. Then I have 9 more JPanels (1 on top of each square) and on click something needs to be drawn on the corresponding square. My problem is that it only works for the top-left square. The rest of the drawings seem to be drawn below the checkboard image. So if I comment out the part that loads the checkboard image,and click as if they were there then the drawings appear correctly. I get the same result with a layered pane. Absolute positioning is used and the coordinates seem to be correct since if I remove the checkboard image then the drawings appear where they should and the drawings do not occupy more than a square.
My code is structured as follows:
'main' class creates the frame and adds an instance of another class which extends JPanel and which also draws the checkboard image using paintComponent(Graphics g).
'main' class has also 9 instances added of a class that extends JPanel and draws something on a mouse click using paintComponent(Graphics g). Each instance is placed on top of a square
Please note that because I was going to do it with just Rectangles I named the second class Rectangles but it is rectangualar JPanels not java Rectangle instances
Code:
public class Main3
{
private JFrame frame=new JFrame("");
private Rectangles rect00=new Rectangles(0,0,129,129);
private Rectangles rect01=new Rectangles(136,0,129,129);
private Rectangles rect02=new Rectangles(268,0,129,129);
private Rectangles rect10=new Rectangles(0,136,129,129);
private Rectangles rect11=new Rectangles(134,136,129,129);
private Rectangles rect12=new Rectangles(269,137,129,129);
private Rectangles rect20=new Rectangles(0,270,129,129);
private Rectangles rect21=new Rectangles(136,269,129,129);
private Rectangles rect22=new Rectangles(269,270,129,129);
public void Display()
{
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(null);
frame.setSize(600,400);
sub inter=new sub();
inter.setLayout(null);
inter.setBounds(0,0,600,400);
inter.setSize(600,400);
rect00.setBounds(rect00.getX(),rect00.getY(),rect00.getWidth(),rect00.getHeight());
rect01.setBounds(rect01.getX(),rect01.getY(),rect01.getWidth(),rect01.getHeight());
rect02.setBounds(rect02.getX(),rect02.getY(),rect02.getWidth(),rect02.getHeight());
rect10.setBounds(rect10.getX(),rect10.getY(),rect10.getWidth(),rect10.getHeight());
rect11.setBounds(rect11.getX(),rect11.getY(),rect11.getWidth(),rect11.getHeight());
rect12.setBounds(rect12.getX(),rect12.getY(),rect12.getWidth(),rect12.getHeight());
rect20.setBounds(rect20.getX(),rect20.getY(),rect20.getWidth(),rect20.getHeight());
rect21.setBounds(rect21.getX(),rect21.getY(),rect21.getWidth(),rect21.getHeight());
rect22.setBounds(rect22.getX(),rect22.getY(),rect22.getWidth(),rect22.getHeight());
rect00.setOpaque(false);
rect01.setOpaque(false);
rect02.setOpaque(false);
rect10.setOpaque(false);
rect11.setOpaque(false);
rect12.setOpaque(false);
rect20.setOpaque(false);
rect21.setOpaque(false);
rect22.setOpaque(false);
inter.add(rect00);
inter.add(rect01);
inter.add(rect02);
inter.add(rect10);
inter.add(rect11);
inter.add(rect12);
inter.add(rect20);
inter.add(rect21);
inter.add(rect22);
frame.add(inter);
frame.setResizable(false);
frame.setVisible(true);
}
public static void main(String args[])
{
new main().Display();
}
private class sub extends JPanel
{
private BufferedImage image;
public sub ()
{
try
{
image=ImageIO.read(new File("image.jpg"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize()
{
return (new Dimension(600,400));
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
}
This is the other class
public class Rectangles extends JPanel implements MouseListener
{
private int Posx;
private int Posy;
private int width;
private int height;
private boolean selected=false;
public Rectangles(int Posx,int Posy,int width,int height)
{
this.Posx=Posx;
this.Posy=Posy;
this.width=width;
this.height=height;
this.addMouseListener(this);
}
#Override
protected void paintComponent(Graphics g)
{
if(selected==true)
{
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(new Color(250, 235, 215));
g2.drawRect(Posx,Posy,width,height);
Graphics2D g3=(Graphics2D)g;
g2.setColor(new Color(0,0,0));
g3.setStroke(new BasicStroke(20));
g3.drawLine(Posx,Posy,Posx+width,Posy+height);
g3.drawLine(Posx+width,Posy,Posx,Posy+height);
}
}
public int getX()
{
return Posx;
}
public int getY()
{
return Posy;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public void setSelected()
{
selected=true;
}
#Override
public void mouseClicked(MouseEvent arg0)
{
}
#Override
public void mouseEntered(MouseEvent arg0)
{
}
public void mouseExited(MouseEvent arg0)
{
}
#Override
public void mousePressed(MouseEvent arg0)
{
}
#Override
public void mouseReleased(MouseEvent arg0)
{
selected=true;
repaint();
}
}
1) You dont honor the components paint chain.
As per java docs for paintComponent(Graphics g):
Further, if you do not invoker super's implementation you must honour
the opaque property, that is if this component is opaque, you must
completely fill in the background in a non-opaque color. If you do not
honor the opaque property you will likely see visual artifacts.
2) super.paintComponent would in most cases be the first call in the method.
3) But there is more, your cast to Graphics2D twice, that should not be done:
Graphics2D g2 = (Graphics2D) g;
...
Graphics2D g3=(Graphics2D)g;
omit the g3 its not needed you already have casted to a Graphics2D object
4) Another problem lies here in sub class. You do this in your main code:
inter.add(rect00);
inter.add(rect01);
...
but in inter which is your variable name for the instance of sub class you only have:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
Thus it will only draw a single image no matter how many rectangles you add!
Also dont do
g2.drawLine(Posx, Posy, Posx + width, Posy + height); rather
g2.drawLine(0, 0, Posx + width, Posy + height); as the JPanel has been added at co-ordinates x and y on its container, when you draw on the JPanel we want to start at the top left i.e 0,0, changing the value would move the image further down on its conatiner
See fixed code here:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
private JFrame frame = new JFrame("");
private Rectangles rect00 = new Rectangles(0, 0, 129, 129);
private Rectangles rect01 = new Rectangles(136, 0, 129, 129);
private Rectangles rect02 = new Rectangles(268, 0, 129, 129);
private Rectangles rect10 = new Rectangles(0, 136, 129, 129);
private Rectangles rect11 = new Rectangles(134, 136, 129, 129);
private Rectangles rect12 = new Rectangles(269, 137, 129, 129);
private Rectangles rect20 = new Rectangles(0, 270, 129, 129);
private Rectangles rect21 = new Rectangles(136, 269, 129, 129);
private Rectangles rect22 = new Rectangles(269, 270, 129, 129);
public void Display() {
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(null);
frame.setSize(600, 400);
sub inter = new sub();
inter.setLayout(null);
inter.setBounds(0, 0, 600, 400);
inter.setSize(600, 400);
rect00.setBounds(rect00.getX(), rect00.getY(), rect00.getWidth(), rect00.getHeight());
rect01.setBounds(rect01.getX(), rect01.getY(), rect01.getWidth(), rect01.getHeight());
rect02.setBounds(rect02.getX(), rect02.getY(), rect02.getWidth(), rect02.getHeight());
rect10.setBounds(rect10.getX(), rect10.getY(), rect10.getWidth(), rect10.getHeight());
rect11.setBounds(rect11.getX(), rect11.getY(), rect11.getWidth(), rect11.getHeight());
rect12.setBounds(rect12.getX(), rect12.getY(), rect12.getWidth(), rect12.getHeight());
rect20.setBounds(rect20.getX(), rect20.getY(), rect20.getWidth(), rect20.getHeight());
rect21.setBounds(rect21.getX(), rect21.getY(), rect21.getWidth(), rect21.getHeight());
rect22.setBounds(rect22.getX(), rect22.getY(), rect22.getWidth(), rect22.getHeight());
rect00.setOpaque(false);
rect01.setOpaque(false);
rect02.setOpaque(false);
rect10.setOpaque(false);
rect11.setOpaque(false);
rect12.setOpaque(false);
rect20.setOpaque(false);
rect21.setOpaque(false);
rect22.setOpaque(false);
inter.addPanel(rect00);
inter.addPanel(rect01);
inter.addPanel(rect02);
inter.addPanel(rect10);
inter.addPanel(rect11);
inter.addPanel(rect12);
inter.addPanel(rect20);
inter.addPanel(rect21);
inter.addPanel(rect22);
frame.add(inter);
frame.setResizable(false);
frame.setVisible(true);
}
public static void main(String args[]) {
new Test().Display();
}
private class sub extends JPanel {
private BufferedImage image;
private ArrayList<Rectangles> rects = new ArrayList<>();
public sub() {
try {
image = ImageIO.read(new File("c:/image.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(600, 400));
}
void addPanel(Rectangles r) {
rects.add(r);
add(r);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Rectangles r : rects) {
g.drawImage(image, r.getX(), r.getY(), null);
}
}
}
}
class Rectangles extends JPanel implements MouseListener {
private int Posx;
private int Posy;
private int width;
private int height;
private boolean selected = false;
public Rectangles(int Posx, int Posy, int width, int height) {
this.Posx = Posx;
this.Posy = Posy;
this.width = width;
this.height = height;
this.addMouseListener(this);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (selected == true) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(250, 235, 215));
g2.drawRect(0,0, width, height);
g2.setColor(new Color(0, 0, 0));
g2.setStroke(new BasicStroke(20));
g2.drawLine(0,0, width,height);
g2.drawLine(getWidth(),0, 0, height);
}
}
public int getX() {
return Posx;
}
public int getY() {
return Posy;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void setSelected() {
selected = true;
}
#Override
public void mouseClicked(MouseEvent arg0) {
}
#Override
public void mouseEntered(MouseEvent arg0) {
}
public void mouseExited(MouseEvent arg0) {
}
#Override
public void mousePressed(MouseEvent arg0) {
}
#Override
public void mouseReleased(MouseEvent arg0) {
selected = true;
repaint();
}
}
A few other pointers:
Dont use Absolute/Null layout. A GridLayout or GridBagLayout would suit your needs fine. (see here for more.)
Dont do JFrame#setSize(...); rather use Correct LayoutManager and call pack() on JFrame before setting it visible.
Dont call setSize on your Rectangles instances, simply override getPreferredSize like you did with sub panel??
No need for implementing MouseListener, just use MouseAdapter thus giving you the freedom to choose which methods to override and not just override all.
Have a read on Concurrency in Swing especailly Event-Dispatch-Thread
I'm making an application that has a drawing board where you draw with your mouse, it draws ontop of an Label in a BUfferedImage. What I'm trying to implement right now is an eraser, the problem is I cannot find anywhere help to make an eraser to clearRect() to an alpha background. (I cannot have a defined color background since the user can change the background to any image he wants). To sum up:
How can you erase/overwrite Graphics2D pixels with alpha pixels? The way i found was with clearRect but you need to specify a background color.
The following is my DrawBoard class which constains everything to draw.
public class DrawBoard extends JPanel implements MouseListener, MouseMotionListener{
public JLabel status;
private JLabel imgLabel; // this is where the drawing happens
public Point pstart, pfinish;
private List<Point> points = new ArrayList<Point>();
private List<BufferedImage> lines = new ArrayList<BufferedImage>();
private static final int BI_WIDTH = 1024;
private static final int BI_HEIGHT = 800;
private static int STROKESIZE = 7;
private BufferedImage bImage = new BufferedImage(BI_WIDTH, BI_HEIGHT,
BufferedImage.TYPE_INT_ARGB);
public Color currentColor;
public static boolean eraser = false;
private int xX1, yY1;
public DrawBoard(){
Graphics2D g2d = bImage.createGraphics();
g2d.dispose();
Dimension size = getPreferredSize();
size.setSize(1024,800); //w, h
setPreferredSize(size);
//status = new JLabel("default");
//add(status, BorderLayout.SOUTH);
addMouseListener(this);
addMouseMotionListener(this);
imgLabel = new JLabel(new ImageIcon(bImage)) {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
paintInLabel(g);
}
};
imgLabel.setOpaque(false);
setOpaque(false);
add(imgLabel, BorderLayout.CENTER);
}
private void paintInLabel(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(getColor()); // this colour is when mouse is pressed
g2d.setStroke(new BasicStroke(STROKESIZE));
if (points.size() < 2) {
return;
}
for (int i = 1; i < points.size(); i++) {
int x1 = points.get(i - 1).x;
int y1 = points.get(i - 1).y;
int x2 = points.get(i).x;
int y2 = points.get(i).y;
g2d.drawLine(x1, y1, x2, y2);
}
}
#Override
public void mouseExited(MouseEvent e){
}
#Override
public void mouseEntered(MouseEvent e){
}
#Override
public void mouseMoved(MouseEvent e){
}
// Where the drawing happens
#Override
public void mousePressed(MouseEvent e) {
//status.setText("you pressed down the mouse");
xX1 = e.getX();
yY1 = e.getY();
points.add(e.getPoint());
}
#Override
public void mouseDragged(MouseEvent e) {
//status.setText("you draged the mouse");
points.add(e.getPoint());
imgLabel.repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
//status.setText("you release the mouse click");
Graphics2D g2d = bImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(getColor()); // this is the final colour
g2d.setStroke(new BasicStroke(STROKESIZE));
if (points.size() >= 2) {
for (int i = 1; i < points.size(); i++) {
int x1 = points.get(i - 1).x;
int y1 = points.get(i - 1).y;
int x2 = points.get(i).x;
int y2 = points.get(i).y;
g2d.drawLine(x1, y1, x2, y2);
}
}
g2d.dispose();
points.clear();
imgLabel.repaint();
}
// End of where the drawing happens
public void clearDrawBoard() {
}
private Color getColor() {
return ColourToolbar.selectedColor;
}
private void setColor(Color col){
this.currentColor = col;
}
#Override
public void mouseClicked(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
}
}
You should set a custom Composite for your Graphics2D, specifically AlphaComposite.Clear before drawing the rectangle. Don't forget to reset the composite to the default (SRC_OVER) when you are done, because the same Graphics object will be reused to paint other components.
I have a class named Foo that extends a class named Bar that extends JPanel and implements ActionListener. When I select Circle and click the draw button, I draw a circle, and when I press rectangle and click draw, it erases the previous shape and draws a rectangle.
However, I want to keep all the shapes on the JPanel until I choose to click the erase button. So I removed the super.paintComponent(g) and it works, but it also causes buttons of class Bar to reappear in a glitchy manner. How can I stop the buttons from painting again?
I was thinking not to extend Bar and make Foo extend JPanel.
public class Bar extends JPanel implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand() == "Draw")
{
this.requestDraw = true;
repaint();
}
if (e.getActionCommand() == "Circle")
{
requestRectangle = false;
requestTriangle = false;
requestCircle = true;
}
if (e.getActionCommand() == "Rectangle")
{
requestCircle = false;
requestTriangle = false;
requestRectangle = true;
}
if (e.getActionCommand() == "Right Triangle")
{
requestCircle = false;
requestRectangle = false;
requestTriangle = true;
}
}
public class Foo extends Bar
{
#Override
public void paintComponent(Graphics g)
{
//super.paintComponent(g);
if(RequestDraw())
{
if(RequestCircle())
circle.draw(g);
if(RequestRectangle())
rectangle.draw(g);
if(RequestTriangle())
rightTriangle.draw(g);
}
if(!RequestDraw())
{
g.setColor(getBackground());
g.fillRect(0,0,getWidth(), getHeight());
}
}
}
}
Along side all of Hovercraft's comments
The Graphics context is shared between components. One of the tasks of the super.paintComponent is to "clean" the graphics context before painting on it.
This is why you're seeing two version of your buttons...
I would also do a number of things differently. This should help with extendability and reuse over time, as well also reduce the logic a little.
I would...
Abstract the shapes to a basic "shape" class that has the minimum requirements, such as fill and stroke color, location, size, stroke, etc and know how to paint itself.
I would create a model of some kind that would allow you to separate and define the boundaries of responsibility. It's not the responsibility of the component to "manage" the shapes, it only cares about painting them on it's surface. Equally, the component doesn't care about what the "shape" is, it only wants to know about how they are to be painted...
I would use Actions to simply the creation of those shapes and adding them to the model...
I've only created a triangle shape (and it has no attributes beyond location and size), but I'm sure you'll get the general idea...(ps you'll need to supply your own triangle icon for the action ;))
public class DrawMe {
public static void main(String[] args) {
new DrawMe();
}
public DrawMe() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
DrawModel model = new DefaultDrawModel();
model.addElement(new Triangle(new Rectangle(10, 10, 100, 100)));
DrawPane drawPane = new DrawPane(model);
JToolBar toolBar = new JToolBar();
toolBar.add(new AddTriangleAction(model));
frame.add(toolBar, BorderLayout.NORTH);
frame.add(drawPane);
frame.setSize(400, 400);
frame.setVisible(true);
}
});
}
/**
* Simple action used to add triangles to the model...the model acts
* as a bridge between the action and the UI.
*/
protected class AddTriangleAction extends AbstractAction {
private DrawModel model;
public AddTriangleAction(DrawModel model) {
// Supply your own icon
putValue(SMALL_ICON, new ImageIcon(getClass().getResource("/shape_triangle.png")));
this.model = model;
}
public DrawModel getModel() {
return model;
}
#Override
public void actionPerformed(ActionEvent e) {
// Randomly add the triangles...
int x = (int)(Math.random() * 400);
int y = (int)(Math.random() * 400);
model.addElement(new Triangle(new Rectangle(x, y, 100, 100)));
}
}
/**
* This is the background pane, from which the draw pane extends...
*/
protected class BackgroundPane extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = getWidth() / 2;
int y = getHeight() / 2;
Graphics2D g2d = (Graphics2D) g.create();
RadialGradientPaint rgp = new RadialGradientPaint(
new Point(x, y),
Math.max(getWidth(), getHeight()),
new float[]{0f, 1f},
new Color[]{Color.GRAY, Color.WHITE}
);
g2d.setPaint(rgp);
g2d.fill(new Rectangle(0, 0, getWidth(), getHeight()));
g2d.setBackground(Color.BLACK);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
/**
* This is a simple model, I stole the list model because it was quicker
* and easier to demonstrate (don't need to write all the listeners)
*/
public interface DrawModel extends ListModel<DrawMeShape> {
public void addElement(DrawMeShape shape);
public void removeElement(DrawMeShape shape);
}
/**
* A default implementation of the DrawModel...
*/
public class DefaultDrawModel extends DefaultListModel<DrawMeShape> implements DrawModel {
#Override
public void removeElement(DrawMeShape shape) {
removeElement((Object)shape);
}
}
/**
* The actually "canvas" that shapes are rendered to
*/
protected class DrawPane extends BackgroundPane {
// Should provide ability to setModel...
private DrawModel model;
public DrawPane(DrawModel model) {
this.model = model;
model.addListDataListener(new ListDataListener() {
#Override
public void intervalAdded(ListDataEvent e) {
repaint();
}
#Override
public void intervalRemoved(ListDataEvent e) {
repaint();
}
#Override
public void contentsChanged(ListDataEvent e) {
repaint();
}
});
}
public DrawModel getModel() {
return model;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw the shapes from the model...
Graphics2D g2d = (Graphics2D) g.create();
DrawModel model = getModel();
for (int index = 0; index < model.getSize(); index++) {
DrawMeShape shape = model.getElementAt(index);
shape.paint(g2d, this);
}
g2d.dispose();
}
}
/**
* A abstract concept of a shape. Personally, if I was doing it, I would
* generate an interface first, but this is just a proof of concept...
*/
public abstract class DrawMeShape {
private Rectangle bounds;
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Rectangle getBounds() {
return bounds;
}
protected abstract Shape getShape();
/**
* The shape knows how to paint, but it needs to know what to paint...
* #param g2d
* #param parent
*/
public void paint(Graphics2D g2d, JComponent parent) {
g2d = (Graphics2D) g2d.create();
Rectangle bounds = getBounds();
Shape shape = getShape();
g2d.translate(bounds.x, bounds.y);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(shape);
g2d.setColor(Color.BLACK);
g2d.draw(shape);
g2d.dispose();
}
}
/**
* An implementation of a Triangle shape...
*/
public class Triangle extends DrawMeShape {
public Triangle(Rectangle bounds) {
setBounds(bounds);
}
#Override
protected Shape getShape() {
// This should be cached ;)
Path2D path = new Path2D.Float();
Rectangle bounds = getBounds();
path.moveTo(bounds.width / 2, 0);
path.lineTo(bounds.width, bounds.height);
path.lineTo(0, bounds.height);
path.lineTo(bounds.width / 2, 0);
path.closePath();
return path;
}
}
}
Happy drawing...
Suggestions:
Don't remove super.paintComponent(g) as it has a necessary important role to play.
Instead why not draw to a BufferedImage and then draw that BufferedImage in the paintComponent(...) method override.
Then if you want to erase drawn images, simply create a new BufferedImage, or draw over it.
As an aside, don't compare Strings using ==. Use the equals(...) or the equalsIgnoreCase(...) method instead. Understand that == checks if the two objects are the same which is not what you're interested in. The methods on the other hand check if the two Strings have the same characters in the same order, and that's what matters here. So instead of
if (fu == "bar") {
// do something
}
do,
if ("bar".equals(fu)) {
// do something
}
or,
if ("bar".equalsIgnoreCase(fu)) {
// do something
}