Layered painting on java? - java

I am basically trying to do something like classic "Paint" (Microsoft's program). But i want to work with layers when painting. I thought i can use JPanel component as layer.
I was testing the code below. The goal is drawing a rectangle with mouse. There is a temp layer (temp) to draw on it while dragging the mouse, and there is actual layer (area) to draw when mouse released. But every time i start drawing a new rectangle, old ones are disappear. Also if i execute setVisible(false) and true again, everything disappears.
MouseInputAdapter mia = new MouseInputAdapter() {
private int startx = 0, starty = 0, stopx = 0, stopy = 0;
public void mousePressed(MouseEvent evt) {
startx = evt.getX();
starty = evt.getY();
}
public void mouseDragged(MouseEvent evt) {
Graphics2D tempg = (Graphics2D) temp.getGraphics();
int width = Math.abs(startx - evt.getX());
int height = Math.abs(starty - evt.getY());
int x = evt.getX(), y = evt.getY();
if(x > startx)
x = startx;
if(y > starty)
y = starty;
Rectangle r = new Rectangle(x, y, width, height);
tempg.clearRect(0, 0, getWidth(), getHeight());
tempg.draw(r);
}
public void mouseReleased(MouseEvent evt) {
Graphics2D g = (Graphics2D) area.getGraphics();
stopx = evt.getX();
stopy = evt.getY();
int width = Math.abs(startx - stopx);
int height = Math.abs(starty - stopy);
int x = startx, y = starty;
if(x > stopx)
x = stopx;
if(y > stopy)
y = stopy;
Rectangle r = new Rectangle(x, y, width, height);
g.draw(r);
}
};
area.addMouseListener(mia);
area.addMouseMotionListener(mia);
temp.addMouseListener(mia);
temp.addMouseMotionListener(mia);
What is wrong with that code?

Every time there's a repaint there's no guarantee you'll get the same graphics in the state you left it.
Two a two-step instead:
Create a List of Rectangles in your class.
In your mouse listener instead of drawing to the graphics, add a rectangle to the list.
Override paintComponent and in there draw the list of rectangles to the graphics it is passed.
Using the list is nice as items at the start of the list will be painted below ones at the end.

Classic bitmap-based graphics painting software operates on a target bitmap. You can render multiple Layers in paintComponent(), as #Keily suggests for Rectangles.
Alternatively, you may want to to look at classic object-based drawing software, outlined here.

Here's a general idea: (I'm assuming you mean layers such as in photoshop)
Set up a single JPanel for drawing.
Make a data structure containing all drawable objects you need for drawing.
In this data structure, also make a field containing an integer expressing which layer that specific drawable object is tied to.
In your paintComponent() method, check which layer is currently active and only draw the the data in that layer or below it.

This is what i was looking for; http://www.leepoint.net/notes-java/examples/mouse/paintdemo.html
My mistake; using getGraphics() method out of paintComponent() and expecting keep changes.
Why #Keilly's answer not working for me; Because if i put shapes in a list or array, when a shape changed (for example; deleting a circle's 1/4) i can't update the element in the list. Because it doesn't be same shape anymore. So i have to keep shapes as drawings, and i don't have to (and dont want to) keep them separately.

Related

Issues with coordinates

