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
Related
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.
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
#Override
public Shape getShape() {
final Ellipse2D.Double result = new Ellipse2D.Double();
final double px = Math.min(getStart().getX(), getEnd().getX());
final double py = Math.min(getStart().getY(), getEnd().getY());
final double pw = Math.abs(getStart().getX() - getEnd().getX());
result.setFrame(px, py, pw, pw);
return result;
}
So this getShape() is returning the shape to a class that draws the shape. getStart() gets the starting Point of the mouse on click, and getEnd() gets the Point at mouse release. Now, when I drag to draw a circle, if i drag to the right or down the circle works as intended and expands to the mouse, if I drag up or left of the cursor the circle expands as it should BUT the circle shape moves up and down with the cursor and I am not sure why.
Shapes in Java are based on the top/left corner as the anchor and the width/height been drawn down/right.
You need to calculate the bounding box between the click point and the drag point
public Shape getShape() {
final Ellipse2D.Double result = new Ellipse2D.Double();
final double px = Math.min(getStart().getX(), getEnd().getX());
final double py = Math.min(getStart().getY(), getEnd().getY());
final double pw = Math.abs(getStart().getX() - getEnd().getX());
result.setFrame(px, py, pw, pw);
return result;
}
The problem is, you're still using the difference between the click point and the drag point to calculate the width, pw should be the difference between the maxX and minX values
So this example shows you how to calculate the anchor and size properties
I read through that post, but it doesn't solve the dragging expansion of the circle, and how it moves up and down.
Then you're doing something wrong
I am trying to get a circle drawn not a circle without the same width and height(ellipse).
Okay, so it should always appear that the circle is been draw from the anchor point, so when the minX or minY is less than the clickPoint's x/y points, then you need to adjust them as a difference of the clickPoint and the size
So, this will make it "appear" as if the circle is always been drawn from the intial click point
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.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SelectionExample {
public static void main(String[] args) {
new SelectionExample();
}
public SelectionExample() {
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 Point clickPoint;
private Shape shape;
private Rectangle box;
public TestPane() {
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int minX = Math.min(e.getX(), clickPoint.x);
int minY = Math.min(e.getY(), clickPoint.y);
int maxX = Math.max(e.getX(), clickPoint.x);
int maxY = Math.max(e.getY(), clickPoint.y);
box = new Rectangle(minX, minY, maxX - minX, maxY - minY);
int size = Math.min(maxX - minX, maxY - minY);
if (minX < clickPoint.x) {
minX = clickPoint.x - size;
}
if (minY < clickPoint.y) {
minY = clickPoint.y - size;
}
shape = new Ellipse2D.Double(minX, minY, size, size);
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
clickPoint = new Point(e.getPoint());
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (shape != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(0, 0, 255, 64));
g2d.fill(shape);
g2d.setColor(Color.BLUE);
g2d.draw(shape);
g2d.draw(box);
g2d.dispose();
}
}
}
}
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?
Language: Java.
Hi, I need to prevent drawing over the same location of a Graphics2D more than once. For example, if the user draws using a BasicStroke with a round cap and a width of 10 over a certain area, that same area cannot be drawn on a second time.
The reason I want to do this is so that the user can draw (free-hand) translucent colours over an image without drawing over the same stroke (thus increasing the density of the colour and reducing its translucency).
I've tried storing the shapes of all the strokes made by the user (as Area objects that subtract the shape) and then clipping the Graphics2D by the intersection of all those Area objects.
This almost works, but the 'shape' obtained by the clip is not quite the same as the 'shape' drawn by the stroke - it is out by a couple of pixels.
Does anyone have any other ideas that might work?
The concept is relatively simple, you need to have multiple layers onto which you can render...
There are multiple different ways to approach the problem. You could maintain a list of Points and on each paint cycle, render these points to a backing buffer, which you would then draw over the main content using a AlphaComposite.
You could (as this example does) draw directly to the backing buffer and repaint the content, again, using a AlphaComposite to render the higher layer.
You could have any number of layers...
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
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.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class PaintOver {
public static void main(String[] args) {
new PaintOver();
}
public PaintOver() {
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 MapPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MapPane extends JPanel {
private BufferedImage background;
private BufferedImage foreground;
public MapPane() {
try {
background = ImageIO.read(getClass().getResource("/TreasureMap.png"));
foreground = new BufferedImage(background.getWidth(), background.getHeight(), BufferedImage.TYPE_INT_ARGB);
} catch (Exception e) {
e.printStackTrace();
}
MouseAdapter mouseHandler = new MouseAdapter() {
private Point startPoint;
#Override
public void mousePressed(MouseEvent e) {
startPoint = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e) {
startPoint = null;
}
#Override
public void mouseDragged(MouseEvent e) {
Point endPoint = e.getPoint();
Graphics2D g2d = foreground.createGraphics();
Point offset = getOffset();
Point from = new Point(startPoint);
from.translate(-offset.x, -offset.y);
Point to = new Point(endPoint);
to.translate(-offset.x, -offset.y);
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(new Line2D.Float(from, to));
g2d.dispose();
startPoint = endPoint;
repaint();
}
};
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
}
#Override
public Dimension getPreferredSize() {
return background == null ? super.getPreferredSize() : new Dimension(background.getWidth(), background.getHeight());
}
protected Point getOffset() {
Point p = new Point();
if (background != null) {
p.x = (getWidth() - background.getWidth()) / 2;
p.y = (getHeight() - background.getHeight()) / 2;
}
return p;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background != null) {
Graphics2D g2d = (Graphics2D) g.create();
Point offset = getOffset();
g2d.drawImage(background, offset.x, offset.y, this);
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.drawImage(foreground, offset.x, offset.y, this);
g2d.dispose();
}
}
}
}