Drawing a nice circle in Java - java

I'm using Java Graphics and I keep getting "ugly" circles.
Here's what my Java program makes
And here's the same thing being made in Matlab
I think it is clear that the Java one is not as "nice" looking as the Matlab one, particularly on the edges of the circle. Note that this has nothing to do with the resolution...these images are practically the same size. Also note that I am already setting rendering hints.
Here's a stand alone with a Main function you can run to test this out.
package test;
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.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class SimplePaint02 {
private static final int LINE_THICKNESS = 4;
private static final int LINE_GAP = 10;
private Color lineColor = Color.red;
public static void main(String[] args) {
new SimplePaint02();
}
public SimplePaint02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame();
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 {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
#Override
public void paintComponent(Graphics g) {
int radius = 50;
BufferedImage buffer = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffer.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
Ellipse2D circle = new Ellipse2D.Float(0, 0, radius,radius);
Shape clip = g2d.getClip();
g2d.setClip(circle);
AffineTransform at = g2d.getTransform();
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45),radius / 2, radius / 2));
int gap = LINE_GAP;
g2d.setColor(Color.WHITE);
g2d.fill(circle);
g2d.setColor(lineColor);
//g2d.setStroke(new BasicStroke(LINE_THICKNESS));
for (int index = 0; index < 10; index++) {
int x1 = index*gap-(LINE_THICKNESS/2);
int y1 = 0;
int x2 = index*gap+(LINE_THICKNESS/2);
int y2 = radius;
int width = x2 - x1;
int height = y2 - y1;
g2d.fillRect(x1, y1, width, height);
//g2d.drawLine(index * gap, 0, index * gap, getRadius());
}
g2d.setTransform(at);
g2d.setClip(clip);
g2d.dispose();
g.drawImage(buffer, 0, 0, this);
}
}
}

EDIT: Please see Code Guy's answer below for a solution. This is marked correct because it was Joey Rohan who figured it out initially!
I got smooth edge when i tried out same thing:
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class DrawSmoothCircle {
public static void main(String[] argv) throws Exception {
BufferedImage bufferedImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.green);
g2d.fillOval(10, 10, 50, 50);
g2d.dispose();
ImageIO.write(bufferedImage, "png", new File("e:\\newimage.png"));
}
}
UPDATE:
After searching alot:
There is nothing wrong with the code but,
Well, unfortunately Java 2D (or at least Sun's current implementation) does not support "soft clipping."
But Also got a trick for the clips:
Follow This link,you can achieve what you are asking for.
(Also, i got a smooth edge, cause i din't use clip stuff,in my above image)

Here was the answer. I adapted the majority of the code from this site. Take a look:
Here's the code:
public void paintComponent(Graphics g) {
// Create a translucent intermediate image in which we can perform
// the soft clipping
GraphicsConfiguration gc = ((Graphics2D) g).getDeviceConfiguration();
BufferedImage img = gc.createCompatibleImage(getWidth(), getHeight(), Transparency.TRANSLUCENT);
Graphics2D g2 = img.createGraphics();
// Clear the image so all pixels have zero alpha
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, getWidth(), getHeight());
// Render our clip shape into the image. Note that we enable
// antialiasing to achieve the soft clipping effect. Try
// commenting out the line that enables antialiasing, and
// you will see that you end up with the usual hard clipping.
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fillOval(0, 0, getRadius(), getRadius());
// Here's the trick... We use SrcAtop, which effectively uses the
// alpha value as a coverage value for each pixel stored in the
// destination. For the areas outside our clip shape, the destination
// alpha will be zero, so nothing is rendered in those areas. For
// the areas inside our clip shape, the destination alpha will be fully
// opaque, so the full color is rendered. At the edges, the original
// antialiasing is carried over to give us the desired soft clipping
// effect.
g2.setComposite(AlphaComposite.SrcAtop);
g2.setColor(lineColor);
int gap = LINE_GAP;
AffineTransform at = g2.getTransform();
g2.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45),getRadius() / 2, getRadius() / 2));
for (int index = 0; index < 10; index++) {
int x1 = index*gap-(LINE_THICKNESS/2);
int y1 = 0;
int x2 = index*gap+(LINE_THICKNESS/2);
int y2 = getRadius();
int width = x2 - x1;
int height = y2 - y1;
g2.fillRect(x1, y1, width, height);
}
g2.setTransform(at);
g2.dispose();
// Copy our intermediate image to the screen
g.drawImage(img, 0, 0, null);
}