I'm working on something that involves clicking specific points on a buffered image in a JPanel. I had issues with this earlier in the project (affine transform translation not working properly), but nothing I found fixed it so I decided I would come back to it later.
I'm not entirely sure how to trouble shoot it since I'm a novice, but I think it's reading my y coordinates too low. I made a mouse input listener that tracks the number of times the user has clicked and gets the mouse pointer's location for functions I haven't made yet. For testing I have it output the coordinates and number of clicks then make a circle centered where the mouse clicks.
#Override
public void mouseClicked(MouseEvent e) {
Point mouseCursor = MouseInfo.getPointerInfo().getLocation();
panel.drawCenteredCircle(mouseCursor.getX(), mouseCursor.getY(), 100);
System.out.println(String.valueOf(mouseCursor));
System.out.println(String.valueOf(clickCount));
clickCount++;
}
Here is drawCenteredCircle in my custom panel class:
public void drawCenteredCircle(double x, double y, int r) {
imgG2 = image.createGraphics();
imgG2.setPaint(Color.RED);
x = (x-r/2.0);
y = (y-r/2.0);
imgG2.fillOval((int)Math.round(x), (int)Math.round(y), r, r);
this.repaint();
imgG2.dispose();
}
I tried taking a screenshot to show what happens, but the circle properly centers on the x coordinate, but not the y coordinate. Instead it draws the circle with the pointer at the top center edge.
I overrided the paintComponent of my JPanel to implement a zoom feature:
#Override
protected void paintComponent(Graphics g) {
//Implimenting zoom
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
/*Supposed to counter the movement from the scale, not working properly
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double x = (w - scale * imageWidth)/2;
double y = (h - scale * imageHeight)/2;*/
AffineTransform at = new AffineTransform()/*.getTranslateInstance(x, y) */;
at.scale(scale, scale);
g2.drawRenderedImage(image, at);
//g2.dispose(); I was told to put this, but I'm not sure if it's necessary or what it does entirely
}
My confused notes are because I got this code from an example someone made and, as I said earlier, the affine translation wasn't working (I took the actual translation out). They're irrelevant to the question.
The reason I put this is because I initially had code that was meant to fit the image to the screen/frame depending if it was fullscreen or not:
int x = image.getWidth();
int y = image.getHeight();
double frameW = frame.getBounds().getWidth();
double frameH = frame.getBounds().getHeight();
//Rectangle winSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
double screenW = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
double screenH = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
if (!isFullScreen) {
if (x/y > frameW/frameH) {
scale = frameW/x;
} else {
scale = frameH/y;
}
} else {
if (x/y > screenW/screenH) {
scale = screenW/x;
} else {
scale = screenH/y;
}
}
It uses my zoom function which scales the image with the double "scale." I noticed that when I zoomed in or out, it would change where the dots would appear relative to the pointer. It wasn't until I removed the code for the image to start fitted to the window and had it start at 100% that I received the result of the pointer being at the top center of the circle.
I also tried removing the part that's supposed to center the circle and the result was the pointer being on the left side and having a gap between it and the top of the circle.
Sorry if this is too much stuff. I'm pretty novice and learned just as much about java (the only coding language I know) working on this project as I knew when I first started it. I'm not sure what information I have that could be helpful in this, so I just threw in everything I thought could help. I appreciate any help, even irrelevant to my question.

How can i draw something only once in Java using Graphics

I am practicing Java, trying to get back into OOP programming. I decided to recreate snake using a tutorial online. I am using Graphics for my code and i was wondering if the paintComponent() method is called 60 times a second or something similar. My probelm is that i am building some walls, if the snake collides he dies, the walls however i only want them drawn once, but it seems that the walls are drawn over and over again (I tested this using sysout). Some code is provided below:
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
Inside the draw function
//Draw wall
wall1 = new Walls(10, 10, 10, 20, UNIT_SIZE, g);
The Walls constructor
Walls(int startX, int startY, int endX, int endY, int UNIT_SIZE, Graphics g)
{
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
g.setColor(Color.GRAY);
for(int i = startY; i<=endY; i++)
{
for(int j = startX; j<=endX; j++)
{
g.fillRect(UNIT_SIZE*j, UNIT_SIZE*i, UNIT_SIZE, UNIT_SIZE);
}
}
}
The Swing painting system calls paintComponent() whenever there's a need to update something about the appearance of your component. The reason can be that the window was hidden or partially obscured and now becomes visible again, or that the contents of the component changed.
So, whenever Swing calls paintComponent(), it's important to draw everything that falls into the paint-requested part of the component, otherwise you'll get nasty paint artifacts like missing elements or leftovers from previous window states.
From your description, I guess it's mostly your software requesting a repaint of your component, by calling the repaint() method somewhere in your code. My recommendations:
Make sure you supply a rectangle to the repaint() call specifying the region that has changed (the snake head, more or less). Swing only repaints the component parts that are known to need it, by setting a fitted clipping region before calling paintComponent().
Optimize your paintComponent() implementation to check whether the clipping region of the Graphics object intersects with your walls. If not, you can skip painting the walls.

For loop drawing PGraphics in an array [Processing]

