gpt4 book ai didi

Java 8 + Swing : How to Draw Flush Polygons

转载 作者:行者123 更新时间:2023-12-04 20:16:19 25 4
gpt4 key购买 nike

(抱歉发了这么长的帖子……至少它有图片?)

我编写了一种算法,通过统计生成 N 个覆盖图像且不重叠的凸多边形,从图像创建马赛克。这些多边形有 3-8 条边,每条边的角度都是 45 度的倍数。这些多边形在内部存储为一个矩形,每个角都有位移。下图解释了它是如何工作的:

enter image description here

getRight() 返回 x + width - 1getBottom() 返回 y + height - 1 >。该类旨在在填充像素周围保持紧密的边界框,以便此图像中显示的坐标是正确的。请注意,width >= ul + ur + 1width >= ll + lr + 1height >= ul + ll + 1,和 height >= ur + ul + 1,否则边上会有空像素。另请注意,角的位移可能为 0,因此表示所有像素都填充在该角中。这使该表示能够存储 3-8 条边的凸多边形,每条边的长度至少为一个像素。

虽然用数学方法表示这些区域很好,但我想画出它们以便我能看到它们。使用简单的 lambda 和迭代多边形中每个像素的方法,我可以完美地渲染图像。例如,下面是 Claude Monet's Woman with a Parasol使用 99 个多边形允许所有分割方向。

enter image description here

呈现此图像的代码如下所示:

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

问题是,当我尝试将这样的 PolygonGraphics.fillPolygon() 方法一起使用时,它并没有填充所有像素!下面是使用这种不同方法渲染的相同马赛克:

enter image description here

所以我有几个关于这种行为的相关问题:

  1. 为什么 Polygon 类不填充所有这些像素,即使角度是 45 度的简单倍数?

  2. 如何在我的渲染器中一致地围绕这个缺陷(就我的应用程序而言)进行编码,以便我可以按原样使用我的 getPolygon() 方法?我不想更改它输出的顶点,因为我需要它们精确计算质心。


MCE

如果上面的代码片段和图片不足以帮助解释问题,我添加了一个最小的、完整的和可验证的示例来演示我上面描述的行为。

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

最佳答案

我花了一整天的时间来解决这个问题,我似乎找到了解决办法。在 Shape 的文档中找到了线索类,内容如下:

Definition of insideness: A point is considered to lie inside a Shape if and only if:

  • it lies completely inside theShape boundary or

  • it lies exactly on the Shape boundary and the space immediately adjacent to the point in the increasing X direction is entirely inside the boundary or

  • it lies exactly on a horizontal boundary segment and the space immediately adjacent to the point in the increasing Y direction is inside the boundary.

实际上,这段文字有点误导;第三种情况覆盖第二种情况(即,即使 Shape 底部的水平边界段中的像素右侧有一个填充点,它仍然不会被填充)。如图所示,下面的 Polygon 不会绘制出 x 的像素:

enter image description here

红色、绿色和蓝色像素是多边形的一部分;其余的不是。蓝色像素属于第一种情况,绿色像素属于第二种情况,红色像素属于第三种情况。请注意,未绘制沿凸包的所有最右边和最低像素。要绘制它们,您必须将顶点移动到如图所示的橙色像素,以创建凸包的新的最右侧/最底部部分。

最简单的方法是使用 camickr 的方法:同时使用 fillPolygon()drawPolygon()。至少在我的 45 度多边凸包的情况下,drawPolygon() 准确地将线绘制到顶点(并且可能对于其他情况也是如此),因此将填充像素fillPolygon() 未命中。但是,fillPolygon()drawPolygon() 都不会绘制单像素的 Polygon,因此必须编写一种特殊情况来处理这种情况.

我在尝试理解上面的insideness 定义时开发的实际解决方案是创建一个不同的Polygon,如图所示修改角。它的好处是(?)只调用绘图库一次并自动处理特殊情况。它实际上可能不是最佳的,但这是我用于任何人考虑的代码:

package com.sadakatsu.mosaic.renderer;

import java.awt.Polygon;
import java.util.Arrays;

import com.sadakatsu.mosaic.Region;

public class RegionPolygon extends Polygon {
public RegionPolygon(Region region) {
int bottom = region.getBottom();
int ll = region.getLL();
int lr = region.getLR();
int right = region.getRight();
int ul = region.getUL();
int ur = region.getUR();
int x = region.getX();
int y = region.getY();

int[] xes = {
x + ul,
right - ur + 1,
right + 1,
right + 1,
right - lr,
x + ll + 1,
x,
x
};

int[] yes = {
y,
y,
y + ur,
bottom - lr,
bottom + 1,
bottom + 1,
bottom - ll,
y + ul
};

npoints = 0;
xpoints = new int[xes.length];
ypoints = new int[xes.length];
for (int i = 0; i < xes.length; ++i) {
if (
i == 0 ||
xpoints[npoints - 1] != xes[i] ||
ypoints[npoints - 1] != yes[i]
) {
addPoint(xes[i], yes[i]);
}
}
}
}

关于Java 8 + Swing : How to Draw Flush Polygons,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26551714/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com