Update
OK. Then the idea is to not use clipping at all but instead to make the clipped shape by subtracting areas from each other.
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class SimplePaint02 {
private static final int LINE_THICKNESS = 4;
private static final int LINE_GAP = 10;
private Color lineColor = Color.red;
public static void main(String[] args) {
new SimplePaint02();
}
public SimplePaint02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame();
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 {
int radius = 75;
#Override
public Dimension getPreferredSize() {
return new Dimension(radius, radius);
}
#Override
public void paintComponent(Graphics g) {
Ellipse2D circle = new Ellipse2D.Float(0, 0, radius, radius);
Area lines = new Area();
int gap = LINE_GAP;
for (int index = 0; index < 10; index++) {
int x1 = index * gap - (LINE_THICKNESS / 2);
int y1 = 0;
int x2 = index * gap + (LINE_THICKNESS / 2);
int y2 = radius;
int width = x2 - x1;
int height = y2 - y1;
Shape lineShape = new Rectangle2D.Double(x1, y1, width, height);
lines.add(new Area(lineShape));
//g3d.fillRect(x1, y1, width, height);
//g2d.drawLine(index * gap, 0, index * gap, getRadius());
}
//g2d.setClip(circle);
Area circleNoLines = new Area(circle);
circleNoLines.subtract(lines);
Area linesCutToCircle = new Area(circle);
linesCutToCircle.subtract(circleNoLines);
//g2d.setTransform(at);
BufferedImage buffer = new BufferedImage(radius * 2, radius * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffer.createGraphics();
RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45), radius / 2, radius / 2));
g2d.setRenderingHints(rh);
g2d.setColor(Color.ORANGE);
g2d.fill(linesCutToCircle);
g2d.setColor(Color.RED);
g2d.fill(circleNoLines);
g2d.dispose();
g.drawImage(buffer, 0, 0, this);
}
}
}
Old code
Part of the problem is that the rendering operations typically do not apply to a Clip, though they will apply to the Shape when it is drawn. I generally solve that by (last) painting the Shape itself. E.G.
A 1.5 pixel BasicStroke is used here for the red circle - smoothing the rough edges produced by the Clip.
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class SimplePaint02 {
private static final int LINE_THICKNESS = 4;
private static final int LINE_GAP = 10;
private Color lineColor = Color.red;
public static void main(String[] args) {
new SimplePaint02();
}
public SimplePaint02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame();
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 {
int radius = 75;
#Override
public Dimension getPreferredSize() {
return new Dimension((int)(1.1*radius), (int)(1.1*radius));
}
#Override
public void paintComponent(Graphics g) {
BufferedImage buffer = new BufferedImage(radius*2, radius*2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffer.createGraphics();
RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
rh.put(RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_ENABLE);
rh.put(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHints(rh);
Ellipse2D circle = new Ellipse2D.Float(0, 0, radius,radius);
Shape clip = g2d.getClip();
g2d.setClip(circle);
AffineTransform at = g2d.getTransform();
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45),radius / 2, radius / 2));
int gap = LINE_GAP;
g2d.setColor(Color.WHITE);
g2d.fill(circle);
g2d.setColor(lineColor);
//g2d.setStroke(new BasicStroke(LINE_THICKNESS));
for (int index = 0; index < 10; index++) {
int x1 = index*gap-(LINE_THICKNESS/2);
int y1 = 0;
int x2 = index*gap+(LINE_THICKNESS/2);
int y2 = radius;
int width = x2 - x1;
int height = y2 - y1;
g2d.fillRect(x1, y1, width, height);
//g2d.drawLine(index * gap, 0, index * gap, getRadius());
}
g2d.setTransform(at);
g2d.setClip(clip);
g2d.setClip(null);
g2d.setStroke(new BasicStroke(1.5f));
g2d.draw(circle);
g2d.dispose();
g.drawImage(buffer, 0, 0, this);
}
}
}

