Java Graphics2D drawImage() and clip(): how to apply antialiasing? - java

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;
}
}
}

Related

how to rotate a circle around it's axis i9n java2D

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?

Java drawing a RadialGradientPaint circle cut into a rectangle

I have the following code to draw a RadialGradientPaint circle:
public class Character {
public static void draw(Graphics2D g2d){
g2d.setColor(Color.RED);
g2d.fillRect(0, 0, 1000, 750);
drawVisibilityPolygon(g2d);
}
private static void drawVisibilityPolygon(Graphics2D g2d){
Point center = new Point(1000 / 2, 750 / 2);
float radius = 200;
float[] dist = {
0f,
1f
};
Color[] colors = {
new Color(0, 0, 0, 0),
new Color(0, 0, 0, 255)
};
drawGradientCircle(g2d, radius, dist, colors, center);
}
private static void drawGradientCircle(Graphics2D g2d, float radius, float[] dist, Color[] colors, Point2D center){
RadialGradientPaint rgp = new RadialGradientPaint(center, radius, dist, colors);
g2d.setPaint(rgp);
g2d.fill(new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, radius * 2, radius * 2));
}
}
The draw method is called by the Display class:
public class Display extends JPanel {
#Override
public void paintComponent(Graphics g){
// SETUP
BufferedImage base = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = base.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, getWidth(), getHeight());
// DRAW STUFF
Character.draw(g2d);
// FINISH UP
g.drawImage(base, 0, 0, null);
g2d.dispose();
g.dispose();
}
}
The display class is contained in a JFrame:
public class Window extends JFrame {
private Display display = new Display();
public Window(String title, int width, int height){
super(title);
add(display);
setSize(width, height);
setIgnoreRepaint(false);
setLocationRelativeTo(null);
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setVisible(true);
}
public Display getDisplay() {
return display;
}
}
This works fine, but I want to fill the part outside of the circle (and only that part) with a color.
I've tried using an AlphaComposite but I cant figure out how to use it to do this. I could just make a bigger circle and decrease the dist1 value but that takes too long to draw (around 10ms on my machine as opposed to the 2ms of the circle in the image). I'm trying to put this into a game.
How would I go about this? Thanks in advance and sorry if this has an obvious solution but I just can't put my finger on it.
If you need more information just ask.
Painting with non-opaque colors causes background to show, so you have to set the background, so you have to set the appropriate background. Have a look at this demo :
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
public class APanel extends JPanel{
APanel(){
setPreferredSize(new Dimension(600,600));
setBackground(Color.YELLOW);
setBorder(new TitledBorder(new LineBorder(new Color(169, 169, 169)),
"Gradient Circle", TitledBorder.LEADING, TitledBorder.BELOW_TOP, null, null));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw((Graphics2D) g);
}
public static void draw(Graphics2D g2d){
g2d.setColor(Color.RED);
g2d.fillRect(100,100, 400, 400);
drawVisibilityCircle(g2d);
}
private static void drawVisibilityCircle(Graphics2D g2d){
Point center = new Point(300, 300);
float radius = 200;
float[] dist = { 0f, 1f};
Color[] colors = { new Color(0, 0, 0, 0), new Color(0, 0, 0, 255)};
//workaround to prevent background color from showing
drawBackGroundCircle(g2d, radius, Color.WHITE, center);
drawGradientCircle(g2d, radius, dist, colors, center);
}
private static void drawBackGroundCircle(Graphics2D g2d, float radius, Color color, Point2D center){
g2d.setColor(color);
radius -= 1;//make radius a bit smaller to prevent fuzzy edge
g2d.fill(new Ellipse2D.Double(center.getX() - radius, center.getY()
- radius, radius * 2, radius * 2));
}
private static void drawGradientCircle(Graphics2D g2d, float radius, float[] dist, Color[] colors, Point2D center){
RadialGradientPaint rgp = new RadialGradientPaint(center, radius, dist, colors);
g2d.setPaint(rgp);
g2d.fill(new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, radius * 2, radius * 2));
}
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new APanel();
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
}

Difference between cyclic and acyclic gradient in java

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);
}
}

Java Graphics2D floating point accurate drawOval alternative?

So I am trying to draw an arc and put a circle around its round endpoint, but I am having issues due to rounding to the nearest pixel. This is visible in some but not all cases.
Is there a way to draw circles using floating points and anti-aliasing to eliminate this rounding error?
You can run this code to see the problem. I have drawn arcs of 0 length (appearing as large dots) instead of full arcs for clarity.
import java.awt.*;
import javax.swing.*;
public class Example extends JFrame {
private int CENTER = 200;
private static int WINDOW = 400;
private int LEFT = 50;
private int TOP = 50;
private int DIM = 300;
private int DIAMETER = 26;
public Example () {
super();
}
public void paint (Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(16, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
g2.setColor(new Color(0, 0, 255));
g2.drawArc(LEFT, TOP, DIM, DIM, 0, 0);
g2.drawArc(LEFT, TOP, DIM, DIM, 32, 0);
g2.drawArc(LEFT, TOP, DIM, DIM, 115, 0);
g2.drawArc(LEFT, TOP, DIM, DIM, 200, 0);
g2.drawArc(LEFT, TOP, DIM, DIM, 331, 0);
this.drawCircle(g2, 0);
this.drawCircle(g2, 32);
this.drawCircle(g2, 115);
this.drawCircle(g2, 200);
this.drawCircle(g2, 331);
g2.setStroke(new BasicStroke(1));
g2.setColor(new Color(0, 0, 0));
g2.drawLine(0, CENTER, DIM * 2, CENTER);
g2.drawLine(CENTER, 0, CENTER, DIM * 2);
}
private void drawCircle(Graphics2D g, int angle) {
g.setStroke(new BasicStroke(3));
g.setColor(new Color(0, 0, 255));
g.drawOval(
Math.round(CENTER + (float)(Math.cos(Math.toRadians(angle)) * (DIM/2)) - DIAMETER/2),
Math.round(CENTER - (float)(Math.sin(Math.toRadians(angle)) * (DIM/2)) - DIAMETER/2),
DIAMETER,
DIAMETER
);
}
public static void main (String args[]) {
Example e = new Example();
e.setSize(WINDOW, WINDOW);
e.setVisible(true);
e.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
As an alternative, consider Ellipse2D with suitable RenderingHints. Typical usage is shown here.
Ellipse2D circle = new Ellipse2D.Float(…);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHints(…);
g2d.fill(circle);
Because various RenderingHints are implementation dependent, the example below will let you evaluate the effects individually.
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/38669048/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JPanel() {
private static final int N = 8;
private final Ellipse2D ellipse = new Ellipse2D.Float();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2d.setStroke(new BasicStroke(N));
ellipse.setFrame(N, N, getWidth() - 2 * N, getHeight() - 2 * N);
g2d.draw(ellipse);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
});
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Test()::display);
}
}

Rotating a wheel in Java using Swing and Graphics2D?

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! ;)

Categories

Resources