Java 8 + Swing:如何绘制Flush多边形

时间:2014-10-24 15:59:15

标签: java swing polygon

(对不起,很长的帖子......至少它有照片吗?)

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

enter image description here

getRight()返回x + width - 1getBottom()返回y + height - 1。该类设计用于在填充像素周围保持紧密的边界框,因此此图像中显示的坐标是正确的。请注意width >= ul + ur + 1width >= ll + lr + 1height >= ul + ll + 1height >= 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);
}

问题在于,当我尝试使用Polygon Graphics.fillPolygon()方法时,它不会填满所有像素!下面是使用这种不同方法渲染的相同马赛克:

enter image description here

所以我对这种行为有一些相关的问题:

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

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


  3. MCE

    如果上面的代码片段和图片不足以帮助解释问题,我添加了一个Minimal,Complete和Verifiable示例来演示我上面描述的行为。

    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 个答案:

答案 0 :(得分:0)

我花了一整天的时间来研究它,我似乎已经解决了这个问题。线索在Shape类的文档中找到,其中包含:

  

内部性的定义:当且仅当以下情况时,才认为该点位于Shape内:

     
      
  • 它完全位于形状边界内或

  •   
  • 它恰好位于Shape边界上,与X方向增加的点紧邻的空间完全在边界内或

  •   
  • 它恰好位于水平边界线上,与Y方向上的点紧邻的空间位于边界内。

  •   

实际上,这篇文章有点误导;第三种情况覆盖第二种情况(即,即使Shape底部的水平边界线段中的像素在其右侧有一个填充点,它仍然不会被填充)。以图示方式表示,下面的Polygon不会绘制 x &#39; ed out像素:

enter image description here

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

最简单的方法是使用camickr的方法:同时使用fillPolygon()drawPolygon()。至少在我的45度多边形凸包的情况下,drawPolygon()将线条精确地绘制到顶点(并且可能也适用于其他情况),因此将填充{{1}的像素错过了。但是,fillPolygon()fillPolygon()都不会绘制单个像素drawPolygon(),因此必须编写一个特殊情况来处理它。

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

Polygon