Related
Is there any way to bend a BufferedImage in Java?
I thought that if I crop the image into smaller pieces and rotate them then I would essentially bend the image, but it doesn't seem to work.
Here is the method I created:
/**
* This is a recursive method that will accept an image the point where the bending will start and the point where the bending will end, as well as the angle of bending
*
* #param original:the original image
* #param startingPoint: the point where the bending should start
* #param endingPoint: the point where the bending should end
* #param radiands: the angle
* #return the bent image
*/
public static BufferedImage getBentImage(BufferedImage original, int startingPoint, int endingPoint, double radians) {
if (startingPoint >= endingPoint)
return original;
int type = BufferedImage.TYPE_INT_ARGB;
int width = original.getWidth();
int height = original.getHeight();
BufferedImage crop = original.getSubimage(0, 0, startingPoint, height);
BufferedImage crop0 = original.getSubimage(startingPoint, 0, width - startingPoint, height);
BufferedImage bendCrop = new BufferedImage(width, height, type);
BufferedImage image = new BufferedImage(width, height, type);
AffineTransform rotation = new AffineTransform();
rotation.translate(0, 0);
rotation.rotate(radians);
Graphics2D g = bendCrop.createGraphics();
g.drawImage(crop0, rotation, null);
g.dispose();
g = image.createGraphics();
g.drawImage(crop, 0, 0, null);
g.drawImage(bendCrop, startingPoint, 0, null);
g.dispose();
return getBentImage(image, startingPoint + 1, endingPoint, radians);
}
This is the original Image:
And this is the result of this getBentImage(image, 200, 220, Math.toRadians(1)):
I was expecting something closer to:
Any ideas on how to actually implement a getBentImage() method?
As suggested in the comments, a simple approach is to divide the image into 3 parts:
Identical to the original.
Bent according to the bending transformation.
Constant diagonal continuation.
Here is a quick and a bit messy example that shows the original shape and the resulting shape below it. I just used a label icon for the images instead of doing custom painting. (Also I didn't adhere to the Java naming conventions with final variables because it's math and not typical coding.)
Since there are quite a few variables in the calculation code, I added a sketch at the end that shows what the variables represent.
public class Main extends JFrame {
static BufferedImage image;
public static void main(String[] args) {
try {
image = ImageIO.read(ClassLoader.getSystemResource("img.png"));
} catch (IOException e) {
e.printStackTrace();
}
new Main();
}
public Main() {
getContentPane().setLayout(new BorderLayout(5, 10));
BufferedImage img2 = transform(15, 100, 300);
JLabel label1 = new JLabel(new ImageIcon(image));
label1.setHorizontalAlignment(JLabel.LEFT);
label1.setOpaque(true);
label1.setBackground(Color.YELLOW);
add(label1, BorderLayout.NORTH);
JLabel label2 = new JLabel(new ImageIcon(img2));
label2.setHorizontalAlignment(JLabel.LEFT);
label2.setOpaque(true);
label2.setBackground(Color.CYAN);
add(label2);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
static BufferedImage transform(int t, int x1, int x2) {
final double TH = Math.toRadians(t);
final int D = x2 - x1;
final int W = image.getWidth();
final int H = image.getHeight();
final int dD = (int) (D / (2 * TH) * Math.sin(2 * TH));
final int dH = (int) (D / TH * Math.pow(Math.sin(TH), 2));
final int pH = (int) ((W - x2) * Math.tan(2 * TH));
final int width = W - (D - dD);
final int height = (int) (H + dH + pH);
System.out.println(W + " " + H + " -> " + width + " " + height);
BufferedImage img2 = new BufferedImage(width, height, image.getType());
for (int x = 0; x < x1; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
img2.setRGB(x, y, rgb);
}
}
for (int x = x1; x < x2; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
int dx = (int) (D / (2 * TH) * Math.sin(2 * (x-x1) * TH / D));
int dy = (int) (D / TH * Math.pow(Math.sin((x-x1) * TH / D), 2));
img2.setRGB(x1 + dx, y + dy, rgb);
}
}
for (int x = x2; x < W; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
int dp = (int) ((x - x2) * Math.tan(2 * TH));
img2.setRGB(x - (D - dD), y + dH + dp, rgb);
}
}
return img2;
}
}
As for the calculations, I'll leave it for you as homework; it's just geometry/trigonometry which belongs on Math.SE more than on SO. If you can't figure it out I'll give you a direction.
Note that this method might not be fast at all and could certainly be optimized, I'll leave that to you also. Oh, and rounding doubles to ints carelessly, so the result is not pixel-perfect.
I dont know what you mean by bending but essentially you have a rectangle and you break one piece of it and rotate it:
so the algorithm is as follows:
rotate line(x, 0, width-1, 0)
rotate line(x, height-1, width-1, height-1)
connect the pieces
So essentially you are looking for rotate line.
The issue I'm having is that the output png only shows recursion on the bottom left corner, and I cant figure out why. Could someone please point me in the right direction, and Sorry about the messy comments.
Thank you.
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
public class Assignment12 {
static int WIDTH = 0;
static int HEIGHT = 0;
static ArrayList<Double> points = new ArrayList<Double>();
public static void main(String[] args) {
if (args.length == 0) {
WIDTH = 800;
HEIGHT = 693;
} else {
Integer W = Integer.valueOf(args[0]);
Integer H = Integer.valueOf(args[0]);
WIDTH = W;
HEIGHT = H;
}
// DONE //*********stuff to do ***********create the final ordered pairs
// of the biggest triangle
// done// *********stuff to do *********** set the values of the ordered
// pairs
// to the dimensions of the rectangle
final double topX = WIDTH / 2;
final double topY = 0;
final double leftX = 0;
final double leftY = HEIGHT - 1;
final double rightX = WIDTH - 1;
final double rightY = HEIGHT - 1;
sierpinski(topX, topY, leftX, leftY, rightX, rightY);
// ##############jeffs code#########
// BufferedImage creates memory space for storing image data
BufferedImage img = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_RGB);
// Graphics2D provides a canvas on which to draw shapes, text, other
// images
Graphics2D g2d = img.createGraphics();
// *********stuff to do *********** do the back ground stuffs
// Clear background to white
g2d.setColor(Color.white);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
// start drawing lines in the correct color.
// Red line from where to where?
g2d.setColor(Color.red);
for (int i = 0; i < points.size(); i = i + 4) {
g2d.drawLine(points.get(i).intValue(),
points.get(i + 1).intValue(), points.get(i + 2).intValue(),
points.get(i + 3).intValue());
}
// g2d.drawLine(0, 0, WIDTH - 1, HEIGHT - 1);
// done// *********stuff to do *********** initialize the recursive
// function to
// done// *********stuff to do *********** get rid of these oval things
// there
// just for reference,
// Draw some random ovals
// for (int i = 0; i < 100; i++) {
// g2d.setColor(new Color((float) Math.random(),
// (float) Math.random(), (float) Math.random()));
// g2d.fillOval((int) (Math.random() * WIDTH),
// (int) (Math.random() * HEIGHT), (int) (Math.random() * 50),
// (int) (Math.random() * 50));
// }
// Finalize the canvas
g2d.dispose();
// Write the image out as a PNG-format file
try {
ImageIO.write(img, "png", new File("out.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
// *********stuff to do *********** create the recursive function for the
// triangles
private static void sierpinski(double topX, double topY, double leftX,
double leftY, double rightX, double rightY) {
// base case area of the triangle reaches x
// (Ax(By-Cy)+Bx(Cy-Ay)+Cx(Ay-By))/2
if (((leftX * (topY - rightY)) + (topX * (rightY - leftY)) + (rightX * (leftY - topY)) / 2) > 10) {// <--
// that 10 should be a static value or just pulled directly from
// args
// start recursive for all three respective points
double leftMidX = (topX + leftX) / 2;
double leftMidY = (topY + leftY) / 2;
double rightMidX = (topX + rightX) / 2;
double rightMidY = (topY + rightY) / 2;
// ***these are just called topMid for convenience. they are the mid
// point values to the bottom line of the triangle.
double botMidX = (leftX + rightX) / 2;
double botMidY = (leftY + rightY) / 2;
// top ... top stays the same
sierpinski(topX, topY, leftMidX, leftMidY, rightMidX, rightMidY);
// left
sierpinski(leftMidX, leftMidY, leftX, leftY, botMidX, botMidY);
// right
sierpinski(rightMidX, rightMidY, botMidX, botMidY, rightX, rightY);
} else {
points.add(topX);
points.add(topY);
points.add(rightX);
points.add(rightY);
points.add(topX);
points.add(topY);
points.add(leftX);
points.add(leftY);
points.add(leftX);
points.add(leftY);
points.add(rightX);
points.add(rightY);
// draw the lines
// g2d.drawLine(topX, topY, rightX, rightY);// right
// g2d.drawLine(topX, topY, leftX, leftY);// left
// g2d.drawLine(leftX, leftY, rightX, rightY);// bot
}
}
}
To draw a Sierpinski triangle calculate the points correctly. The following code here demonstrates it. More elaborated example can be found # github.
For a refference you can view a different approach to calculate triangle's points (calculates the Pascal's triangle and uses places of odd values in it as points for sierpinski triangle) # github.
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import java.util.List;
public class Sierpinski
{
public static int WIDTH = 800;
public static int HEIGHT = 693;
public static List<Double> points = new ArrayList<>();
public static void main(String[] args)
{
if (0 != args.length)
{
WIDTH = Integer.valueOf(args[0]);
HEIGHT = Integer.valueOf(args[0]);
}
// DONE //*********stuff to do ***********create the final ordered pairs
// of the biggest triangle
// done// *********stuff to do *********** set the values of the ordered
// pairs
// to the dimensions of the rectangle
final double topX = WIDTH / 2;
final double topY = 0;
final double leftX = 0;
final double leftY = HEIGHT - 1;
final double rightX = WIDTH - 1;
final double rightY = HEIGHT - 1;
sierpinski(topX, topY, leftX, leftY, rightX, rightY);
// ##############jeffs code#########
// BufferedImage creates memory space for storing image data
BufferedImage img = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_RGB);
// Graphics2D provides a canvas on which to draw shapes, text, other
// images
Graphics2D g2d = img.createGraphics();
// *********stuff to do *********** do the back ground stuffs
// Clear background to white
g2d.setColor(Color.white);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
// start drawing lines in the correct color.
// Red line from where to where?
g2d.setColor(Color.red);
for (int i = 0; i < points.size(); i = i + 4)
{
g2d.drawLine(
points.get(i).intValue(),
points.get(i + 1).intValue(),
points.get(i + 2).intValue(),
points.get(i + 3).intValue());
}
// Finalize the canvas
g2d.dispose();
// Write the image out as a PNG-format file
try
{
ImageIO.write(img, "png", new File("out.png"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
/**
* Triangle base = (Ax(By-Cy)+Bx(Cy-Ay)+Cx(Ay-By))/2
* #return base for the triangle
*/
public static double base(
double topX , double topY ,
double leftX , double leftY ,
double rightX, double rightY )
{
return ( (topX * (rightY - leftY))
+ (rightX* (leftY - topY))
+ (leftX * (topY - rightY ))) / 2;
}
// *********stuff to do *********** create the recursive function for the
// triangles
private static void sierpinski(
double topX , double topY ,
double leftX , double leftY ,
double rightX, double rightY )
{
if (10 < base(topX, topY, leftX, leftY, rightX, rightY))
{// <--
// that 10 should be a static value or just pulled directly from
// args
// start recursive for all three respective points
/**
*
double leftMidX = (topX + leftX ) / 2;
double leftMidY = (topY + leftY ) / 2;
double rightMidX = (topX + rightX) / 2;
double rightMidY = (topY + rightY) / 2;
*/
double leftMidX = leftX + (topX - leftX) / 2;
double leftMidY = topY + (leftY - topY ) / 2;
double rightMidX = topX + (rightX - topX ) / 2;
double rightMidY = leftMidY ;
// ***these are just called topMid for convenience. they are the mid
// point values to the bottom line of the triangle.
double botMidX = topX ;
double botMidY = leftY ;
/*
double botMidX = (leftX + rightX) / 2;
double botMidY = (leftY + rightY) / 2;
*/
// top ... top stays the same
sierpinski(topX, topY, leftMidX, leftMidY, rightMidX, rightMidY);
// left
sierpinski(leftMidX, leftMidY, leftX, leftY, botMidX, botMidY);
// right
sierpinski(rightMidX, rightMidY, botMidX, botMidY, rightX, rightY);
}
else
{
points.add(topX);
points.add(topY);
points.add(rightX);
points.add(rightY);
points.add(topX);
points.add(topY);
points.add(leftX);
points.add(leftY);
points.add(leftX);
points.add(leftY);
points.add(rightX);
points.add(rightY);
}
}
}
I've started to take interest with making animations(slideshows, backgrounds etc) in Java. I know that JavaFX is much better for doing this, but I'm just to stubborn to bother switching over.
Here is what I got so far.
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BlurredLightCells extends JPanel {
private static final long serialVersionUID = 4610174943257637060L;
private Random random = new Random();
private ArrayList<LightCell> lightcells;
private float[] blurData = new float[500];
public static void main(String[] args) {
JFrame frame = new JFrame("Swing animated bubbles");
frame.setSize(1000, 750);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BlurredLightCells(60));
frame.setVisible(true);
}
public BlurredLightCells(int amtOfBCells) {
setSize(1000, 750);
/**
* Below we initiate all the cells that are going to be drawn on screen
*/
Arrays.fill(blurData, 1f / 20f);
lightcells = new ArrayList<LightCell>(amtOfBCells);
for (int i = 0; i < amtOfBCells; i++) {
/**
* Below we generate all the values for each cell(SHOULD be random for each one)
*/
int baseSpeed = random(0, 3);
int xSpeed = (int) Math.floor((Math.random() * (baseSpeed - -baseSpeed + baseSpeed)) + -baseSpeed);
int ySpeed = (int) Math.round((Math.random() * baseSpeed) + 0.5);
int radius = random(25, 100);
int x = (int) Math.floor(Math.random() * getWidth());
int y = (int) Math.floor(Math.random() * getHeight());
int blurrAmount = (int) (Math.floor(Math.random() * 10) + 5);
int alpha = (int) ((Math.random() * 15) + 3);
/**
* Now we draw a image, and apply transparency and a slight blur to it
*/
Kernel kernel = new Kernel(blurrAmount, blurrAmount, blurData);
BufferedImageOp op = new ConvolveOp(kernel);
BufferedImage circle = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
Graphics2D circlegfx = circle.createGraphics();
circlegfx.setColor(new Color(255, 255, 255, alpha));
circlegfx.fillOval(20, 20, radius, radius);
circle = op.filter(circle, null);
LightCell bubble = new LightCell(x, y, xSpeed, ySpeed, radius, getDirection(random.nextInt(3)), circle);
lightcells.add(bubble);
}
}
public int random(int min, int max) {
final int n = Math.abs(max - min);
return Math.min(min, max) + (n == 0 ? 0 : random.nextInt(n));
}
#Override
public void paint(Graphics g) {
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
long start = System.currentTimeMillis();
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
System.out.println("Took " + (System.currentTimeMillis() - start) + " milliseconds to draw ALL cells.");
repaint();
}
public String getDirection(int i) {
switch (i) {
case 0:
return "right";
case 1:
return "left";
case 2:
return "up";
case 3:
return "down";
}
return "";
}
private class LightCell {
private int x, y, xSpeed, ySpeed, radius;
private String direction;
private BufferedImage image;
public LightCell(int x, int y, int xSpeed, int ySpeed, int radius, String direction, BufferedImage image) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.radius = radius;
this.direction = direction;
this.image = image;
}
public void process(Graphics g) {
switch (direction) {
case "right":
moveRight();
break;
case "left":
moveLeft();
break;
case "up":
moveUp();
break;
case "down":
moveDown();
break;
}
g.drawImage(image, x, y, null);
}
private void moveUp() {
x += xSpeed;
y -= ySpeed;
if (y + (radius / 2) < 0) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = radius + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveDown() {
x += xSpeed;
y += ySpeed;
if (y - (radius / 2) > getHeight()) {
y = 0 - (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveRight() {
x += ySpeed;
y += xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x - radius / 2) > getWidth()) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveLeft() {
x -= ySpeed;
y -= xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x + radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
}
}
If you run that, you will see the cells move at a very high speed, and if you look through the code, you see I call repaint() in the paint method in which I override. I know thats not good to do. But my question is, is their any other way in which I could draw each cell outside of the repaint() loop I have right now, because that causes other components to flash/flicker when I use this in a JFrame with other components.
FYI: Aventually I'd like to achieve something similar to this: Click Here
Thanks!
The issue of flicker is to do with the fact that top level containers are not double buffered. Instead of extending from JFrame (or other top level containers), you should consider using something more like JPanel and override it's paintComponent.
nb- Had it in my head that the OP was extending from JFrame...
Two issues could be causing the flickering. The first is overriding paint, the second is not calling super.paint(g) and the time between the updates. A better solution would be to override paintComponent and make sure you are calling super.paintComponent. Also using something like a javax.swing.Timer to schedule updates and regular intervals would also help...
Only call repaint when you want to encourage the RepaintManager to update you component. Don't call repaint from within any paintXxx method, this will cause a never ending loop of paint requests to schedule onto the event queue, eventually consuiming your CPU
I would avoid doing anything in your paintXxx methods that might take time to perform, this will slow down the rendering process. Instead, I would use a javax.swing.Timer for simple updates or for more complicated processing, a Thread which could be used to update the model before it is rendered to the screen.
This is an example of some simple optimisation process I did to take animation of 500 objects to 4500 with only a slight degration in the overall performance.
Updated
I changed you code slight and it works fine...
I changed your paint method to paintComponent and added super.paintComponent(g)
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
}
And at the end of your constructor I added...
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
To update the UI on a regular bases...
I'm having trouble drawing the smallest arc described by 3 points: the arc center, an "anchored" end point, and a second point that gives the other end of the arc by determining a radius. I used the law of cosines to determine the length of the arc and tried using atan for the starting degree, but the starting position for the arc is off.
I managed to get the arc to lock onto the anchor point (x1,y1) when it's in Quadrant 2, but that will only work when it is in Quadrant 2.
Solutions I can see all have a bunch of if-statements to determine the location of the 2 points relative to each other, but I'm curious if I'm overlooking something simple. Any help would be greatly appreciated.
SSCCE:
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;
class Canvas extends JComponent {
float circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2;
Random random = new Random();
public Canvas() {
//Setup.
x1 = random.nextInt(250);
y1 = random.nextInt(250);
//Cant have x2 == circleX
while (x1 == 150 || y1 == 150)
{
x1 = random.nextInt(250);
y1 = random.nextInt(250);
}
circleX = 150; //circle center is always dead center.
circleY = 150;
//Radius between the 2 points must be equal.
dx = Math.abs(circleX-x1);
dy = Math.abs(circleY-y1);
//c^2 = a^2 + b^2 to solve for the radius
radius = (float) Math.sqrt((float)Math.pow(dx, 2) + (float)Math.pow(dy, 2));
//2nd random point
x2 = random.nextInt(250);
y2 = random.nextInt(250);
//I need to push it out to radius length, because the radius is equal for both points.
dx2 = Math.abs(circleX-x2);
dy2 = Math.abs(circleY-y2);
radius2 = (float) Math.sqrt((float)Math.pow(dx2, 2) + (float)Math.pow(dy2, 2));
dx2 *= radius/radius2;
dy2 *= radius/radius2;
y2 = circleY+dy2;
x2 = circleX+dx2;
//Radius now equal for both points.
}
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL));
Arc2D.Float centerPoint = new Arc2D.Float(150-2,150-2,4,4, 0, 360, Arc2D.OPEN);
Arc2D.Float point1 = new Arc2D.Float(x1-2, y1-2, 4, 4, 0, 360, Arc2D.OPEN);
Arc2D.Float point2 = new Arc2D.Float(x2-2, y2-2, 4, 4, 0, 360, Arc2D.OPEN);
//3 points drawn in black
g.setColor(Color.BLACK);
g.draw(centerPoint);
g.draw(point1);
g.draw(point2);
float start = 0;
float distance;
//Form a right triangle to find the length of the hypotenuse.
distance = (float) Math.sqrt(Math.pow(Math.abs(x2-x1),2) + Math.pow(Math.abs(y2-y1), 2));
//Law of cosines to determine the internal angle between the 2 points.
distance = (float) (Math.acos(((radius*radius) + (radius*radius) - (distance*distance)) / (2*radius*radius)) * 180/Math.PI);
float deltaY = circleY - y1;
float deltaX = circleX - x1;
float deltaY2 = circleY - y2;
float deltaX2 = circleX - x2;
float angleInDegrees = (float) ((float) Math.atan((float) (deltaY / deltaX)) * 180 / Math.PI);
float angleInDegrees2 = (float) ((float) Math.atan((float) (deltaY2 / deltaX2)) * 180 / Math.PI);
start = angleInDegrees;
//Q2 works.
if (x1 < circleX)
{
if (y1 < circleY)
{
start*=-1;
start+=180;
} else if (y2 > circleX) {
start+=180;
start+=distance;
}
}
//System.out.println("Start: " + start);
//Arc drawn in blue
g.setColor(Color.BLUE);
Arc2D.Float arc = new Arc2D.Float(circleX-radius, //Center x
circleY-radius, //Center y Rotates around this point.
radius*2,
radius*2,
start, //start degree
distance, //distance to travel
Arc2D.OPEN); //Type of arc.
g.draw(arc);
}
}
public class Angle implements MouseListener {
Canvas view;
JFrame window;
public Angle() {
window = new JFrame();
view = new Canvas();
view.addMouseListener(this);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBounds(30, 30, 400, 400);
window.getContentPane().add(view);
window.setVisible(true);
}
public static void main(String[] a) {
new Angle();
}
#Override
public void mouseClicked(MouseEvent arg0) {
window.getContentPane().remove(view);
view = new Canvas();
window.getContentPane().add(view);
view.addMouseListener(this);
view.revalidate();
view.repaint();
}
#Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub
}
}
Perhaps this will help. It tests with click and drag to set the two points rather than random numbers. It's considerably simpler than what you were attempting and other solutions posted so far.
Notes:
Math.atan2() is a friend in problems like this.
Little helper functions make it easier to reason about your code.
It's best practice to use instance variables for independent values only and compute the dependent values in local variables.
My code fixes some Swing usage problems like calling Swing functions from the main thread.
Code follows:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
class TestCanvas extends JComponent {
float x0 = 150f, y0 = 150f; // Arc center. Subscript 0 used for center throughout.
float xa = 200f, ya = 150f; // Arc anchor point. Subscript a for anchor.
float xd = 150f, yd = 50f; // Point determining arc angle. Subscript d for determiner.
// Return the distance from any point to the arc center.
float dist0(float x, float y) {
return (float)Math.sqrt(sqr(x - x0) + sqr(y - y0));
}
// Return polar angle of any point relative to arc center.
float angle0(float x, float y) {
return (float)Math.toDegrees(Math.atan2(y0 - y, x - x0));
}
#Override
protected void paintComponent(Graphics g0) {
Graphics2D g = (Graphics2D) g0;
// Can always draw the center point.
dot(g, x0, y0);
// Get radii of anchor and det point.
float ra = dist0(xa, ya);
float rd = dist0(xd, yd);
// If either is zero there's nothing else to draw.
if (ra == 0 || rd == 0) { return; }
// Get the angles from center to points.
float aa = angle0(xa, ya);
float ad = angle0(xd, yd); // (xb, yb) would work fine, too.
// Draw the arc and other dots.
g.draw(new Arc2D.Float(x0 - ra, y0 - ra, // box upper left
2 * ra, 2 * ra, // box width and height
aa, angleDiff(aa, ad), // angle start, extent
Arc2D.OPEN));
dot(g, xa, ya);
// Use similar triangles to get the second dot location.
float xb = x0 + (xd - x0) * ra / rd;
float yb = y0 + (yd - y0) * ra / rd;
dot(g, xb, yb);
}
// Some helper functions.
// Draw a small dot with the current color.
static void dot(Graphics2D g, float x, float y) {
final int rad = 2;
g.fill(new Ellipse2D.Float(x - rad, y - rad, 2 * rad, 2 * rad));
}
// Return the square of a float.
static float sqr(float x) { return x * x; }
// Find the angular difference between a and b, -180 <= diff < 180.
static float angleDiff(float a, float b) {
float d = b - a;
while (d >= 180f) { d -= 360f; }
while (d < -180f) { d += 360f; }
return d;
}
// Construct a test canvas with mouse handling.
TestCanvas() {
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
}
// Listener changes arc parameters with click and drag.
MouseInputAdapter mouseListener = new MouseInputAdapter() {
boolean mouseDown = false; // Is left mouse button down?
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = true;
xa = xd = e.getX();
ya = yd = e.getY();
repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = false;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (mouseDown) {
xd = e.getX();
yd = e.getY();
repaint();
}
}
};
}
public class Test extends JFrame {
public Test() {
setSize(400, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().add(new TestCanvas());
}
public static void main(String[] args) {
// Swing code must run in the UI thread, so
// must invoke setVisible rather than just calling it.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().setVisible(true);
}
});
}
}
package curve;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Main
{
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException
{
PointF pFrom = new PointF(-10f, 30.0f);
PointF pTo = new PointF(-100f, 0.0f);
List<PointF> points = generateCurve(pFrom, pTo, 100f, 7f, true, true);
System.out.println(points);
// Calculate the bounds of the curve
Rectangle2D.Float bounds = new Rectangle2D.Float(points.get(0).x, points.get(0).y, 0, 0);
for (int i = 1; i < points.size(); ++i) {
bounds.add(points.get(i).x, points.get(i).y);
}
bounds.add(pFrom.x, pFrom.y);
bounds.add(pTo.x, pTo.y);
BufferedImage img = new BufferedImage((int) (bounds.width - bounds.x + 50), (int) (bounds.height - bounds.y + 50), BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.translate(25.0f - bounds.getX(), 25.0f - bounds.getY());
g.setStroke(new BasicStroke(1.0f));
g.setColor(Color.DARK_GRAY);
g.drawLine(-1000, 0, 1000, 0);
g.drawLine(0, -1000, 0, 1000);
g.setColor(Color.RED);
for (int i = 0; i < points.size(); ++i) {
if (i > 0) {
Line2D.Float f = new Line2D.Float(points.get(i - 1).x, points.get(i - 1).y, points.get(i).x, points.get(i).y);
System.out.println("Dist : " + f.getP1().distance(f.getP2()));
// g.draw(f);
}
g.fill(new Ellipse2D.Float(points.get(i).x - 0.8f, points.get(i).y - 0.8f, 1.6f, 1.6f));
}
g.setColor(Color.BLUE);
g.fill(new Ellipse2D.Float(pFrom.x - 1, pFrom.y - 1, 3, 3));
g.fill(new Ellipse2D.Float(pTo.x - 1, pTo.y - 1, 3, 3));
g.dispose();
ImageIO.write(img, "PNG", new File("result.png"));
}
static class PointF
{
public float x, y;
public PointF(float x, float y)
{
this.x = x;
this.y = y;
}
#Override
public String toString()
{
return "(" + x + "," + y + ")";
}
}
private static List<PointF> generateCurve(PointF pFrom, PointF pTo, float pRadius, float pMinDistance, boolean shortest, boolean side)
{
List<PointF> pOutPut = new ArrayList<PointF>();
// Calculate the middle of the two given points.
PointF mPoint = new PointF(pFrom.x + pTo.x, pFrom.y + pTo.y);
mPoint.x /= 2.0f;
mPoint.y /= 2.0f;
System.out.println("Middle Between From and To = " + mPoint);
// Calculate the distance between the two points
float xDiff = pTo.x - pFrom.x;
float yDiff = pTo.y - pFrom.y;
float distance = (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
System.out.println("Distance between From and To = " + distance);
if (pRadius * 2.0f < distance) {
throw new IllegalArgumentException("The radius is too small! The given points wont fall on the circle.");
}
// Calculate the middle of the expected curve.
float factor = (float) Math.sqrt((pRadius * pRadius) / ((pTo.x - pFrom.x) * (pTo.x - pFrom.x) + (pTo.y - pFrom.y) * (pTo.y - pFrom.y)) - 0.25f);
PointF circleMiddlePoint = new PointF(0, 0);
if (side) {
circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) + factor * (pTo.y - pFrom.y);
circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) + factor * (pFrom.x - pTo.x);
} else {
circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) - factor * (pTo.y - pFrom.y);
circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) - factor * (pFrom.x - pTo.x);
}
System.out.println("Middle = " + circleMiddlePoint);
// Calculate the two reference angles
float angle1 = (float) Math.atan2(pFrom.y - circleMiddlePoint.y, pFrom.x - circleMiddlePoint.x);
float angle2 = (float) Math.atan2(pTo.y - circleMiddlePoint.y, pTo.x - circleMiddlePoint.x);
// Calculate the step.
float step = pMinDistance / pRadius;
System.out.println("Step = " + step);
// Swap them if needed
if (angle1 > angle2) {
float temp = angle1;
angle1 = angle2;
angle2 = temp;
}
boolean flipped = false;
if (!shortest) {
if (angle2 - angle1 < Math.PI) {
float temp = angle1;
angle1 = angle2;
angle2 = temp;
angle2 += Math.PI * 2.0f;
flipped = true;
}
}
for (float f = angle1; f < angle2; f += step) {
PointF p = new PointF((float) Math.cos(f) * pRadius + circleMiddlePoint.x, (float) Math.sin(f) * pRadius + circleMiddlePoint.y);
pOutPut.add(p);
}
if (flipped ^ side) {
pOutPut.add(pFrom);
} else {
pOutPut.add(pTo);
}
return pOutPut;
}
}
and the use the generateCurve method like this to have a curve between the from and to points..
generateCurve(pFrom, pTo, 100f, 7f, true, false);
Okay, here it is, testing and working. The problems were based on the fact that I don't use graphics much, so I have to remind myself that the coordinate systems are backward, and on the fact that the Javadoc description of the Arc2D constructor is atrocious.
In addition to these, I found that your point creation (for the two points to be connected) was extremely inefficient given the requirements. I had assumed you actually had to receive two arbitrary points and then calculate their angles, etc., but based on what you put on Pastebin, we can define the two points however we please. This benefits us.
Anyway, here's a working version, with none of that gobbledegook from before. Simplified code is simplified:
import javax.swing.JComponent;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;
public class Canvas extends JComponent {
double circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2;
Random random = new Random();
double distance;
private static double theta1;
private static double theta2;
private static double theta;
// private static double radius;
private Point2D point1;
private Point2D point2;
private Point2D center;
private static int direction;
private static final int CW = -1;
private static final int CCW = 1;
public Canvas() {
/*
* You want two random points on a circle, so let's start correctly,
* by setting a random *radius*, and then two random *angles*.
*
* This has the added benefit of giving us the angles without having to calculate them
*/
radius = random.nextInt(175); //your maximum radius is higher, but we only have 200 pixels in each cardinal direction
theta1 = random.nextInt(360); //angle to first point (absolute measurement)
theta2 = random.nextInt(360); //angle to second point
//build the points
center = new Point2D.Double(200, 200); //your frame is actually 400 pixels on a side
point1 = new Point2D.Double(radius * Math.cos(toRadians(theta1)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta1)));
point2 = new Point2D.Double(radius * Math.cos(toRadians(theta2)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta2)));
theta = Math.abs(theta1 - theta2) <= 180 ? Math.abs(theta1 - theta2) : 360 - (Math.abs(theta1 - theta2));
if ((theta1 + theta) % 360 == theta2) {
direction = CCW;
} else {
direction = CW;
}
System.out.println("theta1: " + theta1 + "; theta2: " + theta2 + "; theta: " + theta + "; direction: " + (direction == CCW ? "CCW" : "CW"));
System.out.println("point1: (" + (point1.getX() - center.getX()) + ", " + (center.getY() - point1.getY()) + ")");
System.out.println("point2: (" + (point2.getX() - center.getX()) + ", " + (center.getY() - point2.getY()) + ")");
// Radius now equal for both points.
}
public double toRadians(double angle) {
return angle * Math.PI / 180;
}
public double toDegrees(double angle) {
return angle * 180 / Math.PI;
}
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL));
//centerpoint should be based on the actual center point
Arc2D.Double centerPoint = new Arc2D.Double(center.getX() - 2, center.getY() - 2, 4, 4, 0,
360, Arc2D.OPEN);
//likewise these points
Arc2D.Double point11 = new Arc2D.Double(point1.getX() - 2, point1.getY() - 2, 4, 4, 0, 360,
Arc2D.OPEN);
Arc2D.Double point22 = new Arc2D.Double(point2.getX() - 2, point2.getY() - 2, 4, 4, 0, 360,
Arc2D.OPEN);
// 3 points drawn in black
g.setColor(Color.BLACK);
g.draw(centerPoint);
g.draw(point11);
g.draw(point22);
// Arc drawn in blue
g.setColor(Color.BLUE);
g.draw(new Arc2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius, theta1, theta * direction, Arc2D.OPEN));
}
}
I'm trying to build a User Interface for the RGBike POV:
http://www.instructables.com/id/RGBike-POV-Open-project/
The program will display a bike wheel in form of a grid. The user can click
onto the single squares and changes the colour of these.
I want to build this applet in java. I'm stuck at drawing the wheel in the right way.
I need to have a sort of array of every rectangle, to export the colour later.
The best thing would be to draw a sort of circular table. Drawing each shape
With graphics2D to have each as a single object would be an idea, too. But that would
be around 860 single shapes, little bit too much to update them every time by paint().
Spoke POV has done such a user Interface for their project already:
http://www.ladyada.net/make/spokepov/software.html
But only their old python script is open source.
Be VERY grateful that I have previously generate a "segment" shape in the past ;)
This basically generates each segment individually (does some funky translation into real space) and maintains a cache of shapes which can be checked to see if the mouse falls within there bounds.
This is rather inefficient, but I think you get the idea.
I should also be noted, that I didn't bother with a backing buffer. Not to say it could use one, I just got away without it...
public class TestSpoke {
public static void main(String[] args) {
new TestSpoke();
}
public TestSpoke() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
public static final int CIRCLE_COUNT = 16;
public static final int SEGMENT_COUNT = 80;
private Map<Integer, List<Shape>> mapWheel;
private Map<Point, Color> mapColors;
public TestPane() {
mapColors = new HashMap<>(CIRCLE_COUNT * SEGMENT_COUNT);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Map<Integer, List<Shape>> mapWheel = getWheel();
for (Integer circle : mapWheel.keySet()) {
List<Shape> segments = mapWheel.get(circle);
for (int index = 0; index < segments.size(); index++) {
Shape segment = segments.get(index);
if (segment.contains(e.getPoint())) {
mapColors.put(new Point(circle, index), Color.RED);
repaint();
break;
}
}
}
}
});
}
#Override
public void invalidate() {
mapWheel = null;
super.invalidate();
}
protected float getRadius() {
return Math.min(getWidth(), getHeight());
}
/**
* This builds a wheel (if required) made of segments.
* #return
*/
protected Map<Integer, List<Shape>> getWheel() {
if (mapWheel == null) {
mapWheel = new HashMap<>(CIRCLE_COUNT);
// The current radius
float radius = getRadius();
// The radius of each individual circle...
float circleRadius = radius / CIRCLE_COUNT;
// The range of each segment
float extent = 360f / SEGMENT_COUNT;
for (int circle = 0; circle < CIRCLE_COUNT; circle++) {
float startAngle = 0;
List<Shape> segments = new ArrayList<>(SEGMENT_COUNT);
mapWheel.put(circle, segments);
// Calculate the "translation" to place each segement in the
// center of the screen
float innerRadius = circleRadius * circle;
float x = (getWidth() - innerRadius) / 2;
float y = (getHeight() - innerRadius) / 2;
for (int seg = 0; seg < SEGMENT_COUNT; seg++) {
// Generate a Segment shape
Segment segment = new Segment(circleRadius * circle, circleRadius, startAngle, extent);
startAngle += extent;
// We translate the segment to the screen space
// This will make it faster to paint and check for mouse clicks
PathIterator pi = segment.getPathIterator(AffineTransform.getTranslateInstance(x, y));
Path2D path = new Path2D.Float();
path.append(pi, true);
segments.add(path);
}
}
}
return mapWheel;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Map<Integer, List<Shape>> mapWheel = getWheel();
for (Integer circle : mapWheel.keySet()) {
List<Shape> segments = mapWheel.get(circle);
for (int index = 0; index < segments.size(); index++) {
Shape segment = segments.get(index);
Color color = mapColors.get(new Point(circle, index));
if (color != null) {
g2d.setColor(color);
g2d.fill(segment);
}
g2d.setColor(Color.BLACK);
g2d.draw(segment);
}
}
g2d.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class Segment extends Path2D.Float {
public Segment(float radius, float thickness, float extent) {
this(radius, thickness, 0f, extent);
}
public Segment(float radius, float thickness, float startAngle, float extent) {
// Basically, we want to draw the outter edge from a to b angle,
// draw the connecting line from the outter to the inner,
// draw the inner from b to a angel and
// draw the connecting line from the inner to out the outter
// We want to span about 30 degrees, with a small gap...
// I want the gap to be a factor of the radius
Arc2D.Float outter = new Arc2D.Float(0, 0, radius, radius, startAngle, extent, Arc2D.OPEN);
Arc2D.Float inner = new Arc2D.Float(thickness / 2f, thickness / 2f, radius - thickness, radius - thickness, startAngle + extent, -extent, Arc2D.OPEN);
append(outter, true);
float angel = startAngle + extent;
Point2D p1 = getPointOnEdge(angel, radius);
Point2D p2 = getPointOnEdge(angel, radius - thickness);
// We need to adjust in for the change in the radius
p2.setLocation(p2.getX() + (thickness / 2f), p2.getY() + (thickness / 2f));
lineTo(p2.getX(), p2.getY());
append(inner, true);
angel = startAngle;
p1 = getPointOnEdge(angel, radius);
p2 = getPointOnEdge(angel, radius - thickness);
p2.setLocation(p2.getX() + (thickness / 2f), p2.getY() + (thickness / 2f));
lineTo(p1.getX(), p1.getY());
closePath();
}
public Point2D getPointOnEdge(float angel, float radius) {
angel -= 90;
float x = radius / 2f;
float y = radius / 2f;
double rads = Math.toRadians((angel + 90));
// This determins the length of tick as calculate from the center of
// the circle. The original code from which this derived allowed
// for a varible length line from the center of the cirlce, we
// actually want the opposite, so we calculate the outter limit first
float fullLength = (radius / 2f);
// Calculate the outter point of the line
float xPosy = (float) (x + Math.cos(rads) * fullLength);
float yPosy = (float) (y - Math.sin(rads) * fullLength);
return new Point2D.Float(xPosy, yPosy);
}
}
}