I'm trying to build a Drawing Program using Processing. I am currently stuck on using PGrapchics.
When the user draws a rectangle, it shows the shape being drawn. When the user releases their mouse, it then creates a PGraphic of the final shape. I would then like the user to draw on top of that. Here is my problem:
I had to reset the background of the canvas when drawing a rectangle because otherwise, it shows a trail of rectangles. The result is that while the user draws a new rectangle the old ones disappear and come back once the mouse has been releasd
Some thoughts:
I would also like to add features where the user can select on a previously drawn rectangle and change it's colour, stroke, send to back, bring to front etc..
To achieve this, I'm storing all drawn rectangles (PGraphics) into an ArrayList which will be drawn via a for loop. This will allow me to adjust the behaviour by moving the PGraphics elements up and down the ArrayList.
PS: Instead of creating my own class of Shape am I better off using PShape?
int startX;
int startY;
int endX;
int endY;
boolean drawing;
int strokeW = 3;
Shape shape;
PGraphics shapeLayer;
ArrayList<PGraphics> layersList = new ArrayList();
void setup() {
size(500, 500);
cursor(CROSS);
background(255);
smooth();
}
void draw() {
strokeWeight(strokeW);
if (key >= '0' && key <= '9') {
strokeW = key - '0';
}
for(int i = 0; i < layersList.size(); i++) {
image(layersList.get(i), 0, 0);
}
if (drawing) {
shape.createRectangle();
}
}
void mousePressed() {
startX = mouseX;
startY = mouseY;
shapeLayer = createGraphics(width, height);
shapeLayer.beginDraw();
}
void mouseDragged() {
drawing = true;
endX = constrain(mouseX, 0, 500);
endY = constrain(mouseY, 0, 500);
shape = new Shape(startX, startY, endX, endY);
shapeLayer.clear();
}
void mouseReleased() {
drawing = false;
shapeLayer.endDraw();
layersList.add(shapeLayer);
}
Here is the Shape Class:
class Shape {
int startX;
int startY;
int endX;
int endY;
Shape(int x1, int y1, int x2, int y2) {
startX = x1;
startY = y1;
endX = x2;
endY = y2;
}
void createRectangle() {
background(255, 0);
shapeLayer.strokeWeight(strokeW);
shapeLayer.rectMode(CORNERS);
shapeLayer.rect(startX, startY, endX, endY);
rectMode(CORNERS);
rect(startX, startY, endX, endY);
}
}
In the future, please try to narrow your problem down to a MCVE before you post. For example you could have hard-coded it to draw a rectangle when the user drags instead of including all the code for every shape.
But your problem is caused by drawing to the screen and never clearing it out. You need to break your problem down into smaller pieces and then approach those pieces one at a time.
Step 1: Can you create a sketch that just shows a rectangle as you drag, but has the rectangle go away when you let go of the mouse? Start over with a basic sketch that does just this one thing, and get it working perfectly before you move on to the next step.
Step 2: Can you draw shapes to an off-screen buffer? It looks like you've tried this in your current code, but note that you never actually draw any shapes to your buffer, and you never actually draw your buffer to the screen. Again, start with a basic sketch that just does this. Don't even worry about user input or anything yet, just get a hard-coded rectangle drawn to an off-screen buffer, then draw that off-screen buffer to the screen.
Step 3: Can you combine those two to show the rectangle when you're drawing it, then draw it to the off-screen buffer when the user lets go?
Step 4: Only when you have the rectangle working perfectly, then move on to other shapes.
This is how programming works: you have to break your problem down into small steps like this, and then you have to approach each step in isolation. If you get stuck, you can come back with an MCVE showing just one of these steps, and we'll go from there. Good luck.
In addition to Kevin's answer: it does look like you are using another PGraphics buffer to draw into, but the whole sketch could be simpler.
Unless you need an undo/redo mode, where remembering the drawing commands and their order is needed, you can get away with something slightly simpler.
You can find a detailed answer with commented code showing something very similar. You simply need to add the pencil and line modes.

Issue when rotating an object through the atan2 method

