It seems that stroking on sub-pixel coordinates became broken in Java 8.
I have three sets of cases, shown on screenshots (columns represent cases, rows represent different stroke widths) :
Java 7u51 (400% scale)
Java 8u60 (400% scale)
Fill and stroke on the same coordinates. Works as intended, stroked area is larger than the filling area.
Stroking is shrunk (by stroke width) and centered (by half of the width) to be inside bounds of the filling region. This part is broken in Java 8 for 1px stroke, where painting occurs on a sub-pixel coordinate (first row); 3px stroke doesn't have this problem (third row). It seems that 0.5 is rounded up for the 1px stroke.
Filling rectangle is shrunk an centered the same way of case 2. I need this on graphics, which support sub-pixel drawing, to make non-overlapping fill when cells are overlapping. Here you can see that fill operation rounds down 0.5 to 0, so it's only stroking problem.
The code is below:
import static java.awt.BasicStroke.*;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class TestCase
{
public static void main(String[] args)
{
JFrame frame = new JFrame("Test case");
frame.setSize(115, 115);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestPanel());
frame.setVisible(true);
}
private static class TestPanel extends JPanel
{
TestPanel()
{
setOpaque(true);
}
#Override
protected void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.white);
g2.fill(getBounds());
Rectangle2D rect = new Rectangle2D.Double();
Color background = new Color(0, 255, 255);
Color border = new Color(255, 0, 0, 128);
Stroke STROKE_1PX = new BasicStroke(1, CAP_SQUARE, JOIN_MITER);
Stroke STROKE_2PX = new BasicStroke(2, CAP_SQUARE, JOIN_MITER);
Stroke STROKE_3PX = new BasicStroke(3, CAP_SQUARE, JOIN_MITER);
g2.translate(10, 10);
/**
* Filling and stroking by original coordinates
*/
rect.setRect(0, 0, 25, 25);
g2.setColor(background);
g2.fill(rect);
g2.setColor(border);
g2.setStroke(STROKE_1PX);
g2.draw(rect);
g2.translate(0, 35);
g2.setColor(background);
g2.fill(rect);
g2.setColor(border);
g2.setStroke(STROKE_2PX);
g2.draw(rect);
g2.translate(0, 35);
g2.setColor(background);
g2.fill(rect);
g2.setColor(border);
g2.setStroke(STROKE_3PX);
g2.draw(rect);
/**
* Stroking is shrunk to be inside the filling rect
*/
g2.translate(35, -70);
rect.setRect(0, 0, 25, 25);
g2.setColor(background);
g2.fill(rect);
rect.setRect(0.5, 0.5, 24, 24);
g2.setColor(border);
g2.setStroke(STROKE_1PX);
g2.draw(rect);
g2.translate(0, 35);
rect.setRect(0, 0, 25, 25);
g2.setColor(background);
g2.fill(rect);
rect.setRect(1, 1, 23, 23);
g2.setColor(border);
g2.setStroke(STROKE_2PX);
g2.draw(rect);
g2.translate(0, 35);
rect.setRect(0, 0, 25, 25);
g2.setColor(background);
g2.fill(rect);
rect.setRect(1.5, 1.5, 22, 22);
g2.setColor(border);
g2.setStroke(STROKE_3PX);
g2.draw(rect);
/**
* Filling rect is additionally shrunk and centered
*/
g2.translate(35, -70);
rect.setRect(0.5, 0.5, 24, 24);
g2.setColor(background);
g2.fill(rect);
g2.setColor(border);
g2.setStroke(STROKE_1PX);
g2.draw(rect);
g2.translate(0, 35);
rect.setRect(1, 1, 23, 23);
g2.setColor(background);
g2.fill(rect);
g2.setColor(border);
g2.setStroke(STROKE_2PX);
g2.draw(rect);
g2.translate(0, 35);
rect.setRect(1.5, 1.5, 22, 22);
g2.setColor(background);
g2.fill(rect);
g2.setColor(border);
g2.setStroke(STROKE_3PX);
g2.draw(rect);
}
}
}
As I tested, Java 7 doesn't have this problem (tried on 7u51), Windows (8u77) and Mac (8u60) too. Tried on Ubuntu (8u60 and 8u77) and Linux Mint (8u60) on different machines and the bug was here.
Did anyone faced such issue? Is there any general workaround?
I cannot simply handle 1px case in places where stroking is used. It's because there is a lot of places and I'm working with different Graphics2D implementations and it seems, that from what I've used the issue reproduces only on SunGraphics2D. This means I need to use instanceOf in these places to not break common logic.
From bug report discussion:
It is an xrender bug -Dsun.java2d.xrender=false cures it.
I haven't checked this solution myself as I didn't received any notifications from bug reporting system besides that it was reviewed. And as it was Linux-only problem it was decided to wait for official fixes as there was not so many Linux users among our customers.
I would suggest you extend Graphics2D and add correctional measures there on the places where needed.
That way you can keep your main flow consistent, and add all other "logic" errors can be handled where they need to be handled.
This saves you the overhead of modifying N files and undoing those changes if it gets fixed, and keeping the logic where it should be.
Related
I am currently trying to add a day/night cycle to my game, and I am having trouble with lighting. If there is a light source, I want it to create a circle of light around its area. I've made night time by drawing a black rectangle over the screen that becomes less transparent when its night time. That makes the whole screen darker. The code that I have works for one light source, but if I have a light source that overlaps another light source, it makes a strange darker ring. I understand what is causing that ring, but nothing I have tried is removing the ring.
public void render(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(new Color(20, 20, 20, transparency));
float[] dist = { 0.5f, 1.0f };
Color[] color = { new Color(230, 230, 180, 150), new Color(20, 20, 20, transparency) };
//Color[] color = { new Color(230, 230, 180, 150), new Color(0, 0, 0, 0) }; //Old unused color for the gradient
Area a = new Area(new Rectangle(0, 0, Main_Game.WIDTH, Main_Game.HEIGHT));
for (int i = 0; i < lightList.size(); i++) {
RadialGradientPaint p = new RadialGradientPaint(lightList.get(i).center, lightList.get(i).size, dist, color);
g2d.setPaint(p);
g2d.fillOval(lightList.get(i).center.x - lightList.get(i).size, lightList.get(i).center.y - lightList.get(i).size, lightList.get(i).size*2, lightList.get(i).size*2);
a.subtract(new Area(new Ellipse2D.Double(lightList.get(i).center.x - lightList.get(i).size, lightList.get(i).center.y - lightList.get(i).size, lightList.get(i).size*2, lightList.get(i).size*2)));
}
g2d.setColor(new Color(20, 20, 20, transparency));
g2d.fill(a);
}
That code has a linked list that holds all the light sources. And the transparency variable stores the transparency the box that darkens the screen when it's night time.
The ideal way to fix this would be to combine all the RadialGradientPaint objects for each light into one paint object and that way, the lights wouldn't overlap weirdly.
Here is what it looks like when it works with only one light source:
Here is a picture of the weirdness of lighting that I'm getting at night time (there is no issue during the daytime of the game) when there are two light sources close to each other:
Any sort of help or recommendation to setup this lighting would be greatly appreciated :)
I have tried subtracting one lighting circle from another so that they aren't overlapping, but that made the two lights not merge very nicely. I tried drawing the lights as a rectangle instead of an oval but that made a similar issue.
I have a code example that produces weird results when trying to draw lines with semitransparent colors. This only seems to happen for lines with an "upward" direction. I'm using an older JDK 1.8.0_102, but the issue is also reproduced on JDK 13.0.1. This sure seems like a bug, but maybe there's an explanation?
EDIT: The "weird artifacts" from the title are better described as "double width". Please read the comments below the question for more detail. TLDR; the problem persists - a weird behavior IS reproduced with the below code.
BufferedImage image = new BufferedImage(46, 46, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.setBackground(Color.WHITE);
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
graphics.setColor(new Color(0, 0, 0, 255));
graphics.drawLine(5, 20, 20, 5);
graphics.drawLine(25, 5, 40, 20);
graphics.setColor(new Color(0, 0, 0, 128));
graphics.drawLine(5, 40, 20, 25);
graphics.drawLine(25, 25, 40, 40);
graphics.dispose();
ImageIO.write(image, "png", new File("image.png"));
The result from this is the following image:
EDIT #2: During further investigation, I tried setting the Graphics2D stroke property of width 2, in hopes it would at least get me consistent behavior, but unfortunately, I was greeted with more inconsistency - "uppward" lines are arguably 2 pixels wide, while "downward" lines seem to be 3 pixels wide. This is the output of simply adding this line before drawing anything:
graphics.setStroke(new BasicStroke(2));
I'm currently working on a project with the theme of earth hour, and we are only allowed to use rectangles, circles and triangles. Here's the image i'm tring to create (not exactly, mine will be much more simplified!):
https://www.google.com/search?q=earth+hour&biw=1366&bih=586&source=lnms&tbm=isch&sa=X&ved=0ahUKEwj__5H0vtvQAhXLrlQKHTi8BagQ_AUIBygC#imgrc=fQkBxn0a8LnwbM%3A
(not sure if you could see the link)
But when i'm coding it, i'm running into trouble to rotate those rectangles to stand on the tangent line of the circle. I'm a student just learnt some basics of java, like loops and arrays. So my quesiton is that if there's some understandable way that doesn't involve some complex and exotic methods that could rotate those rectangles? I know it will probably involve some complicated solutions that is beyond my knowledge. But any help is much appreciated.
this is part of the code that i build the building standing perpendicularly to the circle(earth):
// create mid buildings
Color blc = new Color(0, 0, 0);
Rectangle midBld = new Rectangle(240, 220, 20, 40, blc);
midBld.draw(g);
Rectangle midBld1 = new Rectangle(242, 190, 16, 30, blc);
midBld1.draw(g);
Triangle midBld2 = new Triangle(250, 160, 8, 30, blc);
midBld2.draw(g);
Triangle midBld3 = new Triangle(250, 160, -8, 30, blc);
midBld3.draw(g);
A Rectangle cannot be rotated, its edges are always in parallel to the axis. But you can rotate and translate the coordinate system in witch you draw the shapes. From Graphics2D API doc.
All coordinates passed to a Graphics2D object are specified in a device-independent coordinate system called User Space, which is used by applications. The Graphics2D object contains an AffineTransform object as part of its rendering state that defines how to convert coordinates from user space to device-dependent coordinates in Device Space.
Graphics2D also provide two methods that are useful in this task: translate that moves the origin of the coordinates and rotate that, well, rotates the system.
package graphics;
import javax.swing.*;
import java.awt.*;
/**
* Earth Hour
*/
public class RotateRect extends JFrame {
private static final int WIDTH = 400;
private static final int HEIGHT = 400;
public RotateRect() {
this.setSize(WIDTH, HEIGHT);
this.setTitle("Rotate Rectangles");
this.setContentPane(new JPanel() {
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// Background: White
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, this.getWidth(), this.getHeight());
// Draw "Earth": Center(200, 400), Radius=200
g2.setColor(Color.BLACK);
g2.fillOval(0, 200, 400, 400);
// Move origin to center of the canvas (surface of earth)
g2.translate(200, 200);
// Rotate the coordinate system, relative to the center of earth.
// note x, y are in the translated system
// Transforms are accumulative
g2.rotate(-Math.PI/6, 0, 200);
// Fill a rectangle with top-left corner at (-20, 80) in the rotated system
// It's important to make the rectangle symmetrical to the y-axis, otherwise the building looks
// funny.
// Also, make the building "sunk" a little, so that it's fully on the ground.
g2.fillRect(-20, -80, 40, 100);
g2.rotate(Math.PI/3, 0, 200);
g2.fillRect(-20, -80, 40, 100);
g2.rotate(-Math.PI/6, 0, 200);
g2.fill(new Rectangle(-20, -80, 40, 100));
}
});
}
public static void main(String [] args) {
RotateRect rr = new RotateRect();
rr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EventQueue.invokeLater(()->rr.setVisible(true));
}
}
Super newbie here. I am trying to draw a Triangle on top of the Rectangles I draw but it does not appear. I know it is there because when I remove the 2 Rectangles and change the color to Yellow or something, it appears. I also need to put stars and sun on top of the Triangle... and yes it is a flag. I am not asking for the exact code. Hints for newbies will be appreciated. Please help!
import java.awt.*;
import java.awt.Polygon;
import java.awt.Graphics;
import javax.swing.*;
public class FlagShapes extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
this.setBackground(Color.WHITE);
//triangle of the flag
int[] xPoints={75, 75, 130};
int[] yPoints={75, 185, 130};
int nPoints=3;
g.setColor(Color.WHITE);
g.fillPolygon(xPoints, yPoints, nPoints);
//blue part of the flag
g.setColor(Color.BLUE);
g.fillRect(75, 75, 200, 55);
//red part of the flag
g.setColor(Color.RED);
g.fillRect(75, 130, 200, 55);
//pole drawing
g.setColor(Color.BLACK);
g.fillRect(72, 75, 2, 300);
}
}
So I found out the answer to my question (thanks Yahoo) Java's default rendering approach uses rasterisation. Just draw the two shapes in the same location Java will draw both even if they overlap. Whichever one is drawn first will be 'under' whichever one is drawn second.
I have the following code. While it displays the lines, rectangle, oval, and string correctly the image does not load. The image is in the correct directory, just cannot figure out why it isn't displaying...
import java.awt.*; // for Graphics, Image, and Color classes
import java.applet.Applet;
public class GraphicsDemo extends Applet
{
public void paint (Graphics g)
{
Image image;
image = this.getImage(getDocumentBase (), "flower.jpg");
// display smaller complete image in upper left corner of window
g.drawImage(image, 0, 0, 427, 284, // destination topL, botR
0, 0, 640, 427, this); // source topL, botR
// establish color of all lines to be drawn
g.setColor(Color.BLUE);
// draw rectangle around region to be expanded
g.drawRect(200, 60, 120, 120); // topL, width & height
// draw lines between corners of rectangles
g.drawLine(200, 60, 240, 240); // upper left
g.drawLine(320, 60, 600, 240); // upper right
g.drawLine(200, 180, 240, 600); // lower left
g.drawLine(320, 180, 600, 600); // lower right
// display expanded part of original image
g.drawImage(image, 240, 240, 600, 600, // destination topL, botR
300, 90, 480, 270, this); // source topL, botR
// draw rectangle around expanded part of image
g.drawRect(240, 240, 360, 360); // topL, width & height
// create BLUE colored oval and write name on it
g.fillOval(520, 380, 45, 30); // topL, width & height
g.setColor(Color.WHITE); // change color for text
g.drawString("Max", 530, 400); // string & start position
} // end main
} // end class GraphicsDemo
More than likely the image does not exist in the document base. The rest of the code is functionally ok. Add the an init method as follows:
public void init() {
System.out.println(getDocumentBase());
}
and copy the image there to the location displayed.
Some related notes:
You're missing this statement from paint
super.paint(g);
Also would suggest moving this statement
image = this.getImage(getDocumentBase (), "flower.jpg");
to the applet's init method so that the image is not being loaded every time the applet is painted.