用奇偶规则填充凹多边形不能正常工作?
Filling concave polygon with even-odd rule doesn't work properly?
我目前正在尝试用颜色填充凹多边形。我在网上找到了一些算法,比如我现在使用的奇偶算法。我首先发现如何检查两条线段是否与以下相交:https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
然后我在 Java 中这样实现它:
public boolean contains(PointD point) {
if (Arrays.stream(vertices).anyMatch(vertex -> vertex == point))
return true;
// Create point at the border of the image
// Line segment AB represents the ray used in the even-odd algorithm
PointD a = point;
PointD b = new PointD(SIZE - 1, y);
// Count how many times AB crosses the individual line segments of
// the concave polygon
int crossing = 0;
for (int i = 0; i < vertices.length; i++) {
PointD c = vertices[i];
PointD d = vertices[(i + 1) % vertices.length];
if (ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d))
crossing += 1;
}
// If the amount of crossings is odd, the point is inside the polygon
return crossing % 2 == 1;
}
private boolean ccw(PointD a, PointD b, PointD c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.y);
}
然而,这并没有产生任何令人满意的结果,如您所见:
Polygon-fill
有谁知道我做错了什么或者有没有其他方法?
示例代码 - 尚未准备好生产,仅用于测试(和一些乐趣)!!!
使用 List
个点而不是数组,但转换为数组以避免更改问题代码。
如前所述,锁定是以一种非常简单、快速(容易出错)的方式完成的,仅用于测试!
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class Polyg extends JPanel {
private static final Color LINE_COLOR = new Color(0, 0, 255, 112);
private static final Color AREA_COLOR = new Color(0, 255, 0, 112);
public static void main(String[] args) throws Exception {
var panel = switch (20) { // test cases
case 10 -> new Polyg(new Dimension(400, 300), 100, 100, 200, 200, 300, 100, 200, 150);
case 20 -> new Polyg(ImageIO.read(new URL("file:mAp26bw.png")));
case 22 -> new Polyg(ImageIO.read(new URL("file:mAp26bw2.png")));
default -> throw new IllegalArgumentException("not implemented");
};
var f = new JFrame();
f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private final Image background;
private final Dimension size;
private final List<Point> points;
private final BufferedImage inside;
private volatile boolean lock = false;
private Polyg(Image background) {
this.background = background;
this.size = new Dimension(background.getWidth(null), background.getHeight(null));
this.points = new ArrayList<>();
this.inside = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
setToolTipText("""
<html>
<b>left-button:</b> add point<br/>
<b>right-button:</b> delete point<br/>
<b>middle-button:</b> print points to console
""");
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() > 1)
return;
switch(e.getButton()) {
case 1:
if (!lock) {
points.add(e.getPoint());
calc();
}
break;
case 2:
points.forEach(System.out::println);
System.out.println();
break;
case 3:
if (!lock && !points.isEmpty()) {
points.remove(points.size()-1);
calc();
}
break;
default:
break;
}
}
});
}
private Polyg(Dimension size, int... coords) {
this.background = null;
this.size = size;
var list = new ArrayList<Point>();
for (var i = 0; i+1 < coords.length; i += 2) {
list.add(new Point(coords[i], coords[i+1]));
}
this.points = Collections.unmodifiableList(list);
this.inside = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
calc();
}
private void calc() {
lock = true;
for (var x = 0; x < inside.getWidth(); x++) {
for (var y = 0; y < inside.getHeight(); y++) {
inside.setRGB(x, y, 0);
}
}
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
for (var x = 0; x < size.width; x++) {
for (var y = 0; y < size.height; y++) {
if (containsP(x, y)) {
inside.setRGB(x, y, AREA_COLOR.getRGB());
}
}
publish((Void) null);
}
return null;
}
@Override
protected void process(java.util.List<Void> chunks) {
repaint();
}
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
repaint();
lock = false;
}
}.execute();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gg = (Graphics2D) g.create();
try {
if (background != null) {
gg.drawImage(background, 0, 0, this);
}
gg.drawImage(inside, 0, 0, this);
gg.setColor(LINE_COLOR);
Point first = null;
Point p1 = null;
for (var p2 : points) {
if (p1 == null) {
first = p2;
} else {
gg.drawLine(p1.x, p1.y, p2.x, p2.y);
}
p1 = p2;
}
if (first != null && p1 != null) {
gg.drawLine(first.x, first.y, p1.x, p1.y);
}
} finally {
gg.dispose();
}
}
@Override
public Dimension getPreferredSize() {
return size;
}
private boolean containsP(int x, int y) {
var vertices = points.toArray(Point[]::new); // avoid changing posted code
Point a = new Point(x, y);
if (Arrays.stream(vertices).anyMatch(vertex -> vertex == a))
return true;
// Create point at the border of the image
// Line segment AB represents the ray used in the even-odd algorithm
Point b = new Point(size.width - 1, y);
// Count how many times AB crosses the individual line segments of
// the concave polygon
int crossing = 0;
for (int i = 0; i < vertices.length; i++) {
Point c = vertices[i];
Point d = vertices[(i + 1) % vertices.length];
if (ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d))
crossing += 1;
}
// If the amount of crossings is odd, the point is inside the polygon
return crossing % 2 == 1;
}
private boolean ccw(Point a, Point b, Point c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
}
}
用作背景的图片:mAp26bw.png(mAp26bw2.png
只是大 4 倍)
我目前正在尝试用颜色填充凹多边形。我在网上找到了一些算法,比如我现在使用的奇偶算法。我首先发现如何检查两条线段是否与以下相交:https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
然后我在 Java 中这样实现它:
public boolean contains(PointD point) {
if (Arrays.stream(vertices).anyMatch(vertex -> vertex == point))
return true;
// Create point at the border of the image
// Line segment AB represents the ray used in the even-odd algorithm
PointD a = point;
PointD b = new PointD(SIZE - 1, y);
// Count how many times AB crosses the individual line segments of
// the concave polygon
int crossing = 0;
for (int i = 0; i < vertices.length; i++) {
PointD c = vertices[i];
PointD d = vertices[(i + 1) % vertices.length];
if (ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d))
crossing += 1;
}
// If the amount of crossings is odd, the point is inside the polygon
return crossing % 2 == 1;
}
private boolean ccw(PointD a, PointD b, PointD c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.y);
}
然而,这并没有产生任何令人满意的结果,如您所见:
Polygon-fill
有谁知道我做错了什么或者有没有其他方法?
示例代码 - 尚未准备好生产,仅用于测试(和一些乐趣)!!!
使用 List
个点而不是数组,但转换为数组以避免更改问题代码。
如前所述,锁定是以一种非常简单、快速(容易出错)的方式完成的,仅用于测试!
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class Polyg extends JPanel {
private static final Color LINE_COLOR = new Color(0, 0, 255, 112);
private static final Color AREA_COLOR = new Color(0, 255, 0, 112);
public static void main(String[] args) throws Exception {
var panel = switch (20) { // test cases
case 10 -> new Polyg(new Dimension(400, 300), 100, 100, 200, 200, 300, 100, 200, 150);
case 20 -> new Polyg(ImageIO.read(new URL("file:mAp26bw.png")));
case 22 -> new Polyg(ImageIO.read(new URL("file:mAp26bw2.png")));
default -> throw new IllegalArgumentException("not implemented");
};
var f = new JFrame();
f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private final Image background;
private final Dimension size;
private final List<Point> points;
private final BufferedImage inside;
private volatile boolean lock = false;
private Polyg(Image background) {
this.background = background;
this.size = new Dimension(background.getWidth(null), background.getHeight(null));
this.points = new ArrayList<>();
this.inside = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
setToolTipText("""
<html>
<b>left-button:</b> add point<br/>
<b>right-button:</b> delete point<br/>
<b>middle-button:</b> print points to console
""");
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() > 1)
return;
switch(e.getButton()) {
case 1:
if (!lock) {
points.add(e.getPoint());
calc();
}
break;
case 2:
points.forEach(System.out::println);
System.out.println();
break;
case 3:
if (!lock && !points.isEmpty()) {
points.remove(points.size()-1);
calc();
}
break;
default:
break;
}
}
});
}
private Polyg(Dimension size, int... coords) {
this.background = null;
this.size = size;
var list = new ArrayList<Point>();
for (var i = 0; i+1 < coords.length; i += 2) {
list.add(new Point(coords[i], coords[i+1]));
}
this.points = Collections.unmodifiableList(list);
this.inside = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
calc();
}
private void calc() {
lock = true;
for (var x = 0; x < inside.getWidth(); x++) {
for (var y = 0; y < inside.getHeight(); y++) {
inside.setRGB(x, y, 0);
}
}
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
for (var x = 0; x < size.width; x++) {
for (var y = 0; y < size.height; y++) {
if (containsP(x, y)) {
inside.setRGB(x, y, AREA_COLOR.getRGB());
}
}
publish((Void) null);
}
return null;
}
@Override
protected void process(java.util.List<Void> chunks) {
repaint();
}
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
repaint();
lock = false;
}
}.execute();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gg = (Graphics2D) g.create();
try {
if (background != null) {
gg.drawImage(background, 0, 0, this);
}
gg.drawImage(inside, 0, 0, this);
gg.setColor(LINE_COLOR);
Point first = null;
Point p1 = null;
for (var p2 : points) {
if (p1 == null) {
first = p2;
} else {
gg.drawLine(p1.x, p1.y, p2.x, p2.y);
}
p1 = p2;
}
if (first != null && p1 != null) {
gg.drawLine(first.x, first.y, p1.x, p1.y);
}
} finally {
gg.dispose();
}
}
@Override
public Dimension getPreferredSize() {
return size;
}
private boolean containsP(int x, int y) {
var vertices = points.toArray(Point[]::new); // avoid changing posted code
Point a = new Point(x, y);
if (Arrays.stream(vertices).anyMatch(vertex -> vertex == a))
return true;
// Create point at the border of the image
// Line segment AB represents the ray used in the even-odd algorithm
Point b = new Point(size.width - 1, y);
// Count how many times AB crosses the individual line segments of
// the concave polygon
int crossing = 0;
for (int i = 0; i < vertices.length; i++) {
Point c = vertices[i];
Point d = vertices[(i + 1) % vertices.length];
if (ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d))
crossing += 1;
}
// If the amount of crossings is odd, the point is inside the polygon
return crossing % 2 == 1;
}
private boolean ccw(Point a, Point b, Point c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
}
}
用作背景的图片:mAp26bw.png(mAp26bw2.png
只是大 4 倍)