I have the following Processing program:
//using Papplet instead of STDraw to visually represent my grid, created by Mahmed Ibrahim
import java.awt.Color;
import processing.core.*;
import processing.core.PApplet;
public class C4Grid extends PApplet {
PShape s;
PShape[][] circleSpaces;
boolean[][] circleSpacesFilled;
boolean[][] circleHasYelowPiece;
boolean[][] circleHasRedPiece;
final float SPACES_BETWEEN_ROWS = 110;
final float SPACES_BETWEEN_COLUMNS = 130;
public C4Grid(){}
public void setup() {
System.out.println("it got to here where it breaks");
size(1000, 1000, P2D);
// Making the shape of the grid using vertices
// so I'm manually drawing my polygon.
s = createShape();
s.beginShape();
s.fill(34, 56, 100);
s.tint(34, 56, 100);
s.stroke(0);
s.strokeWeight(5);
s.vertex(400, 400);
s.vertex(400, -440);
s.vertex(360, -440);
s.vertex(360, -400);
s.vertex(-360, -400);
s.vertex(-360, -440);
s.vertex(-400, -440);
s.vertex(-400, 420);
s.vertex(-420, 420);
s.vertex(-420, 440);
s.vertex(-360, 440);
s.vertex(-360, 420);
s.vertex(-380, 420);
s.vertex(-380, 400);
s.vertex(380, 400);
s.vertex(380, 420);
s.vertex(360, 420);
s.vertex(360, 440);
s.vertex(420, 440);
s.vertex(420, 420);
s.vertex(400, 420);
s.vertex(400, 420);
s.vertex(400, -440);
s.vertex(400, 400);
s.endShape();
System.out.println("it got to here where it breaks");
// using a 2D array to create a grid of circles
// which will represent the spaces on the grid
circleHasYelowPiece = new boolean[7][6];
circleHasRedPiece = new boolean[7][6];
circleSpacesFilled = new boolean[7][6];
circleSpaces = new PShape[7][6];
for (int row = 0; row < 7; row++) {
for (int column = 0; column < 6; column++) {
circleSpaces[row][column] = createShape(ELLIPSE, -380 + (row) * SPACES_BETWEEN_ROWS,
-370 + (column) * SPACES_BETWEEN_COLUMNS, 100, 100);
circleSpaces[row][column].disableStyle();
stroke(0);
strokeWeight(5);
circleSpacesFilled[row][column] = false;
circleHasRedPiece[row][column] = false;
circleHasYelowPiece[row][column] = false;
}
}
}
public void draw() {
translate(width / 2, height / 2);
shape(s);
for (int row = 0; row < 7; row++) {
for (int column = 0; column < 6; column++) {
shape(circleSpaces[row][column]);
}
}
}
public boolean piecePlaced(int column, Color pieceColor) {
column = column - 1; // the choice are form 1-7 but in an array its 0-6;
boolean moveDone = false;
int i = 5;
Color red = new Color(255, 0, 0);
while (i >= 0) {
if (circleSpacesFilled[column][i] == false) {
circleSpacesFilled[column][i] = true;
if (pieceColor.equals(red)) {
circleHasRedPiece[column][i] = true;
circleSpaces[column][i].fill(255, 0, 0);
circleSpaces[column][i].tint(255, 0, 0);
} else {
circleHasYelowPiece[column][i] = true;
circleSpaces[column][i].fill(255, 255, 0);
circleSpaces[column][i].tint(255, 255, 0);
}
return true;
}
}
return false;
}
}
When I run it, I get this NullPointerException. Notice that the exception is coming from within Processing's libraries - it's not directly caused by my own code!
The 3 lines that are suspect are:
currentGame = new C4Game(player1Is,player2Is,player1Color,player2Color);
theGrid = new C4Grid(); theGrid.setup();
s= createShape(); near the top of setup()
currentGame, theGrid, and s are all non-null (I've checked countless times).
Even when I test each line in isolation, I get an error in anything that related to the PShape class. I got rid of every PShape object and it worked, but is there a way to fix it so I can use PShape as part of my code?
When I run your code, I don't get a NullPointerException. I get an error that says this:
When not using the PDE, size() can only be used inside settings().
Remove the size() method from setup(), and add the following:
public void settings() {
size(1000, 1000, "processing.opengl.PGraphics2D");
}
And the error says it all. When you're using Processing as a library, you can't call the size() function from the setup() function. Call it from the settings() function instead.
If I make that change, your code runs fine:
I created a simple "elevator" program (it's still in the beginning stages) that goes up 1 floor when I click UP and vice versa.
I messed up pretty badly when I drew all my components into JFrame, and as expected, it flickers every time I click the button (repaints). I know the solution to be draw in the JPanel and put the said panel in the JFrame, but I have a problem translating my JFrame components into JPanel. I've tried extending JPanel, creating a JFrame object and then overriding the paintComponent() method and doing my drawing there, but when I compile it does not draw it at all. It only creates the frame.
Can anyone help me or give me tips on how to proceed "transferring" my programming from JFrame based to JPanel based? Thank you in advance!
My code is below:
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.Timer;
import java.math.*;
public class MyCanvas extends JFrame {
private int up = 0;
private int down = 0;
private int movefloorup = 0;
private int buildingType;//type of building (1 = Residential, 2 = Commercial)
private int totnumoffloors; //for the total number of floors
private int numofelevators; //for the number of elevators to be generated
private int floorlimit = 0; //to determine up until where the elevator will be moving
private int currenttime; //determine the time of the day the elevator is operating (1 = Morning, 2 = Lunch, 3 = Afternooon)
//For elevator resetting to bottom
private int rectX = 190;
private int switchmarker = 0;
//Lines and stuff
private int horizborder = 0;
private int bordercount = 0;
private class UpAction implements ActionListener //move the elevator up
{
public void actionPerformed(ActionEvent e)
{
if(movefloorup<780){
repaint();
up++;
movefloorup = movefloorup + 130;
//repaint();
}
else
{
switchmarker = 1;
movefloorup = 0;
repaint();
}
}
}
private class DownAction implements ActionListener //move the elevator down
{
public void actionPerformed(ActionEvent e)
{
if(movefloorup>0){
repaint();
down++;
movefloorup = movefloorup - 130;
//repaint();
}
else
{
switchmarker = 0;
movefloorup = 780;
repaint();
}
}
}
public MyCanvas(int buildingType, int totnumoffloors, int numofelevators, int currenttime){
this.buildingType = buildingType;
this.totnumoffloors = totnumoffloors;
this.numofelevators = numofelevators;
this.currenttime = currenttime;
String title;
if(this.buildingType == 1)
{
title = "Residential Building";
}
else
{
title = "Commercial Building";
}
setLayout(null);
horizborder = 500*((int)Math.ceil((double)totnumoffloors/7)); //calculating how wide the window should be
bordercount = ((int)Math.ceil((double)totnumoffloors/7)); //counts how many borders there will be
//NOTES
//A floor is 130 units in the Y-Direction
//Drawing the bulding layout
if(totnumoffloors>7)
{
setSize(horizborder, 1000);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle(title);
setLayout(new BorderLayout());
getContentPane().setBackground(Color.WHITE);
}
else{
setSize(500, 1000); //suitable for 7 floors
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle(title);
setLayout(new BorderLayout());
getContentPane().setBackground(Color.WHITE);
}
JButton upButton = new JButton("UP");
upButton.addActionListener(new UpAction());
add(upButton, BorderLayout.NORTH);
JButton downButton = new JButton("DOWN");
//downButton.setBounds(0, 0, 220, 30);
//downButton.setLocation(100, 100);
downButton.addActionListener(new DownAction());
add(downButton, BorderLayout.SOUTH);
}
public void paint(Graphics graphics){ //this is where you draw shit
super.paint(graphics);
//Floors
graphics.setColor(Color.RED);
int numoffloorsY = 830;
int numoffloorsX = 830;
int floorbeginning = 0;
int floorcounter = 1;
int floorflag = 0;
int rightedge = 500;
if(this.totnumoffloors>7) //drawing the floors
{
//Default number of floors -> 7
for(int i = 0;i<totnumoffloors;i++)
{
graphics.setColor(Color.RED);
graphics.drawLine(floorbeginning,numoffloorsX,rightedge,numoffloorsY); //FLOORS
graphics.setColor(Color.DARK_GRAY);
graphics.setFont(new Font("TimesRoman", Font.PLAIN, 15));
graphics.drawString(" "+floorcounter, floorbeginning+10, numoffloorsY+20); //SAVE THIS FOR DRAWING FLOORS
numoffloorsY = numoffloorsY - 130;
numoffloorsX = numoffloorsX - 130;
floorcounter++;
floorflag++;
if(floorflag==7)
{
floorbeginning = floorbeginning + 500;
rightedge = rightedge+500;
numoffloorsY = 830;
numoffloorsX = 830;
floorflag = 0;
}
}
//Every other floor past 7 will be added here.
/*for(int i = 0;i<totnumoffloors-7;i++)
{
//System.out.println("LOLOOLO");
graphics.setColor(Color.RED);
graphics.drawLine(floorbeginning,numoffloorsX,horizborder,numoffloorsY);
graphics.setColor(Color.DARK_GRAY);
graphics.setFont(new Font("TimesRoman", Font.PLAIN, 15));
graphics.drawString(" "+floorcounter, floorbeginning, numoffloorsY+20);
//graphics.setColor(Color.DARK_GRAY);
//graphics.drawLine(500,0,500,1000);
floorcounter++;
numoffloorsY = numoffloorsY - 130;
numoffloorsX = numoffloorsX - 130;
}*/
//DIVIDING LINE -> to determine the first 7 floors from the ones higher up.
for(int i=0;i<bordercount;i++)
{
graphics.setColor(Color.DARK_GRAY);
graphics.drawLine(500*i,0,500*i,1000);
}
}
else{
for(int i = 0;i<this.totnumoffloors;i++)
{
graphics.setColor(Color.RED);
graphics.drawLine(0,numoffloorsX,500,numoffloorsY);
graphics.setColor(Color.DARK_GRAY);
graphics.setFont(new Font("TimesRoman", Font.PLAIN, 15));
graphics.drawString(" "+floorcounter, floorbeginning+10, numoffloorsY+20); //SAVE THIS FOR DRAWING FLOOR
numoffloorsY = numoffloorsY - 130;
numoffloorsX = numoffloorsX - 130;
floorcounter++;
}
}
//Drawing the elevators
if(up>0 && movefloorup<1000){
graphics.setColor(Color.GRAY);
if(switchmarker==1)
{
System.out.println("ELSA");
rectX = 690;
//rectX = rectX + 190;
}
else
{
rectX = 190;
}
System.out.println(rectX);
graphics.fillRect(rectX, 850-movefloorup, 100, 100); //this needs to match the stats of the rectangle to fill it properly
graphics.drawRect(rectX, 850-movefloorup, 100, 100);
//Line for the door
graphics.setColor(Color.BLACK);
graphics.drawLine(rectX+50, 850-movefloorup, rectX+50, 950-movefloorup); //match the y-coordinate for the rectangle, add 100 for the y-coordinate of the other end
System.out.println(movefloorup);
System.out.println(switchmarker);
//drawLine(x1, y1, x2, y2); --From (x1,y1) to (x2,y2)
}
else if(down>0 && movefloorup>0)
{
graphics.setColor(Color.GRAY);
if(switchmarker==1) //This determines when the elevator should move to the next column of higher floors.
{
System.out.println("ELSA");
rectX = 500;
}
System.out.println(rectX);
graphics.fillRect(rectX, 850-movefloorup, 100, 100); //this needs to match the stats of the rectangle to fill it properly
//graphics.drawRect(190, 850 + movefloorup, 100, 100); //FIRST FLOOR
graphics.drawRect(rectX, 850-movefloorup, 100, 100); //SECOND FLOOR (135 units difference in Y-axis between floors)
//x-coordinate, y-coordinate, width, height
//Line for the door
graphics.setColor(Color.BLACK);
graphics.drawLine(rectX+50, 850-movefloorup, rectX+50, 950-movefloorup); //match the y-coordinate for the rectangle, add 100 for the y-coordinate of the other end
System.out.println(movefloorup);
System.out.println(switchmarker);
}
else
{
graphics.setColor(Color.GRAY);
graphics.fillRect(190, 850, 100, 100); //this needs to match the stats of the rectangle to fill it properly
graphics.drawRect(190, 850, 100, 100); //FIRST FLOOR
graphics.drawRect(190, 850, 100, 100); //SECOND FLOOR (135 units difference in Y-axis between floors)
//x-coordinate, y-coordinate, width, height
//Line for the door
graphics.setColor(Color.BLACK);
graphics.drawLine(240, 850, 240, 950); //match the y-coordinate for the rectangle, add 100 for the y-coordinate of the other end
//System.out.println("In else!");
}
}
}
The main class just gets input from the user, such as the number of floors, time of day, etc.
This is going to be a little messy.
Start by creating a custom component that extends from JPanel (I'll call it ElevatorPane).
Take the contents of the current paint method and place them within this components paintComponent method. This will involve moving the instance variables that the paintComponent method will need including, totnumoffloors, bordercount, up, down, movefloorup, switchmarker, rectX
This is where it gets a little messy...
You need to take the contents of your ActionListeners and translate these into methods within the ElevatorPane, this way you expose the functionality without exposing the details...
Create a constructor within ElevatorPane that takes the number of floors.
Override the getPrefferedSize method of ElevatorPane and return the size that the component needs to be to satisfy your needs...
Create an instance field of ElevatorPane in MyCanvas, instantiate it and add it to the frame.
Clean, build, run...
I want to put several rectangle i a row. But because I'm new to Android and specially to Bitmap, Canvas and so on, I need some help.
It should look like this, only with rectangles:
I have created one rectangle with this code:
Paint paint = new Paint();
paint.setColor(Color.parseColor("#CD5C5C"));
Bitmap bg = Bitmap.createBitmap(480, 800, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bg);
canvas.drawRect(50, 80, 200, 200, paint);
RelativeLayout ll = (RelativeLayout) findViewById(R.id.rect);
ImageView iV = new ImageView(this);
iV.setImageBitmap(bg);
ll.addView(iV);
But now I dont know how to create more rectangles with different colors in a row.
I'm really new and sorry for that maybe stupid question but I need help for it.
Can anybody guide me how to do this in the best way?
The key here are these lines:
paint.setColor(Color.parseColor("#CD5C5C"));
canvas.drawRect(50, 80, 200, 200, paint);
They set the colour and draw a rectangle. You can now repeat these lines to get 2 rectangles:
paint.setColor(Color.parseColor("#CD5C5C"));
canvas.drawRect(50, 80, 200, 200, paint);
paint.setColor(Color.parseColor("#DDDDDD"));
canvas.drawRect(210, 80, 360, 200, paint);
Note that I have changed the colour and co-ordinates a little bit. You could continue doing this several times to get all of your rectangles drawn.
Better still use a variable for the x and y coordinates, and use a loop:
int left = 50; // initial start position of rectangles (50 pixels from left)
int top = 50; // 50 pixels from the top
int width = 150;
int height = 150;
for (int row = 0; row < 2; row++) { // draw 2 rows
for(int col = 0; col < 4; col++) { // draw 4 columns
paint.setColor(Color.parseColor("#CD5C5C"));
canvas.drawRect(left, top, left+width, top+height, paint);
left = (left + width + 10); // set new left co-ordinate + 10 pixel gap
// Do other things here
// i.e. change colour
}
top = top + height + 10; // move to new row by changing the top co-ordinate
}
Hope that helps.
This should do it. I tried to self-document my code as mush as possible. This is very dynamic i.e. you can adjust the height, width, xPad, yPad, etc. and the window will compensate.
import java.awt.*;
import java.util.Random;
import javax.swing.*;
public class RectanglesPanel extends JPanel {
public static final int[] COLORS = new int[] {
0xFFFFFF, 0xF67457, 0xFFC238, 0xEFEF38,
0xBCCACA, 0x75D1E0, 0x84E0C2, 0xC2E749
};
private static Random rand = new Random();
private int width = 80;
private int height = 50;
private int rows = 2;
private int cols = 4;
private int xPad = 20;
private int yPad = 30;
private float strokeWidth = 2.0f;
int windowWidth = calculateOffset(width, cols, xPad);
int windowHeight = calculateOffset(height, rows, yPad);
public RectanglesPanel() {
setPreferredSize(new Dimension(windowWidth, windowHeight));
}
private int calculateOffset(int whole, int partitions, int padding) {
return (whole * partitions) + (padding * (partitions + 1));
}
#Override
public void paintComponent(Graphics g) {
Stroke stroke = new BasicStroke(strokeWidth,
BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER);
((Graphics2D)g).setStroke(stroke);
// Fill in background.
g.setColor(new Color(0xF6F6F6));
g.fillRect(0, 0, windowWidth, windowHeight);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
int x = calculateOffset(width, col, xPad);
int y = calculateOffset(height, row, yPad);
int color = (row * cols + col) % COLORS.length;
// Fill in rectangle.
g.setColor(new Color(COLORS[color]));
g.fillRect(x, y, width, height);
// Stroke the border of the rectangle.
g.setColor(new Color(0xE7E7E7));
g.drawRect(x, y, width, height);
}
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new RectanglesPanel();
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
use for loop keeping the y coordinates constant
for(i=0;i<=200;i=i+40)
{
canvas.drawRect(i,0,i+30,100);
}
for next row increase y coordinate by your require amount and repeat the same or use nested for loops
you can set color by
myPaint.setColor(color.black);
myPaint.setStyle(Style.FILL);
canvas.drawRect(0,0,100,100, myPaint);
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