I'm trying to render a line, but if line starts outside the real canvas bounds, I get strange behavior.
For example, I sometimes get this image instead of a proper line:
Proper line would have looked like this:
Here's the runnable code to generate this sample:
import java.awt.image.*;
import javax.imageio.ImageIO;
import java.io.File;
import java.awt.*;
import java.awt.geom.*;
public class Render {
public static void main(String[] args) throws Exception {
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setColor(Color.WHITE);
g.fillRect(0, 0, 100, 100);
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(2));
g.draw(new Line2D.Double(-92, 37, 88, 39));
g.dispose();
ImageIO.write(image, "png", new File("output.png"));
}
}
I tried using lots of different rendering hints, but no combination gets rid of this problem. What could be the culprit?
Edit:
Here's the image with RenderingHints.VALUE_STROKE_NORMALIZE:
Scaled version of the image (g.scale(10,10)
):
I think it is a bug in the algorithm, related to starting off-canvas. (I filed it as such with Oracle (Review ID: JI-9026491, Oracle Bug database: JDK-8146312))
See this example (Ubuntu 14.04, Java 8u66):
If I reverse the y-coordinates:
Final test:
import java.awt.image.*;
import javax.imageio.ImageIO;
import java.io.File;
import java.awt.*;
import java.awt.geom.*;
public class Testing {
public static void main(String[] args) throws Exception {
BufferedImage image = new BufferedImage(400, 400, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, 400, 400);
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(2));
for (int i=0; i<400; i+=5)
{
double dy = 8.0*(i-100.0) / 200.0;
g.setColor(Color.BLACK);
g.draw(new Line2D.Double(-100, dy+i, 90, i));
g.draw(new Line2D.Double(200+-100, dy+i, 200+90, i));
}
g.dispose();
ImageIO.write(image, "png", new File("output.png"));
}
}
In my opinion, this is a bug in the antialiasing algorithm when starting your line outside the canvas.
After playing around a lot with the Renderinghints, moving the line pixel by pixel and analyzing some values like slope, length, portion in- and outside the canvas, I came to the conclusion, that if you don't want to get really deep into the way the antialiasing algorithm at hand works, you will maybe be satisfied with a proper workaround:
For example:
import java.awt.image.*;
import javax.imageio.ImageIO;
import java.io.File;
import java.awt.*;
import java.awt.geom.*;
public class Render {
public static void main(String[] args) throws Exception {
// YOUR DESIRED SIZE OF CANVAS
int size = 100;
// THE SAFEZONE AROUND THE CANVAS
int safezone = 1000;
// THE IMAGE GETS INITIALIZED WITH SAFEZONE APPLIED
BufferedImage image = new BufferedImage(size+safezone, size+safezone, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setColor(Color.WHITE);
// THE IMAGE GETS FILLED, DON'T FORGET THE SAFEZONE
g.fillRect(0, 0, size+safezone, size+safezone);
// THE ORIGINAL VALUES WITH THE SAFEZONE APPLIED
int x1 = -92 + safezone/2;
int x2 = 88 + safezone/2;
int y1 = 37 + safezone/2;
int y2 = 39 + safezone/2;
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(2));
g.draw(new Line2D.Double(x1, y1, x2, y2));
g.dispose();
// CROP THE IMAGE
image = image.getSubimage(safezone/2, safezone/2, size, size);
ImageIO.write(image, "png", new File("output.png"));
}
}
This will give you:
While using your exact values. The code must of course be fitted to your needs, but for the sake of the example, it works.
While this is not a beautiful solution, the results are and I hope it helps! :)
The behavior you observe is not related to the fact that the line start outside the canvas. If you use a line completely inside the canvas you will observe the same behavior.
The problem is coming from the combination of a small slope dy/dx=1/90
, a small width (2
) and the antialiasing algorithm.
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
The behavior you observe is completly normal and is due to the antialiasing algorithm.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With