I used drawPolygon method to draw circle by generating array of most of the points on circumference of circle with proposed radius.
Code:
import java.awt.*;
import java.applet.*;
/*<applet code="OnlyCircle" width=500 height=500>
</applet>*/
public class OnlyCircle extends Applet{
public void paint(Graphics g){
int r=200;//radius
int x1=250;//center x coordinate
int y1=250;//center y coordinate
double x2,y2;
double a=0;
double pi=3.14159;
int count=0;
int i=0;
int f=0;
int[] x22=new int[628319];
int[] y22=new int[628319];
while(a<=2*pi&&i<628319&&f<628319)
{
double k=Math.cos(a);
double l=Math.sin(a);
x2=x1+r*k;
y2=y1+r*l;
x22[i]=(int)x2;
y22[f]=(int)y2;
i++;
f++;
a+=0.00001;
}
int length=x22.length;
g.drawPolygon(x22,y22,length);
}
}

You can enable anti-aliasing:
Graphics2D g2 = (Graphics2D) g;
Map<RenderingHints.Key, Object> hints = new HashMap<RenderingHints.Key, Object>();
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHints(hints);
I also suggest you draw to the Graphics object you get from the paintComponent method rather than creating an intermediate BufferedImage.

Related

How can I darken a rectangular part of an image in Java?

