I have a buffered image (width = 66, height = 75) that seems to jitter (as if the image is shaking at a small scale) when trying to translate it when it is at an angle. This is the set up that shows the problem (A and D keys to rotate, W and S keys to translate):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test extends JPanel {
BufferedImage image;
public double angle = 0;
public Point center = new Point(160, 160);
public Test() {
try {
image = ImageIO.read(new File("filePath"));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(image.getWidth() + ", " + image.getHeight());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(1000, 500));
frame.add(this);
this.setBackground(Color.gray);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
double deltaAngle = 3;
int speed = 3;
if(e.getKeyCode() == KeyEvent.VK_A)
angle -= deltaAngle;
else if(e.getKeyCode() == KeyEvent.VK_D)
angle += deltaAngle;
else if(e.getKeyCode() == KeyEvent.VK_W)
center.translate(speed*Math.sin(Math.toRadians(angle)), -speed*Math.cos(Math.toRadians(angle)));
else if(e.getKeyCode() == KeyEvent.VK_S)
center.translate(-speed*Math.sin(Math.toRadians(angle)), speed*Math.cos(Math.toRadians(angle)));
frame.repaint();
}
#Override
public void keyReleased(KeyEvent e) {
}
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2.drawString("Angle: " + Double.toString(angle), 20, 20);
drawImageCentered(g2, rotateImage(image, angle), center);
}
public void drawImageAccurate(Graphics2D g, BufferedImage image, Point location) {
AffineTransform transform = new AffineTransform();
transform.translate(location.x, location.y);
//transform.scale(1, 1);
g.drawImage(image, transform, null);
}
public void drawImageCentered(Graphics2D g, BufferedImage image, Point location) {
drawImageAccurate(g, image, new Point(location.x - image.getWidth()/2.0, location.y - image.getHeight()/2.0));
}
public static BufferedImage rotateImage(BufferedImage image, double angle) {
if(angle == 0 || Double.isNaN(angle))
return image;
else {
angle = Math.toRadians(angle);
double x = Math.abs(Math.cos(angle));
double y = Math.abs(Math.sin(angle));
int width = image.getWidth();
int height = image.getHeight();
int nWidth = (int) Math.floor(width*x + height*y);
int nHeight = (int) Math.floor(height*x + width*y);
BufferedImage rotated = new BufferedImage(nWidth, nHeight, image.getType());
Graphics2D tool = rotated.createGraphics();
AffineTransform transformer = new AffineTransform();
transformer.translate((nWidth - width)/2.0, (nHeight - height)/2.0);
transformer.rotate(angle, width/2, height/2);
tool.drawImage(image, transformer, null);
tool.dispose();
return rotated;
}
}
private class Point {
public double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public void translate(double x, double y) {
this.x += x;
this.y += y;
}
}
public static void main(String[] args) {
new Test();
}
}
I notice that the jittering of the image is more noticeable as the image get smaller in size and when the image is at 87 degrees and other close to "straight" angles. I have used rendering hints:
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
and also an AffineTransform:
public void drawImageAccurate(Graphics2D g, BufferedImage image, Point location) {
AffineTransform transform = new AffineTransform();
transform.translate(location.x, location.y);
//transform.scale(1, 1);
g.drawImage(image, transform, null);
}
Which, I think, lowered the intensity of the jittering but I still can see some small jittering. Maybe my expectations are too high, but I do not think I have ever seen jittering in a 2D game which makes me think there can be more done. Or perhaps some solutions are to increase the movement speed of images or prevent using small images for movement. However, it would nice if there is another direct solution I have not been able to find through searches that could remove this image jittering.
I assume the image quality keeps changing as you keep rotating the image into the various angles repeatedly and each time you may perceive conversion losses.
Probably it is better to draw the image once only by applying one AffineTransform only. This AffineTransform is a contatencation of three single transformations:
translate image center to origin
rotate image acount origin
translate image to actual position
So when keys are pressed you recalculate the AffineTransform and ask for repaint.
When repainting, just draw the image using the existing AffineTransform.
I played around a bit and the code looks like this now.
package com.mycompany.test;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test extends JPanel {
/** rotation speed in radians. */
final double deltaAngle = 3.0d * Math.PI/180.0d;
final int speed = 3;
BufferedImage image;
/** angle in radians. */
public double angle = 0;
public Point center = new Point(160, 160);
AffineTransform at;
private void updateTransform() {
at = AffineTransform.getRotateInstance(angle);
at.concatenate(AffineTransform.getTranslateInstance(-image.getWidth()/2, -image.getHeight()/2));
at.concatenate(AffineTransform.getTranslateInstance(center.x, center.y));
}
public Test() {
try {
image = ImageIO.read(new File("filepath"));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(image.getWidth() + ", " + image.getHeight());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(1000, 500));
frame.add(this);
this.setBackground(Color.gray);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_A)
angle -= deltaAngle;
else if(e.getKeyCode() == KeyEvent.VK_D)
angle += deltaAngle;
else if(e.getKeyCode() == KeyEvent.VK_W)
center.translate(speed*Math.sin(Math.toRadians(angle)), -speed*Math.cos(Math.toRadians(angle)));
else if(e.getKeyCode() == KeyEvent.VK_S)
center.translate(-speed*Math.sin(Math.toRadians(angle)), speed*Math.cos(Math.toRadians(angle)));
updateTransform();
frame.repaint();
}
#Override
public void keyReleased(KeyEvent e) {
}
});
frame.pack();
updateTransform();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2.drawString("Angle: " + Double.toString(angle), 20, 20);
g2.drawImage(image, at, null);
}
private class Point {
public double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public void translate(double x, double y) {
this.x += x;
this.y += y;
}
}
public static void main(String[] args) {
new Test();
}
}
However the artifacts are not fully resolved. This is since a bitmap is rotated - such artifacts are to be expected. Not even scaling down the image helps. The only chance may be to use SVG as source. Check out Apache Batik.
Related
I'm trying to do a simulation program using java swing gui. I have 2 images that are same but one is blurred all the way and other one is normal. And a 3rd image that is just a transparent rectangular frame in .png format. What I want to achieve is that, I will drag the transparent rectangular box over the blurred image and it will reveal the non-blurred image below. How can I achieve this?
P.S: Images are loaded into the program using JLabel onto JLayeredPane and JFrame. Also transparent rectangular box has a mouse listener.
A "overlay" illusion...
Basically this is going to generate a subImage of the un-blurred image based on the current mouse position and view port size, this is then blended with the "overlay effect" and rendered on top of the blurred image, creating the "illusion" of a cutout effect.
The following example will follow the mouse around, exposing a 200x200 pixel area of the image around the mouse cursor.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage blurredImage;
private BufferedImage normalImage;
private BufferedImage spyScopeImage;
private int viewPortSize = 200;
private Point currentLocation;
public TestPane() throws IOException {
blurredImage = ImageIO.read(getClass().getResource("/images/PosterBlurred.png"));
normalImage = ImageIO.read(getClass().getResource("/images/Poster.png"));
spyScopeImage = new BufferedImage(viewPortSize, viewPortSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D masked = spyScopeImage.createGraphics();
Color transparent = new Color(255, 0, 0, 0);
Color fill = Color.RED;
RadialGradientPaint rgp = new RadialGradientPaint(
new Point2D.Double(viewPortSize / 2d, viewPortSize / 2d),
viewPortSize,
new float[]{0f, 0.5f, 1f},
new Color[]{fill, transparent, transparent});
// masked.setComposite(AlphaComposite.DstAtop);
masked.setPaint(rgp);
masked.fill(new Rectangle(0, 0, viewPortSize, viewPortSize));
masked.dispose();
MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
currentLocation = e.getPoint();
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
currentLocation = e.getPoint();
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
currentLocation = null;
repaint();
}
};
addMouseMotionListener(mouseAdapter);
}
#Override
public Dimension getPreferredSize() {
if (blurredImage != null) {
return new Dimension(blurredImage.getWidth(), blurredImage.getHeight());
}
return new Dimension(800, 600);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (blurredImage != null) {
int x = (getWidth() - blurredImage.getWidth()) / 2;
int y = (getHeight() - blurredImage.getHeight()) / 2;
g2d.drawImage(blurredImage, x, y, this);
if (currentLocation != null && normalImage != null) {
int mouseX = currentLocation.x - x;
int mouseY = currentLocation.y - y;
int viewPortOffset = viewPortSize / 2;
int minX = Math.max(0, mouseX - viewPortOffset);
int minY = Math.max(0, mouseY - viewPortOffset);
int maxX = Math.min(normalImage.getWidth(), mouseX + viewPortSize);
int maxY = Math.min(normalImage.getHeight(), mouseY + viewPortSize);
int viewX = minX - viewPortOffset;
int viewY = minY - viewPortOffset;
BufferedImage subimage = normalImage.getSubimage(minX, minY, maxX - minX, maxY - minY);
// Here we're going to "mask" the sub image and the "spy scope" effect together
BufferedImage masked = new BufferedImage(viewPortSize, viewPortSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D mg = masked.createGraphics();
mg.drawImage(subimage, 0, 0, this);
mg.setComposite(AlphaComposite.DstAtop);
mg.drawImage(spyScopeImage, 0, 0, this);
mg.dispose();
g2d.drawImage(masked, x + minX, y + minY, this);
}
}
g2d.dispose();
}
}
}
Alternatively...
You could create a "masked" version of the blurred image with the "scope effect" which exposes the image rendered below it, but agin, this will be expensive, as each time you're creating a new image the size of the original blurred image.
This concept is demonstrated in How to create a transparent shape in the image
BEFORE YOU MARK IT AS DUPLICATE
I have searched a lot in the internet for that and tried every solution, but no one does it the same way I do it. In my case the rotation is in a sperate class.
I have created a java class that inherits JLabel class, in my class I have an arrow BufferedImage which I draw using the paintComponent(Graphics g) method.
I am trying to make the arrow point to a specific point (which I get from a different method) but something goes wrong and the arrow rotates to the wrong direction.
I THINK: it doesn't calculate correctly because the imageLocation is relative to the label.
Here is my code:
package pkg1;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public final class ImageLabel extends JLabel {
private float angle = 0.0f; // in radians
private Point imageLocation = new Point();
private File imageFile = null;
private Dimension imageSize = new Dimension(50, 50);
private BufferedImage bi;
private BufferedImage resizeImage(BufferedImage originalImage, int img_width, int img_height) {
int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
BufferedImage resizedImage = new BufferedImage(img_width, img_height, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, img_width, img_height, null);
g.dispose();
return resizedImage;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (bi == null) {
return;
}
imageLocation = new Point(getWidth() / 2 - bi.getWidth() / 2, getHeight() / 2 - bi.getHeight() / 2);
Graphics2D g2 = (Graphics2D) g;
g2.rotate(angle, imageLocation.x + bi.getWidth() / 2, imageLocation.y + bi.getHeight() / 2);
g2.drawImage(bi, imageLocation.x, imageLocation.y, null);
}
public void rotateImage(float angle) { // rotate the image to specific angle
this.angle = (float) Math.toRadians(angle);
repaint();
}
public void pointImageToPoint(Point target) {
calculateAngle(target);
repaint();
}
private void calculateAngle(Point target) {
// calculate the angle from the center of the image
float deltaY = target.y - (imageLocation.y + bi.getHeight() / 2);
float deltaX = target.x - (imageLocation.x + bi.getWidth() / 2);
angle = (float) Math.atan2(deltaY, deltaX);
if (angle < 0) {
angle += (Math.PI * 2);
}
}
}
Okay, so two things jump out at me...
If you take a Point from outside the context of the label, you will have to translate the point into the components coordinate context
The calculateAngle seems wrong
So starting with...
private void calculateAngle(Point target) {
// calculate the angle from the center of the image
float deltaY = target.y - (imageLocation.y + bi.getHeight() / 2);
float deltaX = target.x - (imageLocation.x + bi.getWidth() / 2);
angle = (float) Math.atan2(deltaY, deltaX);
if (angle < 0) {
angle += (Math.PI * 2);
}
}
angle = (float) Math.atan2(deltaY, deltaX); should be angle = (float) Math.atan2(deltaX, deltaY); (swap the deltas)
You will find that you need to adjust the result by 180 degrees in order to get the image to point in the right direction
angle = Math.toRadians(Math.toDegrees(angle) + 180.0);
Okay, I'm an idiot, but it works :P
I'd also make use of a AffineTransform to translate and rotate the image - personally, I find it easier to deal with.
In the example, I've cheated a little. I set the translation of the AffineTransform to the centre of the component, I then rotate the context around the new origin point (0x0). I then paint the image offset by half it's height/width, thus making it appear as the if the image is been rotated about it's centre - It's late, I'm tired, it works :P
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private ImageLabel label;
public TestPane() {
setLayout(new GridBagLayout());
label = new ImageLabel();
add(label);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
label.pointImageToPoint(e.getPoint(), TestPane.this);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public final class ImageLabel extends JLabel {
private double angle = 0;
private Point imageLocation = new Point();
private File imageFile = null;
private Dimension imageSize = new Dimension(50, 50);
private BufferedImage bi;
public ImageLabel() {
setBorder(new LineBorder(Color.BLUE));
bi = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setColor(Color.RED);
g2d.drawLine(25, 0, 25, 50);
g2d.drawLine(25, 0, 0, 12);
g2d.drawLine(25, 0, 50, 12);
g2d.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(bi.getWidth(), bi.getHeight());
}
protected Point centerPoint() {
return new Point(getWidth() / 2, getHeight() / 2);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (bi == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = g2d.getTransform();
Point center = centerPoint();
at.translate(center.x, center.y);
at.rotate(angle, 0, 0);
g2d.setTransform(at);
g2d.drawImage(bi, -bi.getWidth() / 2, -bi.getHeight() / 2, this);
g2d.dispose();
}
public void rotateImage(float angle) { // rotate the image to specific angle
this.angle = (float) Math.toRadians(angle);
repaint();
}
public void pointImageToPoint(Point target, JComponent fromContext) {
calculateAngle(target, fromContext);
repaint();
}
private void calculateAngle(Point target, JComponent fromContext) {
// calculate the angle from the center of the image
target = SwingUtilities.convertPoint(fromContext, target, this);
Point center = centerPoint();
float deltaY = target.y - center.y;
float deltaX = target.x - center.x;
angle = (float) -Math.atan2(deltaX, deltaY);
angle = Math.toRadians(Math.toDegrees(angle) + 180.0);
repaint();
}
}
}
I just want to add that using a JLabel for this purpose is overkill, a simple JPanel or JComponent would do the same job and carry a lot less overhead with it, just saying
I have the below code that draws an ellipse when the user drags the mouse, however I only want a circle to be drawn and not an ellipse also.
I've tried a few things but haven't quite got it to work, from what I understand it places the circle inside a rectangle, so if I can lock that so it places it inside a square then I should get what I want? The code may not be the most efficient, I'm just trying to write this for a project I'm working, to be clear this is not school homework or anything. If I can get at least this circle aspect of the code working it will save me a lot of time in my project
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Line2D;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class DrawingBoardWithMatrix extends JFrame {
public static void main(String[] args) {
DrawingBoardWithMatrix drawingBoardWithMatrix = new DrawingBoardWithMatrix();
}
public DrawingBoardWithMatrix() {
this.setSize(600, 600);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new PaintSurface(), BorderLayout.CENTER);
this.setVisible(true);
}
private class PaintSurface extends JComponent {
ArrayList<Shape> shapes = new ArrayList<>();
Point startDrag, endDrag;
public PaintSurface() {
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
startDrag = new Point(e.getX(), e.getY());
endDrag = startDrag;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
Shape r = makeCircle(startDrag.x, startDrag.y, e.getX(), e.getY());
shapes.add(r);
startDrag = null;
endDrag = null;
repaint();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
endDrag = new Point(e.getX(), e.getY());
repaint();
}
});
}
private void paintBackground(Graphics2D g2){
g2.setPaint(Color.LIGHT_GRAY);
for (int i = 0; i < getSize().width; i += 10) {
Shape line = new Line2D.Float(i, 0, i, getSize().height);
g2.draw(line);
}
for (int i = 0; i < getSize().height; i += 10) {
Shape line = new Line2D.Float(0, i, getSize().width, i);
g2.draw(line);
}
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
paintBackground(g2);
g2.setStroke(new BasicStroke(2));
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.50f));
for (Shape s : shapes) {
g2.setPaint(Color.BLACK);
g2.draw(s);
// g2.setPaint(colors[(colorIndex++) % 6]);
// g2.fill(s);
}
if (startDrag != null && endDrag != null) {
g2.setPaint(Color.LIGHT_GRAY);
Shape r = makeCircle(startDrag.x, startDrag.y, endDrag.x, endDrag.y);
g2.draw(r);
}
}
private Ellipse2D.Float makeCircle(int x1, int y1, int x2, int y2) {
return new Ellipse2D.Float(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
}
}
}
Your makeCircle() draws an ellipse with width Math.abs(x1 - x2) and height Math.abs(y1 - y2). If you want to draw a circle, the width and height must be equal. There are several ways to accomplish this:
Use Math.abs(x1 - x2) for both width and height
Use Math.abs(y1 - y2) for both width and height
Decide between the two based on the mouse position
However, I am very concerned about your statement "I just don't have the time right now to learn Java properly" - this will come back to bite you. If you have any intention of having programming as a serious hobby or potentially a job down the line, you must expend a lot of effort to properly understand what you are doing.
I'm trying to make a paint program, and this class is the main area where you drag your mouse to paint. The problem is the clip has to be rectangular, so any other lines within that rectangle of the clip (the clip gets bigger the faster you move) will get covered by the new clip, however the new clip isn't all needed.
My ideas of solutions are:
To somehow set the clip to a line (but I think that clip would have to be set in the repaint method, not the setClip() in paint component)
To save the image currently on the paint component and set it to the backgroud
Possibly set the ocupancy of the clip lower in the areas without the line?
Thank you for looking at it, here is the code (with some parts left out for simpler reading) and if you know a solution I would love to hear it. Thanks!
public class Canvas extends JPanel implements MouseMotionListener, MouseListener{
int sizeX, sizeY;
String title;
int[] backColor = new int[3];
int brushSize=20;
Point currentP = new Point();
Point pastP = new Point();
Point paintP = new Point();
int diffX, diffY;
boolean initialize=true;
boolean initClip=true;
Canvas(){
backColor[0] = newProject.colorA;
backColor[1] = newProject.colorB;
backColor[2] = newProject.colorC;
if(backColor[0]>=255){
backColor[0]=255;
}
if(backColor[1]>=255){
backColor[1]=255;
}
if(backColor[2]>=255){
backColor[2]=255;
}
sizeX = newProject.sizeX;
sizeY = newProject.sizeY;
//System.out.println(sizeX + " " + sizeY);
setSize(sizeX,sizeY);
setBackground(Color.white);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(brushSize));
if(initialize){
g.setColor(new Color(backColor[0], backColor[1], backColor[2]));
g.fillRect(0, 0, sizeX, sizeY);
g.setColor(Color.red);
g.drawRect(0,0,50,50);
System.out.println("Initialize");
}
else{
g2.drawLine(currentP.x, currentP.y, pastP.x,pastP.y);
}
//System.out.println("Paint");
}
#Override
public void mouseDragged(MouseEvent e) {
if(initClip) //if mouse has been released since last dragged
currentP = e.getPoint(); //This causes PastP and CurrentP to be equal
initClip=false; //since pastP is set equal to CurrentP afterward
pastP = currentP;
currentP = e.getPoint();
diffX=Math.abs(currentP.x-pastP.x); //find the differences to find how big of
diffY=Math.abs(currentP.y-pastP.y); //a clip it needs
if(diffX==0){ //if no movement, set it to brush size so the
diffX=brushSize; //clip shows up
}
if(diffY==0){
diffY=brushSize;
}
initialize=false;
if(currentP.x-pastP.x>0){ //figures out which direction it moved
paintP.x=pastP.x; //sets the clip variable to the correct corner
}
else{
paintP.x=currentP.x;
}
if(currentP.y-pastP.y>0){
paintP.y=pastP.y;
}
else{
paintP.y=currentP.y;
}
System.out.println(paintP);
repaint(paintP.x, paintP.y, diffX, diffY); //repaint with point PaintP and the
//difference it moved
}
#Override
public void mouseReleased(MouseEvent arg0) {
initClip=true;
}
I'm not sure why you would bother. Each time paintComponent is called by the paint system, you are expected to repaint the entire component anyway.
Instead, simply paint what you need to paint, then paint the selection on top of it...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawSelection {
public static void main(String[] args) {
new DrawSelection();
}
public DrawSelection() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage background;
private Rectangle clipRect;
public TestPane() {
try {
background = ImageIO.read(new File("/path/to/your/image"));
} catch (IOException ex) {
ex.printStackTrace();
}
MouseAdapter ma = new MouseAdapter() {
private Point cp;
#Override
public void mousePressed(MouseEvent e) {
cp = e.getPoint();
clipRect = null;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
cp = null;
}
#Override
public void mouseDragged(MouseEvent e) {
Point p = e.getPoint();
int x = Math.min(p.x, cp.x);
int y = Math.min(p.y, cp.y);
int width = Math.max(p.x, cp.x) - x;
int height = Math.max(p.y, cp.y) - y;
clipRect = new Rectangle(x, y, width, height);
repaint();
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
#Override
public Dimension getPreferredSize() {
return background == null ? new Dimension(200, 200) : new Dimension(background.getWidth(), background.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (background != null) {
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.drawImage(background, x, y, this);
}
if (clipRect != null) {
g2d.setColor(UIManager.getColor("List.selectionBackground"));
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.fill(clipRect);
}
g2d.dispose();
}
}
}
If you want to optimise the paint process, why not draw the parts of the image to a backing buffer and simply paint the buffer, then paint the selection or other dynamic parts on top of it within the paintComponent?
I am working on a circular health bar for a top-down 2d shooter and would like some opinions of the best way to do this in only graphics2D. Wondering if drawing many arcs or just using images would be best.
The answer depends on what you want to achieve...
This is a very basic example...
You can go backwards
Even start at a different angle.
With a little bit of work you could reduce the extended so you could have an "open" progress, for example, starting at 245 degrees with a total extent of 270 degrees if you wanted to
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Arc2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class CircleProgress {
public static void main(String[] args) {
new CircleProgress();
}
public CircleProgress() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new ProgressPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ProgressPane extends JPanel {
private float progress;
private float startAt = 270;
private float direction = -1;
private Timer timer;
public ProgressPane() {
timer = new Timer(80, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
float value = getProgress();
if (value >= 1f) {
timer.stop();
}
setProgress(value += 0.01);
}
});
}
public void setProgress(float progress) {
if (progress < 0.0) {
progress = 0;
} else if (progress > 1f) {
progress = 1f;
}
this.progress = progress;
repaint();
}
public float getProgress() {
return progress;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
float extent = (360f * getProgress()) * direction;
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height);
int x = (width - radius) / 2;
int y = (height - radius) / 2;
g2d.setColor(Color.YELLOW);
Arc2D arc = new Arc2D.Double(x, y, radius, radius, startAt, extent, Arc2D.PIE);
g2d.fill(arc);
g2d.dispose();
}
}
}