Подтвердить что ты не робот

Почему эта линия отображается без надлежащего сглаживания?

Я пытаюсь отобразить строку, но если строка начинается за пределами реальных границ холста, я получаю странное поведение.

Например, я иногда получаю это изображение вместо правильной строки:

введите описание изображения здесь

Правильная строка выглядела бы так:

введите описание изображения здесь

Вот код запуска для создания этого примера:

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"));
    }
}

Я пробовал использовать множество различных подсказок рендеринга, но никакая комбинация не избавилась от этой проблемы. Что может быть виновником?

Edit:

Здесь изображение с RenderingHints.VALUE_STROKE_NORMALIZE:

введите описание изображения здесь

Масштабированная версия изображения (g.scale(10,10)):

введите описание изображения здесь

4b9b3361

Ответ 1

Я думаю, что это ошибка в алгоритме, связанная с запуском canvas. (Я подал его как таковой с Oracle (обзор ID: JI-9026491, база данных Oracle Bug: JDK-8146312))

См. этот пример (Ubuntu 14.04, Java 8u66):

введите описание изображения здесь

Если я отменяю y-координаты:

введите описание изображения здесь

Окончательный тест:

введите описание изображения здесь

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"));
    }
}

Ответ 2

Поведение, которое вы наблюдаете, не связано с тем, что линия начинается за пределами холста. Если вы используете линию полностью внутри холста, вы будете наблюдать такое же поведение.

Проблема возникает из комбинации небольшого наклона dy/dx=1/90, малой ширины (2) и алгоритма сглаживания.

Без сглаживания

введите описание изображения здесь

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

С сглаживанием

введите описание изображения здесь

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

Поведение, которое вы наблюдаете, является полностью нормальным и связано с алгоритмом сглаживания.

Ответ 3

Использование вашего примера с Path2D вместо Line2D улучшает вывод.

    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.setColor(Color.WHITE);
    g.fillRect(0, 0, 100, 100);

    g.setColor(Color.BLACK);
    g.setStroke(new BasicStroke(2));
    Path2D path = new Path2D.Double();
    path.moveTo(-92, 37);
    path.lineTo(88, 39);
    g.draw(path);
    g.dispose();
    ImageIO.write(image, "png", new File("output.png"));

Ответ 4

По-моему, это ошибка в алгоритме сглаживания при запуске вашей линии за пределами холста.

После многократного воспроизведения с помощью Renderinghints, перемещая пиксель линии по пикселям и анализируя некоторые значения, такие как наклон, длину, часть внутри и вне холста, я пришел к выводу, что если вы не хотите получать действительно глубоко в том, как работает алгоритм сглаживания, вы, возможно, будете удовлетворены надлежащим обходным путем:

  • Начните с огромной безопасной зоны вокруг финального холста.
  • Нарисуйте изображение как обычно, просто переместившись в область безопасности.
  • Удалите безопасную зону.

Например:

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"));
    }
}

Это даст вам:

обрезанный

При использовании ваших точных значений. Конечно, код должен соответствовать вашим потребностям, но для примера он работает.

Хотя это не красивое решение, результаты есть, и я надеюсь, что это поможет!:)

Ответ 5

Я думаю, что есть определенная проблема с рендерингом в вашем конкретном случае, когда ваша строка образца начиналась в x = -92 (расположение начальной точки вашей линии и наклон является исключением в этом масштабе). Но, как вы заметили, нет никаких проблем со значениями за пределами холста типа x = -12 или x = -22 или x = -82 или даже x = -102. Единственный совет, который вам нужен, это:

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

и я думаю, что если g.setStroke(new BasicStroke(2)); не имеет особого значения, вы можете изменить его на g.setStroke(new BasicStroke(1)); или даже полностью опустить. Это сделает вашу линию более гладкой.