The code creates a JFrame with a JPanel onto which it draws an image loaded from a file. The objective is to make a rectangular area of the picture, such as for example the red square, appear darker than the rest. I'm assuming this may involve taking a subimage of the image, looping through an array of pixels, scaling them, and then painting that subimage onto the JPanel, but I don't know how to do this using the Java API.
package SpriteEditor_Tests;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public class ImageTestApp extends JFrame
{
public BufferedImage image;
int x1 = 50;
int x2 = 100;
int y1 = 50;
int y2 = 100;
public static void main (String [] args)
{
new ImageTestApp();
}
public ImageTestApp()
{
setTitle("Image Test App");
try
{
image = ImageIO.read(new File("C:/Users/Paul/Desktop/derp.png"));
}
catch (IOException io)
{
System.out.println("IO caught"); System.exit(0);
}
setSize(500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
add(new ImageDisplay());
}
class ImageDisplay extends JPanel
{
public void paintComponent(Graphics g)
{
g.drawImage(image, -100, -100, getWidth(), getHeight(), Color.RED, null);
g.setColor(Color.RED);
g.drawRect(x1, y1, Math.abs(x2 - x1), Math.abs(y2 - y1));
}
}
}
A "simple" solution would be to just create a new instance of Color with the desired alpha applied to it and fill the area you want darkened.
This is great if you have a color you want to use, but when I want to use a predefined color, it's not as simple. Instead, I prefer to use an AlphaComposite as it gives me some advantages.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage background;
public TestPane() {
try {
background = ImageIO.read(getClass().getResource("/images/background.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
if (background == null) {
return new Dimension(200, 200);
}
return new Dimension(background.getWidth(), background.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(background, 0, 0, this);
int x = (getWidth() - 100) / 2;
int y = (getHeight() - 100) / 2;
Rectangle rect = new Rectangle(x, y, 200, 200);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2d.fill(rect);
g2d.dispose();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawRect(x, y, 200, 200);
g2d.dispose();
}
}
}
Now, if want to generate a new image with the are darkened, you can follow the same basic concept, but instead of painting to the components Graphics context, you'd paint directly to the BufferedImages Graphics content. This is the wonderful power of the abstract nature of the Graphics API.
Don't forget, when you override a method, you are obliged to either over take ALL of its responsibilities or call its super implementation.
paintComponent does some basic, but important work and you should make sure to call super.paintComponent before you start performing your custom painting, this will just reduce any possibility of issues.
Darken each pixel individually
Okay, if, instead, you want to darken each pixel in the rectangle individually, this becomes a "little" more complicated, but not hard.
After a lot of time and testing, I settled on using the follow algorithm to darken a given color. This will push the color towards "black" the more you darken it, which some algorithms don't do.
public static Color darken(Color color, double fraction) {
int red = (int) Math.round(Math.max(0, color.getRed() - 255 * fraction));
int green = (int) Math.round(Math.max(0, color.getGreen() - 255 * fraction));
int blue = (int) Math.round(Math.max(0, color.getBlue() - 255 * fraction));
int alpha = color.getAlpha();
return new Color(red, green, blue, alpha);
}
Then, all you have to do is get the the color of the pixel, darken it and reapply.
For this example, I actually use a separate sub image, but you can do it directly to the parent image
BufferedImage subImage = background.getSubimage(x, y, 200, 200);
for (int row = 0; row < subImage.getHeight(); row++) {
for (int col = 0; col < subImage.getWidth(); col++) {
int packedPixel = subImage.getRGB(col, row);
Color color = new Color(packedPixel, true);
color = darken(color, 0.5);
subImage.setRGB(col, row, color.getRGB());
}
}
Now, before someone jumps down my throat, no, this is not the most performant approach, but it gets over messing about with "packed" pixel values (because I can never remember how to unpack those :P) and most of my API code is based around the use of Color anyway
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public static Color darken(Color color, double fraction) {
int red = (int) Math.round(Math.max(0, color.getRed() - 255 * fraction));
int green = (int) Math.round(Math.max(0, color.getGreen() - 255 * fraction));
int blue = (int) Math.round(Math.max(0, color.getBlue() - 255 * fraction));
int alpha = color.getAlpha();
return new Color(red, green, blue, alpha);
}
public class TestPane extends JPanel {
private BufferedImage background;
private BufferedImage darkended;
public TestPane() {
try {
background = ImageIO.read(getClass().getResource("/images/background.jpg"));
int x = (background.getWidth() - 100) / 2;
int y = (background.getHeight() - 100) / 2;
BufferedImage subImage = background.getSubimage(x, y, 200, 200);
for (int row = 0; row < subImage.getHeight(); row++) {
for (int col = 0; col < subImage.getWidth(); col++) {
int packedPixel = subImage.getRGB(col, row);
Color color = new Color(packedPixel, true);
color = darken(color, 0.5);
subImage.setRGB(col, row, color.getRGB());
}
}
darkended = subImage;
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
if (background == null) {
return new Dimension(200, 200);
}
return new Dimension(background.getWidth(), background.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(background, 0, 0, this);
int x = (getWidth() - 100) / 2;
int y = (getHeight() - 100) / 2;
g2d.drawImage(darkended, x, y, this);
g2d.setColor(Color.RED);
g2d.drawRect(x, y, 200, 200);
g2d.dispose();
}
}
}

java, rotate BufferedImage with mouse angle

I have been searching all over the internet for a simple way to just rotate a sprite following an angle.
The objective is to have a weapon sprite following the mouse by rotating at the centre of the screen (Top-down shooter in Java). I have tried different things:
NB: The render(Graphics g) function is inside my Gun.java class and uses g, the Graphics element I use to paint on the canvas of the game. The image is the BufferedImage containing the original sprite. And reticle.getAngle() is giving the angle made by the mouse considering the centre of the screen as the origin of the frame.
Attempt 1
public void render(Graphics g) {
// create a new BufferedImage with the image of the gun on it
BufferedImage rotatedImage = new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics gRotatedImage = rotatedImage.getGraphics();
gRotatedImage.drawImage(image, 0, 0, null);
// rotate this gun image in the direction of shoot
private AffineTransform at = new AffineTransform();
at.rotate(reticle.getAngle() + Math.PI,
rotatedImage.getWidth()/2, rotatedImage.getHeight()/2);
AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
rotatedImage = op.filter(rotatedImage, null);
// finally display the rotated version of the gun image
g.drawImage(rotatedImage,
(int)(handler.getDisplay().getWidth()/2 - rotatedImage.getWidth()/2),
(int)(handler.getDisplay().getHeight()/2 - rotatedImage.getHeight()/2),
rotatedImage.getWidth(), rotatedImage.getHeight(), null);
}
With this solution, from java2s.com, I end up with the sprite being displayed at the centre and rotating but more like a helicopter... It keeps rotating not following the mouse.
I also tested all the solutions from the related StackOverflow question. This time, I get the weapon being displayed at the top left corner following the mouse, but I can't find a way to place it at the centre of the screen. I tried translations but then the sprite image rotates considering the top left corner as the centre.
Attempt 2
public void render(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
double rotation = 0f;
int width = image.getWidth() - 1;
int height = image.getHeight() - 1;
rotation = reticle.getAngle();
rotation = Math.toDegrees(rotation) + 180;
g2d.rotate(Math.toRadians(rotation), width / 2, height / 2);
// g2d.translate(handler.getDisplay().getWidth()/2, handler.getDisplay().getHeight()/2);
g2d.drawImage(image, 0, 0, null);
int x = width / 2;
int y = height / 2;
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.RED);
g2d.drawLine(x, y, x, y - height / 4);
g2d.dispose();
}
I just would like to rotate my sprite every tick of the game by the angle provided by reticle.getAngle() which I know is good. I feel really lost on how to use Graphics2D or AffineTransform to perform rotation. Can someone provide an example on how to rotate a sprite following the mouse and then display it at the centre of the screen?
What is the best way to rotate an image which we then want to display at the centre of the screen?
I don't know if this helps, but I wrote this example program, which has a rectangle (works the same as an image really...) at the center of the screen which rotates following your mouse. The important stuff is in the paintComponent method, pretty much everything else is setup.
package main;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class GPanel extends JPanel{
private int width, height;
private Timer timer;
private int mouseX, mouseY;
public static void main(String[] args) {
JFrame f = new JFrame();
GPanel gp = new GPanel(400, 400);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(gp,BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
public GPanel(int width, int height) {
mouseX = 0;
mouseY = 0;
this.width = width;
this.height = height;
this.setPreferredSize(new Dimension(width, height));
timer = new Timer(17 , new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TO DO
repaint();
}
});
addMouseMotionListener();
timer.start();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g.setColor(new Color(255, 255, 255));
g2.fillRect(0, 0, width, height);
g2.setColor(new Color(0, 0, 0));
g2.translate(width / 2, height / 2);
double x = mouseX - width / 2d;
double y = mouseY - height / 2d;
double theta = Math.atan2(x, y);
g2.rotate(-theta);
g2.translate(-20, 0);
g2.fillRect(0, 0, 40, 100);
}
private void addMouseMotionListener() {
this.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
}
#Override
public void mouseDragged(MouseEvent e) {
if(SwingUtilities.isLeftMouseButton(e)) {
//TO DO
} else if(SwingUtilities.isRightMouseButton(e)) {
}
repaint();
}
});
}
}
What I would do is create an affine transformation for the sprite. I imagine a sprite, img should be at (cx, cy) and the angle it is rotated should be theta.
Graphics2D g2d = (Graphics2D)g;
AffineTransform at = new AffineTransform();
//translate the center to be at cx, cy.
at.translate(cx - img.getWidth()/2.0, cy - img.getHeight()/2.0);
//rotate about the center of the sprite.
at.rotate(theta, img.getWidth()/2.0, img.getHeight()/2.0);
g2d.drawImage(img, at, this);
I tested it out with this example, where you can click on the JPanel and center the sprite. The sprite follows the mouse cursor around when you move it.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.geom.*;
public class RotateArrow{
int cx = 0;
int cy = 0;
double theta = 0;
public void startGui(){
JFrame frame = new JFrame("arrow");
BufferedImage img = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.getGraphics();
g.setColor(Color.RED);
g.drawLine(0, 32, 64, 32);
g.drawLine(48, 0, 48, 64);
g.dispose();
JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
AffineTransform at = new AffineTransform();
//rotate about center of image.
at.translate(cx - img.getWidth()/2.0, cy - img.getHeight()/2.0);
at.rotate(theta, img.getWidth()/2.0, img.getHeight()/2.0);
g2d.drawImage(img, at, this);
}
};
panel.addMouseListener( new MouseAdapter(){
#Override
public void mousePressed(MouseEvent evt){
cx = evt.getX();
cy = evt.getY();
panel.repaint();
}
} );
panel.addMouseMotionListener( new MouseAdapter(){
#Override
public void mouseMoved(MouseEvent evt){
double dx = evt.getX() - cx;
double dy = evt.getY() - cy;
if(dx != 0 || cy != 0){
theta = Math.atan2(dy, dx);
panel.repaint();
}
}
} );
frame.setContentPane(panel);
frame.setSize(512, 512);
frame.setVisible(true);
}
public static void main(String[] args){
EventQueue.invokeLater( ()->new RotateArrow().startGui() );
}
}

