Related
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.
I am working with shapes, that are defined in the Graphiti-Framework. It supports the following:
Rectangle(int x, int y, int width, int height), whereas x/y defines the lower-left point,
Text (which is a rectangle as well),
Ellipse(int x, int y, int width, int height), so the same as the rectangle,
Line (int[] points), so an array with points as parameter
Polygon(int[] points), basically the same as line but the first and last point are connected.
My purpose is to rotate this elements. Unfortunately the framework I am using does not support rotation.
What in you opinion is the best solution to realise that?
I want to draw a figure using float or double values, to be precise.
I use:
g.drawOval(0, 0, 10, 10);
to draw a circle, but I only can use integer values.
Is there any statement that use float/double values that do the same?
Here is a picture: Problem
The circles have to be centered, and I can't. Any solution?
Code:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JPanel;
public class Bulls_EYE extends JPanel
{
int red, green, blue;
int height, width;
int heightOval = 475, widthOval = 475;
Random rValue = new Random();
public void paint (Graphics g)
{
super.paint(g);
for (int idx = 0; idx < 100; idx++)
{
g.setColor(new Color(red = 1 + rValue.nextInt(255), green = 1 + rValue.nextInt(255), blue = 1 + rValue.nextInt(255)));
g.fillOval(width+2*idx, height+2*idx, widthOval-5*idx, heightOval-5*idx);
}
}
}
I think it's an interesting question but needs more context. Drawing primitives are usually expressed in pixel coordinates so fractions of a pixel do not make much sense.
If you want precision like a CAD application note that what is displayed on the screen is only an approximation of the underlying model due to the limitations of the display.
You can represent your models precisely in memory (with limitations in floating point representation) and draw the approximation on the screen.
Update
Based on your last update:
We know from the JavaDoc that fillOval takes as parameters (x, y, w, h) where x, y are the upper left coordinates, and w, h are the width and height.
If for each concentric circle you move the upper left coordinates inward, in this case by 2 px, to keep them centered, you must also reduce the width and height by twice that amount. Change the following line:
g.fillOval(width+2*idx, height+2*idx, widthOval-5*idx, heightOval-5*idx);
To
int dx, dy, dw, dh;
dx = 2*idx;
dy = 2*idx;
dw = 2*dx; // note this is 4*idx not 5*idx like you have currently
dh = 2*dy;
g.fillOval(width+dx, height+dy, widthOval-dw, heightOval-dh);
Note that your width and height variables being used in the first and second parameters really doesn't have anything to do with width and height but instead are providing a beginning offset from the origin where the oval is drawn.
There is no reason you should do this, because when drawing an oval with the given coordinates, they are referred to pixels on the screen. Since you can't draw between pixels, 1 is the smallest unit you can use. If you want to round the values before drawing, you can use
g.drawOval(Math.round(a),Math.round(b),Math.round(x),Math.round(y)
which will round the float a, b, x and y before drawing the oval. The only reason I can see is that you calculate the coordinates and the result is a float, then you need to round it like above.
You can use the Arc2D class for drawing circles with float/double precision, since it is a Shape and the Graphics2D class can draw shapes.
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Shape circle = new Arc2D.Double(
// Same values as used in the fillOval function,
// but with double precision.
x, y, width, height,
// Draw a full circle (yes, in degrees).
0, 360,
// Connect the endpoint with the startpoint.
Arc2D.CORD
);
// Paint the circle.
g2d.fill(circle);
}
In a similar way, you can draw rectangles by using the Rectangle2D class.
Also, please use the paintComponent function instead of the paint function, as explained here.
I'll be candid about this; it is a homework assignment, but can someone guide me in the right direction and explain to me how some parts of the code are supposed to work? The directions are below the code and the questions.
This is my code so far:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rainbow extends JPanel
{
// Declare skyColor:
private final Color skyColor = Color.CYAN;
public Rainbow()
{
setBackground(skyColor);
}
// Draws the rainbow.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
// Declare and initialize local int variables xCenter, yCenter
// that represent the center of the rainbow rings:
int xCenter = width/2;
int yCenter = (height * 3) /4;
// Declare and initialize the radius of the large semicircle:
int largeRadius = width/4;
g.setColor(Color.RED);
// Draw the large semicircle:
g.fillArc(xCenter,yCenter,largeRadius,height,0,180);
// Declare and initialize the radii of the small and medium
// semicircles and draw them:
int smallRadius = height/4;
g.setColor(Color.MAGENTA);
g.fillArc(xCenter,yCenter,width,height,0,180);
int mediumRadius = (int) Math.sqrt(smallRadius * largeRadius);
g.setColor(Color.GREEN);
g.fillArc(xCenter,yCenter,width,height,0,180);
// Calculate the radius of the innermost (sky-color) semicircle
// so that the width of the middle (green) ring is the
// arithmetic mean of the widths of the red and magenta rings:
// Draw the sky-color semicircle:
g.fillArc(xCenter,yCenter,width,height,0,180);
}
public static void main(String[] args)
{
JFrame w = new JFrame("Rainbow");
w.setBounds(300, 300, 300, 200);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = w.getContentPane();
c.add(new Rainbow());
w.setVisible(true);
}
}
My questions: How exactly does fillArc work; I understand what goes in the parameter, but what must one do so each arc differs from one another?
How does one set one color for each arc? I tried doing so, and I ended up with the color listed closest the end showing up and overriding the others.
I'll probably have more as a I continue to code.
These were the directions:
![enter image description here][1]
The “rainbow” is made of four overlapping semicircles. The outer ring is red (Color.RED), the middle one is green (Color.GREEN), and the inner ring has the magenta color (Color.MAGENTA). The innermost semicircle has the same color as the background.
Follow the instructions below and fill in the blanks in Rainbow.java.
Start the Rainbow project.
Add a complete comment header with your name before the class declaration at the top of the file.
Add to the Rainbow class a declaration of a private final field skyColor of the type Color, initialized to Color.CYAN (the color of the sky). In Rainbow’s constructor, set the window’s background to skyColor rather than Color.WHITE.
In the paint method, declare local integer variables xCenter and yCenter that represent the coordinates of the center of the rings. Initialize them to 1/2 width and 3/4 height (down) of the content pane, respectively. (Recall that the origin of graphics coordinates in Java is at the upper left corner of the content pane with the y-axis pointing down.) Do not plug in fixed numbers from the window’s dimensions.
Declare a local variable largeRadius that represents the radius of the largest (red) semicircle and initialize it to 1/4 of width.
A method call g.fillArc(x, y, size, size, from, degrees) (with all integer arguments) draws a sector of a circle. x and y are the coordinates of the upper left corner of the rectangle (in this case a square) into which the oval is (logically) inscribed; size is the side of the square (and the diameter of the circle); from is the starting point of the arc in degrees (with 0 at the easternmost point of the horizontal diameter), and degrees (a positive number) is the measure of the arc, going counterclockwise. Add a statement to the paint method to draw the largest (red) semicircle. Test your program.
Add statements to display the medium (green) and small (magenta) semicircles. The radius of the magenta semicircle should be 1/4 of height. The radius of the green one should be the geometric mean (the square root of the product) of the radius of the red semicircle and the radius of the magenta semicircle, rounded to the nearest integer. (A call to Math.sqrt(x) returns the value of square root of x, a double.) Retest your program.
Add statements to display the innermost semicircle of the background (“sky”) color to complete the rainbow. Use the skyColor constant for this semicircle’s color. Choose the radius of the sky-color semicircle in such a way that the width of the middle (green) ring is the arithmetic mean of the widths of the red and magenta rings.
Test your program.
Submit your completed program and run output. Your run output (the rainbow picture) can be included by capturing the screen output (Alt-PrintScrn), pasting it into a graphics program (such as MS Paint) and then saving the image to your Eclipse project directory.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rainbow extends JPanel
{
//Declare skyColor:
private final Color skyColor = Color.CYAN;
public Rainbow()
{
setBackground(skyColor);
}
// Draws the rainbow.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
// Declare and initialize local int variables xCenter, yCenter
// that represent the center of the rainbow rings:
int xCenter = width/2;
int yCenter = (height * 3) /4;
// Declare and initialize the radius of the large semicircle:
int largeRadius = width/4;
g.setColor(Color.RED);
// Draw the large semicircle:
g.fillArc(xCenter - largeRadius,yCenter - largeRadius ,largeRadius,largeRadius,0,180);
// Declare and initialize the radii of the small and medium
//semicircles and draw them:
int smallRadius = height/4;
int mediumRadius = (int) Math.sqrt(smallRadius * largeRadius);
g.setColor(Color.GREEN);
g.fillArc(xCenter-(largeRadius+mediumRadius)/2,yCenter- (largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.setColor(Color.MAGENTA);
g.fillArc(xCenter-(largeRadius+smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
// Calculate the radius of the innermost (sky-color) semicircle
// so that the width of the middle (green) ring is the
// arithmetic mean of the widths of the red and magenta rings:
int skyRadius = (int)((2 * Math.sqrt(smallRadius * largeRadius)) - width/4);
// Draw the sky-color semicircle:
g.setColor(skyColor);
g.fillArc(xCenter-skyRadius,yCenter-skyRadius,skyRadius,skyRadius,0,180);
}
public static void main(String[] args)
{
JFrame w = new JFrame("Rainbow");
w.setBounds(300, 300, 300, 200);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = w.getContentPane();
c.add(new Rainbow());
w.setVisible(true);
}
}
fillArc() fills in a section of the circle based upon the parameters you gave it. For example your first arc.
You're drawing the fill arc, which in this case is a semi-circle, of the color red.
//Set the arc color
g.setColor(Color.RED);
// Draw the large semicircle:
g.fillArc(xCenter,yCenter,largeRadius,height,0,180);
There's our fillArc. That doesn't look anything like a rainbow. In order to get the rainbow shape, we have to draw a smaller arc inside of it. In your case the next one is green. So we do fillArc again after setting the color to green. But we shrunk the radius a little bit so the green doesn't cover the entire red section.
Keep in mind when we draw, we're drawing on top, so if you drew the green on first it would be covered by the red one.
Then we draw another arc inside this once more, but make this one the color of the sky (white in this case). This creates the final rainbow shape. So we do fillArc again, but with a slightly smaller radius and the color white.
And there, we drew a rainbow.
To center this beautiful creation, we have to understand a few things about the fillArc function.
The parameters are:
public abstract void fillArc(int x,
int y,
int width,
int height,
int startAngle,
int arcAngle)
int x and int y represent the coordinates for the upper left hand corner of the arc that you are drawing. The reason your code isn't centering is because of how you're drawing the arc.
g.fillArc(xCenter - largeRadius,yCenter - largeRadius,largeRadius,largeRadius,0,180);
g.fillArc(xCenter-(largeRadius+mediumRadius)/2,yCenter-(largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.fillArc(xCenter-(largeRadius+smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
I took out a few of the excess stuff. You see how you're subtracting the (largeRadius+smallRadius)/2 and (largeRadius+mediumRadius)/2? This is shifting the rainbow to make it off center. What you should have is instead:
g.fillArc(xCenter - largeRadius/2,yCenter - largeRadius,largeRadius,largeRadius,0,180);
g.fillArc(xCenter-(mediumRadius)/2,yCenter-(largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.fillArc(xCenter-(smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
This will properly center the rainbow. Here's why.
That's the point where they will start the drawing the arc from. If you want to center the entire rainbow, you'd shift it over by half of it's entire width. So if you want to center the red arc, you'd do
xCenter - (largeRadius/2)
As this is setting the x start to the left by half. You wouldn't include largeRadius in the other arcs, as you're centering them around this point. Thus you'd want to shift them over by half of their individual widths, which is why their x positions are
xCenter-(mediumRadius)/2
xCenter-(smallRadius)/2
Centering on the Y-axis works differently. You have to consider that the height of the rainbow overall is 1/4 the largeRadius. Your code uses yCenter = 3/4 * height, so that changes it a bit.
This is my solution
g.fillArc(xCenter - largeRadius/2,yCenter - largeRadius/2 + largeRadius/4 -height/4,largeRadius,largeRadius,0,180);
g.fillArc(xCenter-(mediumRadius)/2,yCenter-(mediumRadius)/2 + largeRadius/4 -height/4,mediumRadius,mediumRadius,0,180);
g.fillArc(xCenter-(smallRadius)/2,yCenter-(smallRadius)/2 + largeRadius/4 -height/4,smallRadius,smallRadius,0,180);
Let's take a look. I subtracted the largeRadius/2 (and respective radiuses) for the same principle as in x. But then I added largeRadius/4 because we have to shift the entire rainbow down. This is because subtracting the respective radius/2 only centers the rainbow as if it were an entire circle, not semi-circles.
Adding largeRadius/4 shifts the rainbow down by overall half of it's height, centering it correctly for a semi-circle. Finally, subtracting height/4 makes changes the yCenter to height/2, since 3/4 * height is a requirement in your assignment.
Sorry about all the problems in the comments, hope this cleared it up.
I've modified part of your code so you could get an idea. Remember that your xCenter and yCenter represent the center of your circle, not the coordinates you need to use in the fillArc method. The instructions you provided explain it pretty well. You can get an idea from what I did here and figure the rest by yourself.
// First declare and initialize all radiuses
int largeRadius = width/4;
int smallRadius = height/4;
int mediumRadius = (int) Math.sqrt(smallRadius * largeRadius);
//Then draw each arc in descending order from the largest one
g.setColor(Color.RED);
g.fillArc(xCenter-largeRadius,yCenter-largeRadius,largeRadius,largeRadius,0,180);
g.setColor(Color.GREEN);
g.fillArc(xCenter-(largeRadius+mediumRadius)/2,yCenter-(largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.setColor(Color.MAGENTA);
g.fillArc(xCenter-(largeRadius+smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
// Calculate the radius of the innermost (sky-color) semicircle
For you skyRadius consider:
Red width = large radius - medium radius
green width = medium - small
magenta width = small radius - skyradius
if I did the math rightyou get: skyRadius = smallRadius - 2*(mediumRadius-smallRadius)+largeRadius-mediumRadius
int skRadius=smallRadius-2*(mediumRadius-smallRadius)+largeRadius-mediumRadius;
g.setColor(skyColor);
g.fillArc(xCenter-(largeRadius+skRadius)/2,yCenter-(largeRadius+skRadius)/2,skRadius,skRadius,0,180);
A much simpler equation for skyRadius is:
int skyRadius = largeRadius - 3 * mediumRadius + 3 * smallRadius;
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.