Почему эта линия отображается без надлежащего сглаживания?
Я пытаюсь отобразить строку, но если строка начинается за пределами реальных границ холста, я получаю странное поведение.
Например, я иногда получаю это изображение вместо правильной строки:
![введите описание изображения здесь]()
Правильная строка выглядела бы так:
![введите описание изображения здесь]()
Вот код запуска для создания этого примера:
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)
):
![введите описание изображения здесь]()
Ответы
Ответ 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));
или даже полностью опустить. Это сделает вашу линию более гладкой.