How to draw part of a large BufferedImage?

I have a 10000x10000 BufferedImage and I'm looking to draw only part of it to a Canvas, is there a way to do this using args such as:
x, y, width, height ?
So for example, drawImage(img, x, y, width, height) would draw a rectangle from the image starting at (x, y) and having (width, height) as the dimensions?
EDIT:
I'm going to re- word this question:
I have a 10000x10000 image and I only want to display a portion of it on the screen, the problem with just offsetting it by x and y is that this still causes lag as the entire image is being rendered, just most of it off canvas. How can I basically make it so that the entire image is rendered but I can scroll around it without causing the canvas to lag?
I have a 10000x10000 BufferedImage and I'm looking to draw only part
of it to a Canvas, is there a way to do this using args such as:
Don't use canvas for custom painting in java. use JComponent or JPanel instead. It has a nice function paintComponent(Graphics g), override it and paint your image inside with g.drawImage(x, y, width, height, observer);
Swing graphics has Graphics.clipRect(int x, int y, int width, int height) to bound the area rectangle to which you wish to draw prior to drawing the image.
Edit (In response to your edited question):
First approach is to use BufferedImage..getSubimage(x, y, width, height) to get a sub image with specified rectangle region. It is faster.
BufferedImage img = ImageIO.read(new File("file"));
img = img.getSubimage(50, 50, 500, 500); // 500 x 500
This function will give you a new image cropped with the rectangle(x, y, width, height) of your original image you specified. Use the returned image to draw on your component.
Tutorial resource: Clipping the Drawing Region
Demo: Demonstrating clipping Image with Animation:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.logging.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;
class MyCanvas extends JPanel implements ActionListener
{
public BufferedImage buffImg;
public Rectangle rectangle;
Random random;
long lastTimeChanged;
int dirX = 1, dirY = 1;
volatile static boolean imageLoading = true;
public MyCanvas() {
random = new Random();
rectangle = new Rectangle(50, 50, 250, 250);
lastTimeChanged = System.currentTimeMillis();
setBackground(Color.WHITE);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(imageLoading)
{
showWaitForLoading(g);
return;
}
g.clipRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
g.drawImage(buffImg, 0, 0, getWidth(), getHeight(), this);
}
public void showWaitForLoading(Graphics g)
{
Graphics2D g2d = (Graphics2D)g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.DARK_GRAY);
g2d.fillRoundRect(getWidth()/2-100, getHeight()/2-15, 200, 30, 30, 30);
g2d.setColor(Color.WHITE);
g2d.drawString("Loading image...", getWidth()/2 - 45, getHeight()/2 + 3 );
g2d.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
long endTime = System.currentTimeMillis();
if(endTime - lastTimeChanged > 500)
{
dirX = random.nextInt(2) == 0 ? -1 : 1;
dirY = random.nextInt(2) == 0 ? -1 : 1;
lastTimeChanged = endTime;
}
if(rectangle.x < 0)dirX = 1;
else if(rectangle.x + rectangle.width > getWidth())dirX = -1;
if(rectangle.y < 0)dirY = 1;
else if(rectangle.y + rectangle.height > getHeight())dirY = -1;
rectangle.x = rectangle.x + dirX * 10;
rectangle.y = rectangle.y + dirY * 10;;
repaint();
}
}
public class CustomPainting {
public static void main(String[] args) throws IOException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
frame.setSize(new Dimension(500, 500));
frame.add(canvas);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(200, canvas);
timer.start();
new Thread()
{
public void run()
{
try {
canvas.buffImg = ImageIO.read(new URL("http://images6.fanpop.com/image/photos/33400000/Cute-Panda-beautiful-pictures-33434826-500-500.jpg"));
MyCanvas.imageLoading = false;
} catch (IOException ex) {
Logger.getLogger(CustomPainting.class.getName()).log(Level.SEVERE, null, ex);
}
}
}.start();
}
});
}
}
You can scale or draw a part of an image using Graphics.drawImage as mentioned another answer and according to Java documentation, ImageObserver argument is not needed for BufferedImage so you can just pass null.
http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics.html
However, my choice would be the clipping drawing region of image instead.
Here is an example you can try:
Graphics2D g = BufferedImage.getGraphics;
g.setClip(x, y, width, height);
g.drawImage(sx, sy, x - sx, y - sy, null );
Yes there is: drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)

