So i just learnt about affine transformation in java 2D and how each transformation behaves.So what i tried as a side project was to create a circle rotating around it's axis program,i tried translating first to the (0,0) then rotating by a degree then translating back to initial position,did that through 360 iterations with 1 degree increment but the circle still rotates out of that center points(although it goes back to its original point at last iteration).
here's what have done so far:
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
//Use of antialiasing to have nicer lines.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
//The lines should have a thickness of 3.0 instead of 1.0.
BasicStroke bs = new BasicStroke(3.0f);
g2d.setStroke(bs);
//The GeneralPath to decribe the car.
//GeneralPath gp = new GeneralPath();
//Start at the lower front of the car.
g2d.setPaint(new Color(110, 100, 0));
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//Draw the car.
//g2d.fillOval(215, 135, 50, 50);
Shape s = new Ellipse2D.Double(160,160,40,40);
sustain(1000);
for(int i=0;i<360;i++) {
AffineTransform rotation = new AffineTransform();
rotation.setToRotation(Math.PI/180+i);
AffineTransform translate = new AffineTransform();
translate.setToTranslation(-160, -160);
AffineTransform translate2 = new AffineTransform();
translate2.setToTranslation(160, 160);
rotation.concatenate(translate);
translate2.concatenate(rotation);
clearWindow(g2d);
g2d.setPaint(new Color(110, 100, 0));
g2d.fill(translate2.createTransformedShape(s));
}
I've spent some time re-reading your question and looking over you code and I'm still unclear on
What it is you want to do and
What your problem is
But when has that ever stopped me from having a play 😉
Okay, so this has two circles (same shape) circling around a central point (translated) point.
Something to keep in mind is, transforms are accumulative, so you can see, between the second and third circle, I reset the transform (dispose of the graphics and take another snapshot) so my poor challenged brain doesn't get completely screwed up
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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int angle = 0;
public TestPane() {
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angle += 1;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(120, 120);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
//Use of antialiasing to have nicer lines.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Shape s = new Ellipse2D.Double(0, 0, 40, 40);
g2d.transform(AffineTransform.getTranslateInstance(40, 40));
g2d.setPaint(Color.RED);
g2d.draw(s);
g2d.transform(AffineTransform.getTranslateInstance(-30, -30));
g2d.transform(AffineTransform.getRotateInstance(Math.toRadians(angle), 50, 50));
g2d.setPaint(new Color(110, 100, 0));
g2d.drawRect(0, 0, 40, 40);
g2d.draw(s);
g2d.dispose();
g2d = (Graphics2D) g.create();
g2d.transform(AffineTransform.getTranslateInstance(40, 40));
g2d.transform(AffineTransform.getTranslateInstance(-20, -20));
g2d.transform(AffineTransform.getRotateInstance(Math.toRadians(angle / 2), 40, 40));
g2d.setPaint(Color.BLUE);
g2d.drawRect(0, 0, 40, 40);
g2d.draw(s);
}
}
}
I need it to rotate around its axis(have a circular motion in respect to its own center with out changing positions)
Okay, still not clear. If you want to rotate the object around it's centre point, but have it moving at the same time, then the order in which you apply your transformations is important.
For example, I'd translate it's position first, then rotate it, as it's easier to rotate about it's centre point without needing to calculate additional offsets
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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int angle = 0;
private Path2D path;
public TestPane() {
path = new Path2D.Double();
path.moveTo(20, 20);
path.lineTo(0, 20);
path.append(new Ellipse2D.Double(0, 0, 40, 40), false);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angle += 1;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(120, 120);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
//Use of antialiasing to have nicer lines.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.transform(AffineTransform.getTranslateInstance(40, 40));
g2d.transform(AffineTransform.getRotateInstance(Math.toRadians(angle), 20, 20));
g2d.setPaint(Color.RED);
g2d.draw(path);
g2d.dispose();
}
}
}
And as an addition, you could also have a look at How to rotate an object around another moving object in java?
Related
The BufferedImage drawn by the drawImage and clip method of Java Graphics2D have jagged edges, how to apply antialiasing?
The code:
BufferedImage img = ImageIO.read(new File("D:\\Pictures\\U\\U\\3306231465660486.jpg"));
JFrame frame = new JFrame();
frame.add(new JPanel() {
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.RED);
g2d.drawLine(10, 10, 300, 100);
g2d.translate(50, 200);
g2d.rotate(Math.toRadians(30), getWidth() / 2.0, getHeight() / 2.0);
g2d.drawImage(img, 0, 0, this);
g2d.clip(new Rectangle(-110, 110, 80, 110));
g2d.fill(new Rectangle(-100, 100, 100, 100));
}
});
frame.setSize(600, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
One solution is to blur the border of the image once you load it. You should also use the RenderingHints.KEY_RENDERING with RenderingHints.VALUE_RENDER_QUALITY.
This is the final result:
The full code is available below. Note that is uses a blur method described by Marco13 in this answer: https://stackoverflow.com/a/22744303/4289700.
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MultipleGradientPaint;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Main {
private static BufferedImage blurImageBorder(BufferedImage input, double border) {
int w = input.getWidth();
int h = input.getHeight();
BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = output.createGraphics();
g.drawImage(input, 0, 0, null);
g.setComposite(AlphaComposite.DstOut);
Color c0 = new Color(0x000000FF);
// Left
g.setPaint(new GradientPaint(new Point2D.Double(0, border), c0, new Point2D.Double(border, border), c0));
g.fill(new Rectangle2D.Double(0, border, border, h- border - border));
// Right
g.setPaint(new GradientPaint(new Point2D.Double(w - border, border), c0, new Point2D.Double(w, border), c0));
g.fill(new Rectangle2D.Double(w- border, border, border, h- border - border));
// Top
g.setPaint(new GradientPaint(new Point2D.Double(border, 0), c0, new Point2D.Double(border, border), c0));
g.fill(new Rectangle2D.Double(border, 0, w - border - border, border));
// Bottom
g.setPaint(new GradientPaint(new Point2D.Double(border, h - border), c0, new Point2D.Double(border, h), c0));
g.fill(new Rectangle2D.Double(border, h - border, w - border - border, border));
final float[] floatArray = new float[]{ 0, 1 };
final Color[] colorArray = new Color[]{ c0, c0 };
// Top Left
g.setPaint(new RadialGradientPaint(new Rectangle2D.Double(0, 0, border + border, border + border),
floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
g.fill(new Rectangle2D.Double(0, 0, border, border));
// Top Right
g.setPaint(new RadialGradientPaint(
new Rectangle2D.Double(w - border - border, 0, border + border, border + border),
floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
g.fill(new Rectangle2D.Double(w - border, 0, border, border));
// Bottom Left
g.setPaint(new RadialGradientPaint(
new Rectangle2D.Double(0, h - border - border, border + border, border + border),
floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
g.fill(new Rectangle2D.Double(0, h - border, border, border));
// Bottom Right
g.setPaint(new RadialGradientPaint(
new Rectangle2D.Double(w - border - border, h - border - border, border + border, border + border),
floatArray, colorArray, MultipleGradientPaint.CycleMethod.NO_CYCLE));
g.fill(new Rectangle2D.Double(w - border, h - border, border, border));
g.dispose();
return output;
}
public static void main(String[] args) throws IOException {
BufferedImage raw = ImageIO.read(new File("/path/to/picture.jpg"));
BufferedImage img = blurImageBorder(raw, 1);
JFrame frame = new JFrame();
frame.add(new JPanel() {
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.drawLine(10, 10, 300, 100);
g2d.translate(50, 200);
g2d.rotate(Math.toRadians(30), getWidth() / 2.0, getHeight() / 2.0);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.drawImage(img, 0, 0, this);
g2d.fill(new Rectangle(-100, 100, 100, 100));
}
});
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(600, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
For the image, it is sufficient to paint the image into another image that is 2 pixels larger, and then draw the resulting image with bilinear interpolation. So you can just pass your image through a method like this one:
private static BufferedImage addBorder(BufferedImage image)
{
BufferedImage result = new BufferedImage(
image.getWidth() + 2, image.getHeight() + 2,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.drawImage(image, 1, 1, null);
g.dispose();
return result;
}
The result will be this:
Here is the MCVE, including the line that sets the ..._INTERPOLATION_BILINEAR rendering hint:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
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.SwingUtilities;
public class ImageBorderAntialiasing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
//BufferedImage img = loadUnchecked("7bI1Y.jpg");
BufferedImage img = addBorder(loadUnchecked("7bI1Y.jpg"));
JFrame frame = new JFrame();
frame.add(new JPanel()
{
#Override
protected void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setColor(Color.RED);
g2d.drawLine(10, 10, 300, 100);
g2d.translate(50, 200);
g2d.rotate(Math.toRadians(30),
getWidth() / 2.0, getHeight() / 2.0);
g2d.drawImage(img, 0, 0, this);
g2d.fill(new Rectangle(-100, 100, 100, 100));
}
});
frame.setSize(600, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static BufferedImage addBorder(BufferedImage image)
{
BufferedImage result = new BufferedImage(
image.getWidth() + 2, image.getHeight() + 2,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.drawImage(image, 1, 1, null);
g.dispose();
return result;
}
private static BufferedImage loadUnchecked(String fileName)
{
try
{
return ImageIO.read(new File(fileName));
}
catch (IOException e)
{
e.printStackTrace();
return null;
}
}
}
After this was answered, the question was updated to also ask about the clip method
Antialiasing the result of a clip operation may be more difficult. The clip operation is very hard "by nature" (and I assume that it eventually will be handled by something like a Stencil Buffer in hardware).
One approach to solve this could be do do the clipping manually. So instead of doing
g2d.clip(new Rectangle(-110, 110, 80, 110));
g2d.fill(new Rectangle(-100, 100, 100, 100));
you could do something like
Shape clip = new Rectangle(-110, 110, 80, 110);
Shape rectangleA = new Rectangle(-100, 100, 100, 100);
g2d.fill(clip(clip, rectangleA));
where the clip method is implemented to to manually compute the intersection of the shapes.
Note: Computing the intersection of two shapes can be rather expensive. If this becomes an issue, one might have to revise the approach. But on another note: I've heavily been doing Swing programming for ~20 years now, and cannot remember to ever have used the Graphics2D#clip method at all....
The difference between using Graphics2D#clip and the manual clipping is shown here:
and a closeup:
And there is the code:
(It does no longer contain the image part, because the problems are fairly unrelated...)
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ClippedDrawingAntialiasing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(1,2));
frame.getContentPane().add(new JPanel()
{
#Override
protected void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.RED);
g2d.drawLine(10, 10, 300, 100);
g2d.translate(50, 200);
g2d.rotate(Math.toRadians(30),
getWidth() / 2.0, getHeight() / 2.0);
g2d.clip(new Rectangle(-110, 110, 80, 110));
g2d.fill(new Rectangle(-100, 100, 100, 100));
g2d.setColor(Color.BLUE);
g2d.fill(new Rectangle(-60, 120, 60, 170));
}
});
frame.getContentPane().add(new JPanel()
{
#Override
protected void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.RED);
g2d.drawLine(10, 10, 300, 100);
g2d.translate(50, 200);
g2d.rotate(Math.toRadians(30),
getWidth() / 2.0, getHeight() / 2.0);
Clipper clipper =
new Clipper(new Rectangle(-110, 110, 80, 110));
g2d.fill(clipper.clip(new Rectangle(-100, 100, 100, 100)));
g2d.setColor(Color.BLUE);
g2d.fill(clipper.clip(new Rectangle(-60, 120, 60, 170)));
}
});
frame.setSize(1200, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static class Clipper
{
private final Shape shape;
Clipper(Shape shape)
{
this.shape = shape;
}
Shape clip(Shape other)
{
Area a = new Area(shape);
a.intersect(new Area(other));
return a;
}
}
}
I am currently learning Java GUI development and here I stuck at Gradient.
I want to know the difference Cyclic and Acyclic Gradient?
Cyclic Gradient
Can be simply put as pattern repeating itself on gradient area.
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
class GradientPane extends JComponent {
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
Point2D.Float p1 = new Point2D.Float(150.f, 75.f); // Gradient line start
Point2D.Float p2 = new Point2D.Float(250.f, 75.f); // Gradient line end
float width = 300;
float height = 50;
GradientPaint g1 = new GradientPaint(p1, Color.WHITE, p2, Color.DARK_GRAY, true); // Cyclic
// gradient
Rectangle2D.Float rect1 = new Rectangle2D.Float(p1.x - 100, p1.y - 25, width, height);
g2D.setPaint(g1); // Gradient color fill
g2D.fill(rect1); // Fill the rectangle
g2D.setPaint(Color.BLACK); // Outline in black
g2D.draw(rect1); // Fill the rectangle
g2D.draw(new Line2D.Float(p1, p2));
}
}
public class CyclicGradientPaint {
public static void main(String[] a) {
JFrame window = new JFrame("Cyclic Gradient Paint");
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new GradientPane());
window.setVisible(true);
}
}
Acyclic Gradient
Pattern not repeating itself but widening itself as gradient area goes.
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
class GradientPane extends JComponent {
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
Point2D.Float p1 = new Point2D.Float(150.f, 75.f); // Gradient line start
Point2D.Float p2 = new Point2D.Float(250.f, 75.f); // Gradient line end
float width = 300;
float height = 50;
Rectangle2D.Float rect1 = new Rectangle2D.Float(p1.x - 100, p1.y - 25, width, height);
GradientPaint g2 = new GradientPaint(p1, Color.WHITE, p2, Color.DARK_GRAY, false); // Acyclic gradient
rect1.setRect(p1.x - 100, p1.y - 25, width, height);
g2D.setPaint(g2); // Gradient color fill
g2D.fill(rect1); // Fill the rectangle
g2D.setPaint(Color.BLACK); // Outline in black
g2D.draw(rect1); // Fill the rectangle
g2D.draw(new Line2D.Float(p1, p2));
}
}
public class AcyclicGradientPaint {
public static void main(String[] a) {
JFrame window = new JFrame("Acyclic Gradient Paint");
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new GradientPane());
window.setVisible(true);
}
}
I am working on a class that can rotate a wheel around the center. The wheel is created using graphics2d, but I can not figure out exactly how to get the wheel to rotate around the center.
Currently, the wheel rotates, but not exactly about the origin.
My ultimate goal here is to create the wheel so that it is multicolored as well as a program around it, but my main concern here is getting the rotating wheel to work. If you could point me in the right direction I would be forever grateful!
Here is my current code:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
public class RotateApp {
private static final int N = 3;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(N, N, N, N));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotatePanel());
frame.pack();
frame.setVisible(true);
System.out.println();
}
});
}
}
class RotatePanel extends JPanel implements ActionListener {
private static final int SIZE = 256;
private static double DELTA_THETA = Math.PI / 90;
private final Timer timer = new Timer(25, this);
private Image image = RotatableImage.getImage(SIZE);
private double dt = DELTA_THETA;
private double theta;
public RotatePanel() {
this.setBackground(Color.lightGray);
this.setPreferredSize(new Dimension(
image.getWidth(null), image.getHeight(null)));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
image = RotatableImage.getImage(SIZE);
dt = -dt;
}
});
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.rotate(theta);
g2d.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2d.drawImage(image, 0, 0, null);
}
public void actionPerformed(ActionEvent e) {
theta += dt;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
}
class RotatableImage {
private static final Random r = new Random();
static public Image getImage(int size) {
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
g2d.setStroke(new BasicStroke(10.0f));
g2d.draw(new Line2D.Double(0, 100, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 200, 100));
g2d.draw(new Line2D.Double(100, 0, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 100, 200));
g2d.draw(new Line2D.Double(25, 25, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 175, 175));
g2d.draw(new Line2D.Double(175, 25, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 25, 175));
g2d.draw(new Ellipse2D.Double(0, 0, 200, 200));
g2d.dispose();
return bi;
}
}
You can use the Rotated Icon class to do the rotation for you so you don't have to worry about all the rotation logic and the rotation logic is in a reusable class.
An example of using this class would be:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation4 extends JPanel
{
private JLabel label;
private RotatedIcon rotated;
private int degrees;
public Rotation4(Image image)
{
setLayout( new GridBagLayout() );
Icon icon = new ImageIcon( image );
rotated = new RotatedIcon(icon, 0);
rotated.setCircularIcon(true);
label = new JLabel(rotated);
label.setOpaque(true);
label.setBackground(Color.RED);
add(label, new GridBagConstraints());
setDegrees( 0 );
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
rotated.setDegrees( degrees );
label.revalidate();
label.repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
Image bi = RotatableImage.getImage(210);
final Rotation4 r = new Rotation4(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
static class RotatableImage
{
private static final Random r = new Random();
static public Image getImage(int size)
{
BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
g2d.setStroke(new BasicStroke(10.0f));
g2d.draw(new Line2D.Double(5, 105, 205, 105));
g2d.draw(new Line2D.Double(105, 5, 105, 205));
g2d.draw(new Line2D.Double(35, 35, 175, 175));
g2d.draw(new Line2D.Double(175, 35, 35, 175));
g2d.draw(new Ellipse2D.Double(5, 5, 199, 199));
g2d.setColor(Color.BLACK);
g2d.fillOval(100, 100, 10, 10);
g2d.dispose();
return bi;
}
}
}
Note I also had to make changes with your image and your painting. These changes will need to be made whether you use the RotatedIcon or do the rotation code yourself:
The image size was changed to 210. This is because your stroke size is 10, so you need to account for the extra pixels in the circle outline.
You need to change the original of the circle by half the stroke size. So in this case the origin becomes (5, 5).
The size of the oval needs to be changed to 199. This is because of the way the outline of the oval is painted. 1 extra pixel is needed for the outline. If you leave the size at 200 then 1 pixel of the outline will be lost. This is not very noticeable when using a stroke size of 10, but if you use a size of 1, then the outline will be missing at the right and bottom edges.
The locations of your lines needs to be changes. you don't want the line right to the edge of the circle because then you will get a flat line at the edge instead of the rounded line. So I started the line 5 pixels from the start and ended it 5 pixels from the end.
Ok, with little modification and fewer "spokes", I got your wheel rotate centric (1.) and multicolor (2.):
UPDATE on 1.) To make centric rotation in your (original) code just chage SIZEto 200!
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.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
public class RotateApp {
private static final int N = 3;
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(N, N, N, N));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotatePanel());
frame.pack();
frame.setVisible(true);
System.out.println();
});
}
}
class RotatePanel extends JPanel implements ActionListener {
private static final int SIZE = 256;
private static final double DELTA_THETA = Math.PI / 90;
private final Timer timer = new Timer(25, this);
private Image image = RotatableImage.getImage(SIZE);
private double dt = DELTA_THETA;
private double theta;
public RotatePanel() {
this.setBackground(Color.lightGray);
this.setPreferredSize(new Dimension(SIZE, SIZE));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
dt = -dt;
image = RotatableImage.getImage(SIZE);
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.rotate(theta,128,128);
g2d.drawImage(image, 0, 0, null);
}
#Override
public void actionPerformed(ActionEvent e) {
theta += dt;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
}
class RotatableImage {
private static final Random r = new Random();
static public Image getImage(int size) {
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
final Color c1 = Color.getHSBColor(r.nextFloat(), 1, 1);
final Color c2 = Color.getHSBColor(r.nextFloat(), 1, 1);
g2d.setPaint(c1);
g2d.setStroke(new BasicStroke(10.0f));
g2d.draw(new Line2D.Double(0, size/2, size, size/2));
g2d.setPaint(c2);
g2d.draw(new Line2D.Double(size/2, 0, size/2, size));
g2d.setPaint(c1);
g2d.draw(new Ellipse2D.Double(0, 0, size, size));
g2d.dispose();
return bi;
}
}
Explanation:
So the "wobble" in your solution came from the fact, that you sized the image and the container/panel 256x256, but "based" your wheel layout on 200x200 only. I fixed all dimensions, and drew a correct cross, the g2d.rotate(theta,128,128); (!) relates then to the center.
Multi-color(easy): You can invoke setPaint() between each shape! ;)
So, I have a JPanel where I draw a triangle. My intent is to turn the triangle to arbitrary angles that the user chooses. Now in order to be able to rotate the triangle without having it looking cropped I need a JPanel bigger than the triangle. So to achieve all this, my paintcomponent looks like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth()-(getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight()-(getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
It works, but not how I would like it. Right now, it paints a green triangle over a transparent JPanel. The issue is that I want to keep the transparency on the JPanes when rotating the triangle. I know that I'm supposed to clear the contents of the JPanel if I want to redraw the JPanel and not end up with the old and new content, but all the responses I've seen ask to use clearRect which doesn't work here. clearRect will paint with the background color making the JPanel opaque. Can't I reinitialize the graphics component and draw again?
Right now, trying to set the background with something like
g2d.setBackground(new Color(0,0,0,0));
g2d.clearRect(0,0, getWidth(), getHeight());
Ends up just making a black background and the thing that seems more promising is maybe something like:
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.3f));
But I don't know how to use the composite options and I keep making the triangle transparent and not the background
This should create the version with the clearRect() and the black background:
package clicknturn;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ClickNTurn extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ClickNTurn ex = new ClickNTurn();
ex.setVisible(true);
}
});
}
public ClickNTurn() {
setTitle("Simple example");
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
Entry tmpEntry = new Entry();
JPanel container = new JPanel();
container.setBackground(Color.GRAY);
container.setLayout(null);
this.add(container);
container.add(new Entry());
}
}
class Entry extends JPanel{
private int rotationAngle;
public Entry(){
this.setBounds(10,10, 200, 200);
this.setSize(200,200);
Entry me = this;
rotationAngle = 0;
setLayout(new GridBagLayout());
setOpaque(true);
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
rotationAngle += 10;
me.repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth()-(getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight()-(getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.setBackground(new Color(0,0,0,0));
g2d.clearRect(0,0, getWidth(), getHeight());
g2d.setBackground(null);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
}
Ty!
Axel
Did you try setting the background color to "null"?
g2d.setBackground(null);
As I mentioned in my comment, set opaque on your drawing JPanel to false appears to fix your problem.
This was the program that I created several hours ago to test it:
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ClickNTurn extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ClickNTurn ex = new ClickNTurn();
ex.setVisible(true);
}
});
}
public ClickNTurn() {
setTitle("Simple example");
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
Entry tmpEntry = new Entry();
JPanel container = new JPanel();
container.setBackground(Color.GRAY);
container.setLayout(null);
this.add(container);
container.add(new Entry());
}
}
class Entry extends JPanel {
private int rotationAngle;
public Entry() {
this.setBounds(10, 10, 200, 200);
this.setSize(200, 200);
// !! Entry me = this;
rotationAngle = 0;
setLayout(new GridBagLayout());
//!! setOpaque(true);
setOpaque(false); //!!
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
rotationAngle += 10;
// !! me.repaint();
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth() - (getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight() - (getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
// g2d.setBackground(new Color(0, 0, 0, 0));
// g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.setBackground(null);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
}
Use setOpaque and pass it false, this will make the component transparent, as well as let the paint engine know that it needs to take special care painting it, like clearing the Graphics context properly and painting beneath it.
There is nothing special you need to do in your code, simply continue painting as you normally would and the API will take care of the rest
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.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
public class ClickNTurn extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ClickNTurn ex = new ClickNTurn();
ex.setVisible(true);
}
});
}
public ClickNTurn() {
setTitle("Simple example");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setBackground(Color.RED);
add(new Entry());
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
class Entry extends JPanel {
private int rotationAngle;
public Entry() {
Entry me = this;
rotationAngle = 0;
setLayout(new GridBagLayout());
setOpaque(false);
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
rotationAngle += 10;
me.repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth() - (getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight() - (getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
}
}
The code below draws rectangle and 2 ellipses.
While should draw 3 ellipses.
My OS is Windows 7 prof 64 bit
My Java is 1.6 x86 also 1.7 x64 tested.
Why?
package tests;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
public class AntialiacingScaleTester {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JPanel circlePanel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(1));
//g2d.drawOval(0, 0, 200, 200);
g2d.draw(new Ellipse2D.Double(0, 0, 200, 200));
AffineTransform old = g2d.getTransform();
g2d.setColor(Color.GREEN);
g2d.scale(1000, 1000);
g2d.setStroke(new BasicStroke(0.001f));
g2d.draw(new Ellipse2D.Double(0, 0, 0.225, 0.225));
g2d.setColor(Color.BLUE);
g2d.scale(10, 10);
g2d.setStroke(new BasicStroke(0.001f));
g2d.draw(new Ellipse2D.Double(0, 0, 0.025, 0.025));
g2d.setTransform(old);
}
};
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.setLayout(new MigLayout("fill"));
//frame.add(circlePanel, "w 300, h 300, grow");
//frame.add(circlePanel);
frame.setLayout(null);
circlePanel.setBounds(new Rectangle(0, 0, 300, 300));
frame.add(circlePanel);
frame.setBounds(0, 0, 350, 300);
//frame.pack();
frame.setVisible(true);
}
});
}
}
I copy/pasted your code and it drew the 2 ellipses you wrote about, the only change I made was to replace your MigLayout by null, set the frame and panel dimensions by hand and remove the frame.pack() invocation:
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
/**
* #param args
*/
public static void main(String[] args) {
JPanel circlePanel = new JPanel() {
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(1));
//g2d.drawOval(0, 0, 200, 200);
g2d.draw(new Ellipse2D.Double(0, 0, 200, 200));
AffineTransform old = g2d.getTransform();
g2d.scale(10000, 10000);
g2d.setStroke(new BasicStroke(0.001f));
g2d.draw(new Ellipse2D.Double(0, 0, 0.025, 0.025));
g2d.setTransform(old);
}
};
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
circlePanel.setBounds(new Rectangle(0, 0, 300, 300));
frame.add(circlePanel);
frame.setBounds(0, 0, 350, 300);
//frame.pack();
frame.setVisible(true);
}
}
Update:
Could reproduce the problem using the Oracle JDK (java version "1.8.0-ea") instead of the OpenJDK. Got the diamond shape, as pointed out in another answers, the scale factor is the root cause of the shape degenaration, don't know if that should be the appropiate behaviour though, so, there must be a bug in one these JRE's.
The following test program works fine for both JRE's:
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
/**
* #param args
*/
public static void main(String[] args) {
JPanel circlePanel = new JPanel() {
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(1));
//g2d.drawOval(0, 0, 200, 200);
g2d.draw(new Ellipse2D.Double(0, 0, 200, 200));
AffineTransform old = g2d.getTransform();
g2d.scale(10, 10);
g2d.setStroke(new BasicStroke(1.0f));
g2d.draw(new Ellipse2D.Double(0, 0, 25.0, 25.0));
g2d.setTransform(old);
}
};
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
circlePanel.setBounds(new Rectangle(0, 0, 300, 300));
frame.add(circlePanel);
frame.setBounds(0, 0, 350, 300);
//frame.pack();
frame.setVisible(true);
}
}
Replicated on Java 7, Windows 7 in Eclipse, removing the Layout Manager.
My feeling is that it's due to the combination of the high scaling and inaccuracies at small floating point values, reducing the number of points generated.
If you substitute values you find between 0.0363 and 0.0362 the rendering API breaks. It no longer generates an Arc, but instead a square.
The work-around is to stop combining the huge scaling with tiny sized objects. Scale down and increase the size.