Draw a String with a specific angle degree in line - java

I'm drawing a graph with two points of each point having a line with a weight.
for example graph: point "15" to point "16" line with the weight of 1.872 and point "16" to point "15" with the weight of 1.567.
take a look at my graph for now:
I want to draw a String with always parallel (adjacent) to the line.
I calculated the slope for the straight and the angel I did calculate is the arctan of this slope:
I had use this function to rotate the string:
public static void drawRotate(Graphics2D g2d, double x, double y, double angle, String text) {
g2d.translate((float)x,(float)y);
g2d.rotate(Math.toRadians(angle));
g2d.drawString(text,0,0);
g2d.rotate(-Math.toRadians(angle));
g2d.translate(-(float)x,-(float)y);
}
With the angle of arctan((y2-y1)/(x2-x1)= the slope of the line ) and it didn't work well.
How can I rotate this String to run parallel always to the line I draw?
My goal: Draw the String like this example

Here is a quick demo to be used as a guide on how it might be done. I omitted some things like the arrowheads since that is just busy work. And I guesstimated on the label positions. I would recommend you read about the three argument version of Graphics.rotate() and RenderingHints and anti-aliasing to smooth the lines.
You may want to write general methods to facilitate positioning the text and labels based on font size.
But I believe your primary problem was doing int division when calculating the slope.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class GraphicsExample extends JPanel {
JFrame f = new JFrame("Draw Vector");
final static int WIDTH = 500;
final static int HEIGHT = 500;
String A = "1.567 [B->A]";
String B = "1.862 [A->B]";
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new GraphicsExample().start());
}
public void start() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x1= 50;int y1 = 400;
int x2 = 400; int y2 = 200;
// copy the graphics context.
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int diameter = 20;
drawLine(g2d,x1+diameter/2, y1+diameter/2, x2+diameter/2, y2+diameter/2);
g2d.setFont(new Font("Arial", Font.PLAIN, 18));
drawEndPoint(g2d,x1,y1, diameter, "A");
drawEndPoint(g2d,x2,y2, diameter, "B");
double angle = Math.atan((double)(y1-y2)/(x1-x2));
g2d.rotate(angle,x1,y1);
// based on font, this computes the placement of the Strings
FontMetrics fm = g2d.getFontMetrics();
int width = SwingUtilities.computeStringWidth(fm, A); // use for both
g2d.setColor(Color.black);
g2d.drawString(A, x1 + ((x2-x1) - width)/2, y1);
g2d.drawString(B, x1 + ((x2-x1) - width)/2, y1+ 30);
// discard the context.
g2d.dispose();
}
public void drawEndPoint(Graphics2D g2d, int x, int y, int diameter, String label) {
g2d.setColor(Color.BLUE);
g2d.drawString(label, x, y);
g2d.fillOval(x,y,diameter, diameter);
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(2f));
g2d.drawOval(x,y,diameter, diameter);
}
public void drawLine(Graphics2D g2d, int x1, int y1, int x2, int y2) {
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(3f));
g2d.drawLine(x1,y1,x2,y2);
}
}
Shows

Related

Random Circle Printer Java: Graphics not printing