Get mouse detection with a dynamic shape

Basically I'm constructing a world map. I know how to have a square click area. But I'd like to make it so I can put the countries together and be able to click on the country. Now it's pretty obvious that I can't use square click areas for that because I'd have overlapping click areas. Could I do it by looking at the transparency of each pixel? Even so I don't know how to do that?
Use Shape.contains(Point2D) - something like this:
This example uses overlapping ellipses to show how the contains(..) method will accurately identify which ovals the mouse click falls inside. But the kind of map you are referring to will probably be made of a number of GeneralPath objects (one for each country) that do not overlap.
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.BasicStroke;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.io.File;
import java.util.*;
import javax.imageio.ImageIO;
public class ShapeContainment {
List<Ellipse2D> shapes = new ArrayList<Ellipse2D>();
int w = 400;
int h = 100;
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Point2D mouse = new Point2D.Double(0, 0);
JLabel l;
ShapeContainment() {
Random r = new Random();
for (int ii = 0; ii < 10; ii++) {
int x = r.nextInt(w / 2);
int y = r.nextInt(h / 2);
int wE = r.nextInt(w - x);
int hE = r.nextInt(h - y);
Ellipse2D ellipse = new Ellipse2D.Double(x, y, wE, hE);
shapes.add(ellipse);
}
l = new JLabel(new ImageIcon(img));
MouseAdapter listener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
mouse = me.getPoint();
drawImage();
}
};
l.addMouseListener(listener);
drawImage();
JOptionPane.showMessageDialog(null, l);
}
public void drawImage() {
Graphics2D g = img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, w, h);
RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHints(hints);
Color bg = new Color(0, 128, 0, 60);
Color inside = new Color(0, 0, 255, 120);
Color outside = new Color(255, 0, 0, 120);
g.setStroke(new BasicStroke(4));
for (Ellipse2D shape : shapes) {
g.setColor(bg);
g.fill(shape);
if (shape.contains(mouse)) {
g.setColor(inside);
} else {
g.setColor(outside);
}
g.draw(shape);
}
g.setColor(Color.RED);
int x = (int) mouse.getX();
int y = (int) mouse.getY();
g.setStroke(new BasicStroke(2));
int s = 3;
g.drawLine(x-s, y, x+s, y);
g.drawLine(x, y-s, x, y+s);
l.setIcon(new ImageIcon(img));
g.dispose();
try {
ImageIO.write(
img,
"png",
new File(System.currentTimeMillis() + ".png"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Runnable run = new Runnable() {
#Override
public void run() {
new ShapeContainment();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(run);
}
}

Triangle inside rectangular grids

I have written a code that draws grids and a triangle inside one grid cell. The grid size is increased/decreased when the window is maximized or minimized.
My requirement is that the triangle size should also increase/decrease to fit the grid cell each time the grid size is increased/decreased.
My code is as follows:
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Grid extends JPanel {
/**
* #param args
*/
public static void main(String[] args) {
Grid g = new Grid();
JFrame f = new JFrame("Application GUI Window");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
f.getContentPane().add("Center", g);
f.pack();
f.setSize(new Dimension(450,400));
f.show();
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Dimension d = getSize();
g2d.setBackground(getBackground());
g2d.clearRect(0, 0, d.width, d.height);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawGrid(d.width, d.height, g2d);
int[] xPoints = {20,15,25};
int[] yPoints = {15,25,25};
int n = 3;
Polygon triangle = new Polygon(xPoints, yPoints, n);
g.fillPolygon(triangle);
}
private void drawGrid(int width, int height, Graphics2D g2d) {
/* BasicStroke border = new BasicStroke(3, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 0, new float[]{0,1,0,1}, 0);
g2d.setStroke(border);
g2d.drawRect(3,3,width-6,height-6);*/
//horizontal lines
int cellheight = height/10;
int cellwidth = width/5;
for (int j=0;j<height;j=j+cellheight)
{
BasicStroke line = new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 0, new float[]{0,1,0,1}, 0);
g2d.setStroke(line);
g2d.drawLine(0, j, cellwidth*5, j);
}
//vertical lines
for (int i=0;i<width;i=i+cellwidth)
{
BasicStroke line = new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 0, new float[]{0,1,0,1}, 0);
g2d.setStroke(line);
g2d.drawLine(i, 0, i, cellheight*10);
}
}
}
Thanks in advance for your help.
You can tie the coordinates of the triangle to the dimensions of a grid cell. Here is a snippet based on your example:
int cellHeight = d.height/10;
int cellWidth = d.width/5;
int xOffset = cellWidth/6;
int yOffset = cellHeight/6;
int[] xPoints = {cellWidth/2, xOffset, cellWidth - xOffset};
int[] yPoints = {yOffset, cellHeight - yOffset, cellHeight - yOffset};
Polygon triangle = new Polygon(xPoints, yPoints, xPoints.length);
Note that in Swing you usually should override paintComponent() rather than paint(), unless in some special cases. See A Closer Look at the Paint Mechanism for more details.
Also note that show() is deprecated in favor of setVisible(). You can replace it with: setVisible(true)

Categories