I'm trying to have an image follow my cursor, smoothly moving where it is located and turning to face it.
When i'm near the origin, it works flawlessy: the image will flip completely without issues, however the further i go from the origin of the window, the less the image turns. When i'm at the other side of the screen, instead of flipping over it'll rotate 5°-15° instead.
I'd love if somebody could point out what the issue is =)
Here's my current code for the image:
lblRover = new JLabel(sees) { // sees is an ImageIcon
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform aT = g2.getTransform();
Shape oldshape = g2.getClip();
double x = getWidth()/2.0;
double y = getHeight()/2.0;
aT.rotate(Math.toRadians(degrees), x, y);
g2.setTransform(aT);
g2.setClip(oldshape);
super.paintComponent(g);
}
};
lblRover.setSize(179, 180);
lblRover.setLocation(500, 300);
JFrame.getFrames()[0].add(lblRover);
And this is the code that handles the rotation, plus its movement:
NewJFrame.PInf = MouseInfo.getPointerInfo();
/* The frame is contained in NewJFrame, PInf is a PointerInfo */
p = (NewJFrame.PInf.getLocation()); // p is a point
p.x-=NewJFrame.getWindows()[0].getLocationOnScreen().x;
p.y-=NewJFrame.getWindows()[0].getLocationOnScreen().y;
//i subtract the location of the window relative to the screen
img = NewJFrame.lblRover.getLocation();
img.x+=NewJFrame.lblRover.getWidth()/2;
img.y+=NewJFrame.lblRover.getHeight()/2;
// img will be the point with the center of my image
sas=getAngle(p,img); // sas is a float variable
NewJFrame.degrees=sas;
//
// From now on i move the image
//
diffx = p.x-img.x;
diffy = p.y-img.y;
diffx/=80; // 80 is an arbitrary number to smooth the effect
diffy/=80; // high numbers will make the image not move at all
Point var = new Point(NewJFrame.lblRover.getLocation());
// I may have to use img here or subtract width and height /2
var.x+=diffx;
var.y+=diffy;
NewJFrame.lblRover.setLocation(var);
// A 5ms sleep to smooth the movement
// I also refresh a debug label
// Also i do refresh the frame, otherwise nothing happens
NewJFrame.jLabel1.setText(""+NewJFrame.lblRover.getLocation().y
+"/"+ p.y+" "+NewJFrame.lblRover.getLocation().x +"/"+ p.x );
NewJFrame.getFrames()[0].repaint();
try {
sleep(5);
} catch (InterruptedException ex) {
Logger.getLogger(thread.class.getName()).log(Level.SEVERE, null, ex);
}
Well, after multiple tries i've narrowed it down.
Now, the rotation code is put last, before the repaint, and it looks like this:
sas=this.getAngle((float)diffy,(float)diffx);
NewJFrame.degrees=sas;
I've also created this new method that uses two float instead of two points:
public float getAngle(Float x, Float y) {
float angle = (float) Math.toDegrees(Math.atan2(x, y));
//System.out.println(angle);
return angle;
I have no clue why it works now but it didn't before as the procedure is basically identical...but hey it works! =D

Drawing a dot grid

I'm new to graphics programming. I'm trying to create a program that allows you to draw directed graphs. For a start I have managed to draw a set of rectangles (representing the nodes) and have made pan and zoom capabilities by overriding the paint method in Java.
This all seems to work reasonably well while there aren't too many nodes. My problem is when it comes to trying to draw a dot grid. I used a simple bit of test code at first that overlayed a dot grid using two nested for loops:
int iPanX = (int) panX;
int iPanY = (int) panY;
int a = this.figure.getWidth() - iPanX;
int b = this.figure.getHeight() - (int) iPanY;
for (int i = -iPanX; i < a; i += 10) {
for (int j = -iPanY; j < b; j += 10) {
g.drawLine(i, j, i, j);
}
}
This allows me to pan the grid but not zoom. However, the performance when panning is terrible! I've done a lot of searching but I feel that I must be missing something obvious because I can't find anything on the subject.
Any help or pointers would be greatly appreciated.
--Stephen
Use a BufferedImage for the dot grid. Initialize it once and later only paint the image instead of drawing the grid over and over.
private init(){
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// then draw your grid into g
}
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
// then draw the graphs
}
And zooming is easily achieved using this:
g.drawImage(image, 0, 0, null); // so you paint the grid at a 1:1 resolution
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
// then draw the rest into g2 instead of g
Drawing into the zoomed Graphics will lead to proportionally larger line width, etc.
I think re-drawing all your dots every time the mouse moves is going to give you performance problems. Perhaps you should look into taking a snapshot of the view as a bitmap and panning that around, redrawing the view 'properly' when the user releases the mouse button?

Categories

Resources