I tried to create a code that printed n random circles in a 500 x 500 frame but it didn't work.
Can somebody tell me why this code isn't running?
When I run this code, it lets me enter the number of random circles I want but the frame always appear to be empty - no circles are drawn.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.util.Scanner;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class RandomCircles extends JComponent
{
private int n;
public RandomCircles(int N)
{
n = N;
}
public void PaintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
double x = Math.random() * 500;
double y = Math.random() * 500;
double diameter = Math.random() * 500;
// Making sure the circle stays within the frame
for (int i = 0; i < n; i++)
{
while(x + diameter <= 500 || y + diameter <= 500)
{
Ellipse2D.Double circle
= new Ellipse2D.Double(x, y, diameter, diameter);
g2.draw(circle);
}
}
}
public static void main(String[]args)
{
Scanner in = new Scanner(System.in);
System.out.println("Enter number of circles here: ");
int n = in.nextInt();
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setTitle("Random Circles");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
RandomCircles circle = new RandomCircles(n);
frame.add(circle);
// Add PaintComponent method somewhere here?
frame.setVisible(true);
}
}
I have a feeling that I need to add in the public void PaintComponent(Graphics g) somewhere to print it out, but I am not sure how.
The problem is on this line:
public void PaintComponent(Graphics g)
You are attempting to override the paintComponent(Graphics) method. You need to be careful that you get the name and the parameters right. Notice you spelled your method with an upper case P.
It is advisable that you add the annotation #Override on methods that are supposed to override a super class' method. That way you get a notification if you get the signature wrong.
So your method should look like this:
#Override
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
...
}
== Edit after comments ==
The while loop is also causing an issue. Try doing this instead.
for (int i = 0; i < n; i++)
{
double x = Math.random() * 500;
double y = Math.random() * 500;
double diameter = Math.random() * 500;
Ellipse2D.Double circle
= new Ellipse2D.Double(x, y, diameter, diameter);
g2.draw(circle);
}
Notice that I moved the random number generation inside the loop. This does not guarantee the circle fitting in the frame but that is something you can modify later.

Draw random dots inside a circle

I would like to draw 50 random dots within a given circle. The problem is the dots are not contained in the circle. Here is a runnable example:
package mygraphicsshapehomework;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
public class MyGraphicsShapeHomeWork extends JFrame {
public static void main(String[] args) {
new MyGraphicsShapeHomeWork();
}
public MyGraphicsShapeHomeWork() {
super("Title");
setBounds(600, 400, 700, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawOval(40, 40, 90, 90);
Color newColor = new Color(255, 0, 0);
g2.setColor(newColor);
for (int i = 0; i < 50; i++) {
int x = (int) Math.ceil(Math.random() * 10);
int y = (int) Math.ceil(Math.random() * 10);
g2.fillOval(i+x, i+y, 3, 3); // ???
}
}
}
Here is the result it produces:
How can I draw the dots within the circle only?
To get a random point in a circle with radius R find a random angle and a random radius:
double a = random() * 2 * PI;
double r = R * sqrt(random());
Then the coordinates of the point are:
double x = r * cos(a)
double y = r * sin(a)
Here are some notes about the drawing part. You should not paint directly on top level container such as JFrame. Instead, use JComponent or JPanel. Override paintComponent() for painting rather than paint() and don't forget to call super.paintComponent(g)
Take a look at Performing Custom Painting tutorial for more information.
Do not use setBounds(), override panel's getPreferredSize() and pack() the frame. Also, you rarely need to extend JFrame.
Here is a basic example that demonstrates drawing with a sub-pixel precision:
import java.awt.Color;
import java.awt.Dimension;
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;
import javax.swing.SwingUtilities;
public class TestDots extends JPanel{
public static final int POINTS_NUM = 1000;
public static final Color POINT_COLOR = Color.RED;
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
double padding = 10;
double radius = Math.min(this.getWidth(), this.getHeight()) / 2 - padding * 2;
g2.draw(new Ellipse2D.Double(padding, padding, radius * 2, radius * 2));
g2.setColor(POINT_COLOR);
for (int i = 0; i < POINTS_NUM; i++) {
double a = Math.random() * 2 * Math.PI;
double r = radius * Math.sqrt(Math.random());
double x = r * Math.cos(a) + radius + padding;
double y = r * Math.sin(a) + radius + padding;
g2.draw(new Ellipse2D.Double(x, y, 1, 1));
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("TestDots");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TestDots());
frame.pack();
frame.setVisible(true);
}
});
}
}
Here is a result:
For the position of the dots, generate random coordinates within the bounds of the outer circle. In order to generate these coordinates, the radius of the point from the center of the circle must be less than that of the outer circle. Get a random angle using
float a = Math.random() * Math.PI * 2;
Then, subtract a random value from the outer radius:
outerR - (Math.sqrt(Math.random()) * outerR)
and assign the positions to:
double x = Math.cos(a)*newR;
double y = Math.sin(a)*newR;
I'm sure there is a more mathematical approach to this, but this was the simplest in my opinion.

