(Извините за длинный пост ... по крайней мере, у него есть фотографии?)Java 8 + Swing: Как рисовать Промывать многоугольники
Я написал алгоритм, который создает мозаику из изображения статистически генераций N выпуклые многоугольники, которые покрывают изображение без перекрытия. Эти полигоны имеют расстояние между 3-8 сторонами, а каждая сторона имеет угол, кратный 45 градусам. Эти многоугольники хранятся внутри как прямоугольник с смещениями для каждого угла. Ниже изображение, которое объясняет, как это работает:
getRight()
возвращает x + width - 1
и getBottom()
возвращается y + height - 1
. Класс предназначен для поддержания жесткой ограничивающей рамки вокруг заполненных пикселей, поэтому координаты, показанные на этом изображении, являются правильными. Обратите внимание, что width >= ul + ur + 1
, width >= ll + lr + 1
, height >= ul + ll + 1
и height >= ur + ul + 1
, или на стороне будут пустые пиксели. Обратите также внимание на то, что смещение угла может быть равным 0, что указывает на то, что все пиксели заполнены в этом углу. Это позволяет этому представлению хранить 3-8-сторонние выпуклые многоугольники, каждая из сторон которых имеет по крайней мере один пиксель в длину.
Хотя приятно математически представлять эти регионы, я хочу нарисовать их, чтобы я мог их видеть. Используя простую лямбду и метод, который выполняет итерацию по каждому пикселю в многоугольнике, я могу сделать изображение идеально. В качестве примера ниже приведено значение Claude Monet's Woman with a Parasol с использованием 99 полигонов, разрешающих все направления разделения.
Код, который делает это изображение выглядит следующим образом:
public void drawOnto(Graphics graphics) {
graphics.setColor(getColor());
forEach(
(i, j) -> {
graphics.fillRect(x + i, y + j, 1, 1);
}
);
}
private void forEach(PerPixel algorithm) {
for (int j = 0; j < height; ++j) {
int nj = height - 1 - j;
int minX;
if (j < ul) {
minX = ul - j;
} else if (nj < ll) {
minX = ll - nj;
} else {
minX = 0;
}
int maxX = width;
if (j < ur) {
maxX -= ur - j;
} else if (nj < lr) {
maxX -= lr - nj;
}
for (int i = minX; i < maxX; ++i) {
algorithm.perform(i, j);
}
}
}
Однако, это не является идеальным по многим причинам. Во-первых, концепция графического представления многоугольника теперь является частью самого класса; лучше разрешить другим классам, чья задача состоит в том, чтобы представить эти полигоны. Во-вторых, это влечет за собой много звонков на fillRect()
, чтобы нарисовать один пиксель. Наконец, я хочу иметь возможность разрабатывать другие методы рендеринга этих полигонов, чем рисовать их как-есть (например, performing weighted interpolation over the Voronoi tessellation represented by the polygons' centers).
Все это указывает на генерацию java.awt.Polygon
, которая представляет собой вершины многоугольника (который я назвал Region
, чтобы отличать класс Polygon
). Нет проблем; Я написал метод, чтобы сгенерировать Polygon
, который имеет вышеуказанные углы без каких-либо дубликатов для обработки случаев, что смещение равно 0 или что сторона имеет только один пиксель на нем:
public Polygon getPolygon() {
int[] xes = {
x + ul,
getRight() - ur,
getRight(),
getRight(),
getRight() - lr,
x + ll,
x,
x
};
int[] yes = {
y,
y,
y + ur,
getBottom() - lr,
getBottom(),
getBottom(),
getBottom() - ll,
y + ul
};
int[] keptXes = new int[8];
int[] keptYes = new int[8];
int length = 0;
for (int i = 0; i < 8; ++i) {
if (
length == 0 ||
keptXes[length - 1] != xes[i] ||
keptYes[length - 1] != yes[i]
) {
keptXes[length] = xes[i];
keptYes[length] = yes[i];
length++;
}
}
return new Polygon(keptXes, keptYes, length);
}
Проблема заключается в том, что, когда я попробуйте использовать такой Polygon
с методом Graphics.fillPolygon()
, он не заполняет все пиксели! Ниже та же мозаика визуализации с этим другим способом:
Так у меня есть несколько взаимосвязанных вопросов по поводу этого поведения:
Почему
Polygon
класса не заполняет во всех этих пикселях, хотя углы простые кратные 45 градусов?Как я могу последовательно кодировать этот дефект (насколько мое приложение) в моих средствах визуализации, чтобы я мог использовать мой метод
getPolygon()
как есть? Я не хочу менять вершины, которые он выводит, потому что мне нужно, чтобы они были точными для вычисления центра масс.
MCE
Если вышеуказанные фрагменты кода и изображения не достаточно, чтобы объяснить эту проблему, я добавил Minimal, полный и Проверяемость пример, который демонстрирует поведение я описал выше.
package com.sadakatsu.mce;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Main {
@FunctionalInterface
private static interface PerPixel {
void perform(int x, int y);
}
private static class Region {
private int height;
private int ll;
private int lr;
private int width;
private int ul;
private int ur;
private int x;
private int y;
public Region(
int x,
int y,
int width,
int height,
int ul,
int ur,
int ll,
int lr
) {
if (
width < 0 || width <= ll + lr || width <= ul + ur ||
height < 0 || height <= ul + ll || height <= ur + lr ||
ul < 0 ||
ur < 0 ||
ll < 0 ||
lr < 0
) {
throw new IllegalArgumentException();
}
this.height = height;
this.ll = ll;
this.lr = lr;
this.width = width;
this.ul = ul;
this.ur = ur;
this.x = x;
this.y = y;
}
public Color getColor() {
return Color.BLACK;
}
public int getBottom() {
return y + height - 1;
}
public int getRight() {
return x + width - 1;
}
public Polygon getPolygon() {
int[] xes = {
x + ul,
getRight() - ur,
getRight(),
getRight(),
getRight() - lr,
x + ll,
x,
x
};
int[] yes = {
y,
y,
y + ur,
getBottom() - lr,
getBottom(),
getBottom(),
getBottom() - ll,
y + ul
};
int[] keptXes = new int[8];
int[] keptYes = new int[8];
int length = 0;
for (int i = 0; i < 8; ++i) {
if (
length == 0 ||
keptXes[length - 1] != xes[i] ||
keptYes[length - 1] != yes[i]
) {
keptXes[length] = xes[i];
keptYes[length] = yes[i];
length++;
}
}
return new Polygon(keptXes, keptYes, length);
}
public void drawOnto(Graphics graphics) {
graphics.setColor(getColor());
forEach(
(i, j) -> {
graphics.fillRect(x + i, y + j, 1, 1);
}
);
}
private void forEach(PerPixel algorithm) {
for (int j = 0; j < height; ++j) {
int nj = height - 1 - j;
int minX;
if (j < ul) {
minX = ul - j;
} else if (nj < ll) {
minX = ll - nj;
} else {
minX = 0;
}
int maxX = width;
if (j < ur) {
maxX -= ur - j;
} else if (nj < lr) {
maxX -= lr - nj;
}
for (int i = minX; i < maxX; ++i) {
algorithm.perform(i, j);
}
}
}
}
public static void main(String[] args) throws IOException {
int width = 10;
int height = 8;
Region region = new Region(0, 0, 10, 8, 2, 3, 4, 1);
BufferedImage image = new BufferedImage(
width,
height,
BufferedImage.TYPE_3BYTE_BGR
);
Graphics graphics = image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
region.drawOnto(graphics);
ImageIO.write(image, "PNG", new File("expected.png"));
image = new BufferedImage(
width,
height,
BufferedImage.TYPE_3BYTE_BGR
);
graphics = image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
graphics.setColor(Color.BLACK);
graphics.fillPolygon(region.getPolygon());
ImageIO.write(image, "PNG", new File("got.png"));
}
}
Это самая крутая вещь, которую я видел весь день :-) +1 –
Это смотрит на меня, как будто определение интерьер многоугольника в * вашем * коде отличается от кода java.awt.Polygon; оба правильны в * своей * системе отсчета, они просто разные определения. Вам нужно будет преобразовать свое определение в Polygon; никоим образом не обойти это, если вы хотите использовать Polygon ... – Durandal
@Durandal: Это правда. Моя проблема в том, что я еще не нашел объяснений, как работает этот подход. Если я смогу научиться рифме и причине объяснить, почему иногда она генерирует одни и те же результаты, а другие - нет, я смогу добиться прогресса. До сих пор я не нашел этого объяснения. – sadakatsu