【发布时间】:2025-12-14 19:00:01
【问题描述】:
(抱歉,帖子太长了……至少它有图片?)
我编写了一个算法,通过统计生成覆盖图像且不重叠的 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 个边的凸多边形,每个边的长度至少为一个像素。
虽然用数学方法表示这些区域很好,但我想绘制它们以便我可以看到它们。使用简单的 lambda 和迭代多边形中每个像素的方法,我可以完美地渲染图像。例如,下面是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
如果上面的代码 sn-ps 和图片不足以帮助解释问题,我添加了一个 Minimal, Complete, and Verifiable Example 来演示我上面描述的行为。
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
-
在我看来,your 代码中多边形内部的定义与 java.awt.Polygon 的定义不同;两者在它们的参考框架内都是正确的,它们只是不同的定义。您将需要从您的定义转换为 Polygon 的;如果您想使用 Polygon,则无法解决...
-
@Durandal:确实如此。我的问题是我还没有找到关于这种方法如何工作的解释。如果我能学会押韵和理由来解释为什么有时会产生相同的结果而有时却不会,我将能够取得进步。到目前为止,我还没有找到这个解释。
-
@peeskillet: :) 当它完全可用时,我会将这个应用程序 Mosaic 放到我的 GitHub (github.com/sadakatsu) 上。一旦它启动,你就可以玩它。我希望这将在 1-2 周内完成。
-
覆盖 JPanel 的 getPreferredSIze,然后所有坐标都来自 getHeight/weight incl。在调整大小时重新绘制(忘记了完美的像素,例如将 10 除以 3:-),所有块都只是形状,将所有对象放入数组,在paintCOMponent 内部循环对象数组,这里有一些代码示例关于跨度>