Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this line rendered without proper antialiasing?

Tags:

java

swing

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:

enter image description here

Proper line would have looked like this:

enter image description here

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:

enter image description here

Scaled version of the image (g.scale(10,10)):

enter image description here

like image 508
Rogach Avatar asked Nov 02 '15 22:11

Rogach


3 Answers

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):

enter image description here

If I reverse the y-coordinates:

enter image description here

Final test:

enter image description here

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"));
    }
}
like image 194
Rob Audenaerde Avatar answered Oct 23 '22 04:10

Rob Audenaerde


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:

  1. Start with a huge safezone around the final canvas.
  2. Draw the image as usual, just moved by the range of the safezone.
  3. Crop away the safezone.

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:

cropped

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

like image 33
Espresso Mortale Avatar answered Oct 23 '22 04:10

Espresso Mortale


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.

Without antialiasing

enter image description here

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                       RenderingHints.VALUE_ANTIALIAS_OFF);

With antialiasing

enter image description here

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                       RenderingHints.VALUE_ANTIALIAS_ON);

The behavior you observe is completly normal and is due to the antialiasing algorithm.

like image 1
Ortomala Lokni Avatar answered Oct 23 '22 06:10

Ortomala Lokni