Connecting points with lines to create a line graph

Hello I am fairly new to java and have been stuck on this problem for awhile so hopefully someone will be able to save me. Basically I am creating a program that can graph an equation and right now I'm testing out x^2 between -10 and 10. I can get the points to graph in the right spots but I can't figure out how to fill in the spots in between the points so it looks like a real graph.
Here's my code:
import java.util.Scanner;
import javax.swing.JFrame;
import java.awt.*;
class PlotGraph extends JFrame{
public void paint(Graphics l){
l.drawLine(50, 300, 550, 300); //x axis
l.drawLine(300, 550, 300, 50); //y axis
//Orignin x = 300 y = 300
int xmin, xmax, y, tmin, tmax;
xmin =(-10);
xmax = 10;
int x_bet, y_bet;
while(xmin<=xmax){
y = 300-(xmin*xmin);
l.drawLine(xmin+300, y, xmin+300, y);
//while(x_bet>xmin){
//l.drawLine(, , , );
//}
xmin++;
}
}
public static void main(String [] args) {
PlotGraph graph = new PlotGraph();
graph.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
graph.setSize(600, 600);
graph.setVisible(true);
graph.setTitle("PlotGraph");
}
}
Another way to do it is to create a GeneralPath, like this.
import javax.swing.JFrame;
import java.awt.*;
import java.awt.geom.*;
class PlotGraph extends JFrame{
public void paint(Graphics l){
l.drawLine(50, 300, 550, 300); //x axis
l.drawLine(300, 550, 300, 50); //y axis
int xmin, xmax, y, tmin, tmax;
xmin =(-10);
xmax = 10;
int x_bet, y_bet;
GeneralPath gp = new GeneralPath();
y = 300-(xmin*xmin);
gp.moveTo((double)xmin+300, (double)y);
while(xmin<=xmax){
y = 300-(xmin*xmin);
gp.lineTo((double)xmin+300, (double)y);
xmin++;
}
Graphics2D g2 = (Graphics2D)l;
g2.setColor(Color.RED);
g2.draw(gp);
}
public static void main(String [] args) {
PlotGraph graph = new PlotGraph();
graph.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
graph.setSize(600, 600);
graph.setVisible(true);
graph.setTitle("PlotGraph");
}
}
This source still has problems though:
GUI updates should be done on the EDT.
Custom painting is either best done in a JPanel/JComponent or a BufferedImage displayed in a JLabel.
The 'graph component' should declare a preferred size, rather than setting a size for the frame..
Maybe try this:
int x = xmin;
int last_y = 300-(x*x);
for (x = xmin+1; x<=xmax; x++);
y = 300-(x*x);
l.drawLine(x-1, last_y, x, y);
last_y = y;
}
You want the line to be drawn between previous x and y coordinates and the current ones. That's what last_y is for.
There are several ways to do this. If this is an assignment then I'm guessing that your professor is looking to see what methods you come up with. At the most basic level you just draw a line from the last point to the current point. How else could you accomplish this? Maybe a polyline in a different color? Maybe use a little math to draw larger circles a then run a line through them?
Here is one simple way to address the specific problem. I wouldn't recommend turning this in (if it's an assignment) but it shows you the basic principle.
import java.util.Scanner;
import javax.swing.JFrame;
import java.awt.*;
class PlotGraph extends JFrame{
public void paint(Graphics l){
l.drawLine(50, 300, 550, 300); //x axis
l.drawLine(300, 550, 300, 50); //y axis
int xmin, xmax, y, tmin, tmax;
int z = 0;
xmin =(-10);
xmax = 10;
int x_bet, y_bet;
while(xmin<=xmax){
y = 300-(xmin*xmin);
l.drawLine(xmin+300, y, xmin+300, y);
if(z!=0)
l.drawLine(xmin+300, y, xmin+300, z);
z=y;
xmin++;
}
}
public static void main(String [] args) {
PlotGraph graph = new PlotGraph();
graph.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
graph.setSize(600, 600);
graph.setVisible(true);
graph.setTitle("PlotGraph");
}
}

Is there any way to draw lines with subpixel precision in Swing?

This question has been asked before, but the answers don't solve the problem, so I ask it again.
It was suggested that instead of using g2.drawLine, you could use g2.draw(line), where line is a Line2D.Double. However, as you can see from the screenshot, the lines are still drawn as if they ended on an integer pixel (each set of 10 lines is exactly parallel).
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Line2D;
public class FrameTestBase extends JFrame {
public static void main(String args[]) {
FrameTestBase t = new FrameTestBase();
t.add(new JComponent() {
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int i = 0; i < 50; i++) {
double delta = i / 10.0;
double y = 5 + 5*i;
Shape l = new Line2D.Double(5, y, 200, y + delta);
g2.draw(l);
// Base case, with ints, looks exactly the same:
// g2.drawLine(5, (int)y, 200, (int)(y + delta));
}
}
});
t.setDefaultCloseOperation(EXIT_ON_CLOSE);
t.setSize(220, 300);
t.setVisible(true);
}
}
So is it impossible in Swing to correctly render lines that don't end exactly on a pixel?
Try setting the following:
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Taken from this answer:
Java - Does subpixel line accuracy require an AffineTransform?
Here is the result for comparison:

Java Graphics.fillPolygon: How to also render right and bottom edges?

When drawing polygons, Java2D leaves off the right and bottom edges. I understand why this is done. However, I would like to draw something that includes those edges. One thing that occurred to me was to follow fillPolygon with drawPolygon with the same coordinates, but this appears to leave a gap. (See the little triangular image at the bottom.) There are two possibilities, but I can't tell which. To enable antialiasing, I'm doing this:
renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHints(renderHints);
One possibility is that the antialiasing is not being done on the alpha channel, so the gap is caused by overdraw. In that case, if the alpha channel were what was being antialiased, the edges would abut properly. The other possibility is that there is just a gap here.
How can I fix this?
Also, I'm not sure, but it appears that the polygon outline may actually be TOO BIG. That is, it may be going further out than the right and bottom edges that I want to include.
Thanks.
-- UPDATE --
Based on a very nice suggestion by Hovercraft Full of Eels, I have made a compilable example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class polygon {
private static final int WIDTH = 20;
public static void main(String[] args) {
BufferedImage img = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
int[] xPoints = {WIDTH / 3, (2*WIDTH) / 3, WIDTH / 3};
int[] yPoints = {0, WIDTH / 2, WIDTH};
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.green);
g2.drawLine(0, WIDTH-1, WIDTH, WIDTH-1);
g2.drawLine(0, 0, WIDTH, 0);
g2.drawLine(WIDTH/3, 0, WIDTH/3, WIDTH);
g2.drawLine((2*WIDTH/3), 0, (2*WIDTH/3), WIDTH);
g2.setColor(Color.black);
g2.drawPolygon(xPoints, yPoints, xPoints.length);
g2.setColor(Color.black);
g2.fillPolygon(xPoints, yPoints, xPoints.length);
g2.dispose();
ImageIcon icon = new ImageIcon(img);
JLabel label = new JLabel(icon);
JOptionPane.showMessageDialog(null, label);
}
}
If you leave the filled polygon red, you get the image below (zoomed by 500%), which shows that the polygon does not extend all the way to the right edge. That is, the vertical green line is corresponds to x=(2*WIDTH)/2, and although the red polygon includes that coordinate, it does not paint any pixels there.
To see the gap problem, I changed red in the program to black. In this image, you can see a subtle gap on the lower right side, where the outline drawn by drawPolygon does not quite meet up with what was drawn with fillPolygon.
Show us your code for your drawing in a simple compilable runnable program. For instance when I try to imitate your image and used RenderingHints, it seemed to produce an appropriate sized image with complete right/bottom edges:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class Foo002 {
private static final int WIDTH = 20;
public static void main(String[] args) {
BufferedImage img = new BufferedImage(WIDTH, WIDTH,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
int[] xPoints = { WIDTH / 3, (2 * WIDTH) / 3, WIDTH / 3 };
int[] yPoints = { 0, WIDTH / 2, WIDTH };
g2.setColor(Color.black);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2.fillPolygon(xPoints, yPoints, xPoints.length);
g2.dispose();
ImageIcon icon = new ImageIcon(img);
JLabel label = new JLabel(icon);
label.setBorder(BorderFactory.createLineBorder(Color.black));
JPanel panel = new JPanel();
panel.add(label);
JOptionPane.showMessageDialog(null, panel);
}
}
If you can show us a similar program that reproduces your problem, then we can give you better help.
I like the convenience of ImageIcon, shown by #HFOE, but this variation may make it a little easier to see what's happening. From the Graphics API,
Operations that draw the outline of a figure operate by traversing an
infinitely thin path between pixels with a pixel-sized pen that hangs
down and to the right of the anchor point on the path. Operations that
fill a figure operate by filling the interior of that infinitely thin
path.
In contrast, Graphics2D must follow more complex rules for antialiasing, which allow it to "draw outside the lines."
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.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/questions/7701097 */
public class PixelView extends JPanel {
private static final int SIZE = 20;
private static final int SCALE = 16;
private BufferedImage img;
public PixelView(Color fill) {
this.setBackground(Color.white);
this.setPreferredSize(new Dimension(SCALE * SIZE, SCALE * SIZE));
img = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
int[] xPoints = {SIZE / 3, (2 * SIZE) / 3, SIZE / 3};
int[] yPoints = {0, SIZE / 2, SIZE};
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.green);
g2.drawLine(0, SIZE - 1, SIZE, SIZE - 1);
g2.drawLine(0, 0, SIZE, 0);
g2.drawLine(SIZE / 3, 0, SIZE / 3, SIZE);
g2.drawLine((2 * SIZE / 3), 0, (2 * SIZE / 3), SIZE);
g2.setColor(Color.black);
g2.drawPolygon(xPoints, yPoints, xPoints.length);
g2.setColor(fill);
g2.fillPolygon(xPoints, yPoints, xPoints.length);
g2.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}
private static void display() {
JFrame f = new JFrame("PixelView");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridLayout(1, 0));
f.add(new PixelView(Color.black));
f.add(new PixelView(Color.red));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
}
Sometimes "the graphics pen hangs down and to the right from the path it traverses", and sometimes it doesn't.
I don't have any clear idea how to predict when it will or won't, but I have observed that RenderingHints.VALUE_STROKE_PURE can sometimes be used to alter the behavior, by trial and error.
In particular, I found that if you turn on STROKE_PURE during your drawPolygon() calls in your program,
it will make them match up with your fillPolygon() calls, as you desire.
I did a little study showing the effect of the STROKE_CONTROL hint, which is one of:
STROKE_NORMALIZE (the default, on my system)
STROKE_PURE
on the following calls:
drawLine()
drawPolygon()
fillPolygon()
in both antialiasing modes:
ANTIALIASING_OFF
ANTIALIASING_ON
And there is one more annoying dimension that apparently matters as well:
rendered directly to a JComponent on the screen
rendered to a BufferedImage.
Here are the results when rendering directly to a visible JComponent:
And here are the results when rendering into a BufferedImage:
(Notice the two cases in which the two pictures differ, i.e. in which direct rendering differs from BufferedImage rendering:
ANTIALIAS_OFF/STROKE_NORMALIZE/fillPolygon and ANTIALIAS_OFF/STROKE_PURE/drawPolygon.)
Overall, there doesn't seem to be much rhyme or reason to the whole thing.
But we can make the following specific observations based on the above pictures:
Observation #1:
If you want your antialiased drawPolygon()s and antialiased fillPolygon()s to match up well
(the original question), then turn on STROKE_PURE during the antialiased drawPolygon() calls.
(It doesn't matter whether it's on during the antialiased fillPolygon() calls.)
Observation #2:
If you want your antialiased fillPolygon()s and non-antialiased fillPolygon()s
to match up (because, say, your app allows the user to switch antialiasing on and off,
and you don't want the picture to jump northwest and southeast each time they do that),
then turn on STROKE_PURE during the non-antialiased fillPolygon() calls.
Here is the program I used to generate the pictures above.
My results are from compiling and running it it with opensdk11 on linux;
I'd be interested to know if anyone gets any different results on different platforms.
/** Study the effect of STROKE_PURE. */
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
#SuppressWarnings("serial")
public final class AntiAliasingStudy {
// These can be fiddled with.
final static int patchWidth = 24; // keep this a multiple of 4 for sanity
final static int patchHeight = 20; // keep this a multiple of 4 for sanity
final static int borderThickness = 4;
final static int mag = 6;
// derived quantities
final static int totalWidth = 5*borderThickness + 4*patchWidth;
final static int totalHeight = 4*borderThickness + 3*patchHeight;
private static void drawLittleStudy(Graphics2D g2d,
int x00, int y00,
int patchWidth, int patchHeight, int borderThickness, int totalWidth, int totalHeight) {
g2d.setColor(new java.awt.Color(240,240,240));
g2d.fillRect(x00,y00,totalWidth, totalHeight);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 4; ++col) {
int x0 = x00 + borderThickness + col*(patchWidth+borderThickness);
int y0 = y00 + borderThickness + row*(patchHeight+borderThickness);
int x1 = x0 + patchWidth;
int y1 = y0 + patchHeight;
g2d.setColor(java.awt.Color.WHITE);
g2d.fillRect(x0, y0, patchWidth, patchHeight);
boolean antialias = (col >= 2);
boolean pure = (col % 2 == 1);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, pure ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE);
g2d.setColor(java.awt.Color.RED);
if (row == 0) {
// lines (drawLine)
// diagonals
g2d.drawLine(x0,y1, x1,y0);
g2d.drawLine(x0,y0, x1,y1);
// orthogonals
g2d.drawLine((x0+patchWidth/4),y0, (x0+patchWidth*3/4),y0);
g2d.drawLine((x0+patchWidth/4),y1, (x0+patchWidth*3/4),y1);
g2d.drawLine(x0,(y0+patchHeight/4), x0,(y0+patchHeight*3/4));
g2d.drawLine(x1,(y0+patchHeight/4), x1,(y0+patchHeight*3/4));
} else if (row == 1) {
// outlines (drawPolygon)
// A stopsign
g2d.drawPolygon(new int[] {x0+patchWidth/2-2, x0, x0, x0+patchWidth/2-2, x0+patchWidth/2+2, x1, x1, x0+patchWidth/2+2},
new int[] {y0, y0+patchHeight/2-2, y0+patchHeight/2+2, y1, y1, y0+patchHeight/2+2, y0+patchHeight/2-2, y0},
8);
} else if (row == 2) {
// fill (fillPolygon)
// A stopsign
g2d.fillPolygon(new int[] {x0+patchWidth/2-2, x0, x0, x0+patchWidth/2-2, x0+patchWidth/2+2, x1, x1, x0+patchWidth/2+2},
new int[] {y0, y0+patchHeight/2-2, y0+patchHeight/2+2, y1, y1, y0+patchHeight/2+2, y0+patchHeight/2-2, y0},
8);
}
}
}
} // drawLittleStudy
// Show a study, previously created by drawLittleStudy(), magnified and annotated.
private static void showMagnifiedAndAnnotatedStudy(Graphics g,
BufferedImage studyImage,
int x00, int y00,
int patchWidth, int patchHeight, int borderThickness, int totalWidth, int totalHeight, int mag,
ImageObserver imageObserver) {
// Magnify the image
g.drawImage(studyImage,
/*dst*/ x00,y00,x00+totalWidth*mag,y00+totalHeight*mag,
/*src*/ 0,0,totalWidth,totalHeight,
imageObserver);
// Draw annotations on each picture in black,
// in the highest quality non-biased mode
// (now that we know what that is!)
g.setColor(java.awt.Color.BLACK);
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 4; ++col) {
int x0 = borderThickness + col*(patchWidth+borderThickness);
int y0 = borderThickness + row*(patchHeight+borderThickness);
int x1 = x0 + patchWidth;
int y1 = y0 + patchHeight;
if (false) {
g.drawLine(x00+x0*mag,y00+y0*mag, x00+x1*mag,y00+y0*mag);
g.drawLine(x00+x1*mag,y00+y0*mag, x00+x1*mag,y00+y1*mag);
g.drawLine(x00+x1*mag,y00+y1*mag, x00+x0*mag,y00+y1*mag);
g.drawLine(x00+x0*mag,y00+y1*mag, x00+x0*mag,y00+y0*mag);
}
if (row == 0) {
// diagonals
g.drawLine(x00+x0*mag,y00+y1*mag, x00+x1*mag,y00+y0*mag);
g.drawLine(x00+x0*mag,y00+y0*mag, x00+x1*mag,y00+y1*mag);
// orthogonals
g.drawLine(x00+(x0+patchWidth/4)*mag,y00+y0*mag, x00+(x0+patchWidth*3/4)*mag,y00+y0*mag);
g.drawLine(x00+(x0+patchWidth/4)*mag,y00+y1*mag, x00+(x0+patchWidth*3/4)*mag,y00+y1*mag);
g.drawLine(x00+x0*mag,y00+(y0+patchHeight/4)*mag, x00+x0*mag,y00+(y0+patchHeight*3/4)*mag);
g.drawLine(x00+x1*mag,y00+(y0+patchHeight/4)*mag, x00+x1*mag,y00+(y0+patchHeight*3/4)*mag);
} else { // row 1 or 2
// A stopsign
g.drawPolygon(new int[] {x00+(x0+patchWidth/2-2)*mag, x00+x0*mag, x00+x0*mag, x00+(x0+patchWidth/2-2)*mag, x00+(x0+patchWidth/2+2)*mag, x00+x1*mag, x00+x1*mag, x00+(x0+patchWidth/2+2)*mag},
new int[] {y00+y0*mag, y00+(y0+patchHeight/2-2)*mag, y00+(y0+patchHeight/2+2)*mag, y00+y1*mag, y00+y1*mag, y00+(y0+patchHeight/2+2)*mag, y00+(y0+patchHeight/2-2)*mag, y00+y0*mag},
8);
}
}
}
FontMetrics fm = g.getFontMetrics();
{
String[][] texts = {
{"ANTIALIAS_OFF", "STROKE_NORMALIZE"},
{"ANTIALIAS_OFF", "STROKE_PURE"},
{"ANTIALIAS_ON", "STROKE_NORMALIZE"},
{"ANTIALIAS_ON", "STROKE_PURE"},
};
for (int col = 0; col < 4; ++col) {
int xCenter = borderThickness*mag + col*(patchWidth+borderThickness)*mag + patchWidth*mag/2;
{
int x = x00 + xCenter - fm.stringWidth(texts[col][0])/2;
int y = y00 + 3*(patchHeight+borderThickness)*mag + fm.getAscent();
g.drawString(texts[col][0], x,y);
x = xCenter - fm.stringWidth(texts[col][1])/2;
y += fm.getHeight();
g.drawString(texts[col][1], x,y);
}
}
}
{
String[] texts = {
"drawLine",
"drawPolygon",
"fillPolygon",
};
for (int row = 0; row < 3; ++row) {
int yCenter = y00 + borderThickness*mag + row*(patchHeight+borderThickness)*mag + patchHeight*mag/2;
int x = x00 + 4*(patchWidth+borderThickness)*mag + 10;
g.drawString(texts[row], x,yCenter);
}
}
} // showMagnifiedAndAnnotatedStudy
private static Dimension figureOutPreferredSize(FontMetrics fm) {
int preferredWidth = (totalWidth-borderThickness)*mag + 10 + fm.stringWidth("drawPolygon") + 9;
int preferredHeight = fm.getHeight() + totalHeight + (totalHeight-borderThickness)*mag + 2*fm.getHeight() + 2;
return new Dimension(preferredWidth, preferredHeight);
}
private static class IndirectExaminationView extends JComponent {
public IndirectExaminationView() {
setFont(new Font("Times", Font.PLAIN, 12));
setPreferredSize(figureOutPreferredSize(getFontMetrics(getFont())));
}
#Override public void paintComponent(Graphics g) {
FontMetrics fm = g.getFontMetrics();
g.setColor(java.awt.Color.BLACK);
g.drawString("through BufferedImage:", 0,fm.getAscent());
// The following seem equivalent
java.awt.image.BufferedImage studyImage = new java.awt.image.BufferedImage(totalWidth, totalHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
//java.awt.image.BufferedImage studyImage = (BufferedImage)this.createImage(totalWidth, totalHeight);
drawLittleStudy(studyImage.createGraphics(),
0,0,
patchWidth, patchHeight, borderThickness, totalWidth, totalHeight);
Graphics2D studyImageGraphics2D = studyImage.createGraphics();
g.drawImage(studyImage,
/*dst*/ 0,fm.getHeight(),totalWidth,fm.getHeight()+totalHeight,
/*src*/ 0,0,totalWidth,totalHeight,
this);
showMagnifiedAndAnnotatedStudy(g, studyImage,
0,fm.getHeight()+totalHeight,
patchWidth, patchHeight, borderThickness, totalWidth, totalHeight, mag, this);
}
} // DirectExaminationView
private static class DirectExaminationView extends JComponent {
public DirectExaminationView() {
setFont(new Font("Times", Font.PLAIN, 12));
setPreferredSize(figureOutPreferredSize(getFontMetrics(getFont())));
}
private BufferedImage imgFromTheRobot = null;
#Override public void paintComponent(Graphics g) {
final FontMetrics fm = g.getFontMetrics();
g.setColor(java.awt.Color.BLACK);
g.drawString("direct to JComponent:", 0,fm.getAscent());
drawLittleStudy((Graphics2D)g,
0,fm.getHeight(),
patchWidth, patchHeight, borderThickness, totalWidth, totalHeight);
if (imgFromTheRobot != null) {
System.out.println(" drawing image from robot");
showMagnifiedAndAnnotatedStudy(g, imgFromTheRobot,
0, fm.getHeight()+totalHeight,
patchWidth, patchHeight, borderThickness, totalWidth, totalHeight, mag, this);
imgFromTheRobot = null;
} else {
System.out.println(" scheduling a robot");
g.drawString("*** SCREEN CAPTURE PENDING ***", 0, fm.getHeight()+totalHeight+fm.getHeight()+fm.getHeight());
// Most reliable way to do it seems to be to put it on a timer after a delay.
Timer timer = new Timer(1000/2, new ActionListener() {
#Override public void actionPerformed(ActionEvent ae) {
System.out.println(" in timer callback");
Robot robot;
try {
robot = new Robot();
} catch (AWTException e) {
System.err.println("caught AWTException: "+e);
throw new Error(e);
}
Point myTopLeftOnScreen = getLocationOnScreen();
Rectangle rect = new Rectangle(
myTopLeftOnScreen.x, myTopLeftOnScreen.y + fm.getHeight(),
totalWidth,totalHeight);
BufferedImage img = robot.createScreenCapture(rect);
imgFromTheRobot = img;
repaint();
System.out.println(" out timer callback");
}
});
timer.setRepeats(false);
timer.start();
}
}
} // DirectExaminationView
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override public void run()
{
final JFrame directFrame = new JFrame("direct to JComponent") {{
getContentPane().add(new DirectExaminationView());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocation(0,0);
setVisible(true);
}};
new JFrame("through BufferedImage") {{
getContentPane().add(new IndirectExaminationView());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocation(directFrame.getWidth(),0);
setVisible(true);
}};
}
});
}
} // class AntiAliasingStudy

Categories

Resources