如何计算java.awt.geom.Area的面积?

13

我正在寻找一种计算任意java.awt.geom.Area实例面积的方法,以像素为单位。

背景:我的应用程序中有可能重叠的Shape。我想知道一个Shape与另一个Shape重叠的程度。这些Shape可能会被扭曲、旋转等。如果我有一个area(Shape)(或Area)函数,我可以像下面这样使用两个Shape的交集:

double fractionObscured(Shape bottom, Shape top) {
    Area intersection = new Area(bottom);
    intersection.intersect(new Area(top));
    return area(intersection) / area(bottom);
}

为什么使用double[] coords = new double[6];而不使用其他索引? - user1270938
你应该能够扩展多边形相交公式以适用于贝塞尔曲线。然后,你可以使用路径迭代器来获取所有形状/区域的近乎完美的面积。 - Thomas Ahle
5个回答

12

使用以下代码片段来查找多边形的面积:

int sum = 0;
for (int i = 0; i < n -1; i++)
{
    sum = sum + x[i]*y[i+1] - y[i]*x[i+1];
}
// (sum / 2) is your area.
System.out.println("The area is : " + (sum / 2));

这里的n是顶点的总数,x[i]和y[i]是顶点i的x和y坐标。 请注意,为了使该算法起作用,多边形必须是闭合的,它不能用于开放式的多边形。

您可以在这里找到与多边形相关的数学算法。您需要自行将其转换为代码 :)


谢谢提供链接。这是一种有效的方法,但不是我想走的方向。Shape 可以包括曲线段,并且可以是其他形状的组合。这里的数学太复杂了,我无法跟随。 - iter
3
@iter,你可以使用getPathIterator(AffineTransform at, double flatness)方法将曲线近似为多边形。此外,Area构造函数会将形状分解为非自交的组件,因此如果你将该算法改为使用PathIterator,它也会有效。 - finnw

6

我曾在项目中使用这个类来近似计算一个形状的面积。虽然速度较慢,但在高分辨率下仍可能比计算像素更快(因为计算像素的成本随分辨率的增加呈二次方增长,而周长上的线段数量呈线性增长)。

import static java.lang.Double.NaN;

import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;

public abstract class Areas {
    public static double approxArea(Area area, double flatness, int limit) {
        PathIterator i =
            new FlatteningPathIterator(area.getPathIterator(identity),
                                       flatness,
                                       limit);
        return approxArea(i);
    }

    public static double approxArea(Area area, double flatness) {
        PathIterator i = area.getPathIterator(identity, flatness);
        return approxArea(i);
    }

    public static double approxArea(PathIterator i) {
        double a = 0.0;
        double[] coords = new double[6];
        double startX = NaN, startY = NaN;
        Line2D segment = new Line2D.Double(NaN, NaN, NaN, NaN);
        while (! i.isDone()) {
            int segType = i.currentSegment(coords);
            double x = coords[0], y = coords[1];
            switch (segType) {
            case PathIterator.SEG_CLOSE:
                segment.setLine(segment.getX2(), segment.getY2(), startX, startY);
                a += hexArea(segment);
                startX = startY = NaN;
                segment.setLine(NaN, NaN, NaN, NaN);
                break;
            case PathIterator.SEG_LINETO:
                segment.setLine(segment.getX2(), segment.getY2(), x, y);
                a += hexArea(segment);
                break;
            case PathIterator.SEG_MOVETO:
                startX = x;
                startY = y;
                segment.setLine(NaN, NaN, x, y);
                break;
            default:
                throw new IllegalArgumentException("PathIterator contains curved segments");
            }
            i.next();
        }
        if (Double.isNaN(a)) {
            throw new IllegalArgumentException("PathIterator contains an open path");
        } else {
            return 0.5 * Math.abs(a);
        }
    }

    private static double hexArea(Line2D seg) {
        return seg.getX1() * seg.getY2() - seg.getX2() * seg.getY1();
    }

    private static final AffineTransform identity =
        AffineTransform.getQuadrantRotateInstance(0);
}

3

一种方法是使用适当的AlphaComposite,将每个经过缩放和变换Shape用不同的颜色填充(fill()),并计算底层Raster中重叠像素的数量。

附录1:使用此计算器查看AlphaComposite.Xor效果,可以看到任何两种不透明颜色的交集都为零。

附录2:计算像素可能会有性能问题,采样可以帮助解决。如果每个Shape相对凸,则可能可以通过intersect()面积与ShapegetBounds2D()面积之和的比率来估计重叠部分。例如,
Shape s1, s2 ...
Rectangle2D r1 = s1.getBounds2D();
Rectangle2D r2 = s2.getBounds2D();
Rectangle2D r3 = new Rectangle2D.Double();
Rectangle2D.intersect(r1, r2, r3);
double overlap = area(r3) / (area(r1) + area(r2));
...
private double area(Rectangle2D r) {
    return r.getWidth() * r.getHeight();
}

您可能需要通过经验来验证结果。


感谢您指出栅格化图像部分和查看实际样本值的选项。 - iter
我认为这更准确,但我也建议一个可能更快的替代方案,可能已经足够了。 - trashgod

3

如果我能的话,我会进行评论。Suraj,你的算法是正确的,但代码应该这样写:

        int sum = 0;
        for (int i = 0; i < npoints ; i++)
        {
            sum = sum + Xs[i]*Ys[(i+1)%npoints] - Ys[i]*Xs[(i+1)%npoints];
        }

        return Math.abs(sum / 2);

在你的代码中,最后一个顶点没有被考虑在内。只需进行一次小修改即可 :)

0

给出的答案不准确,我发现遵循solution会得到更好的结果。

private int calcAreaSize(Area area){
    int sum = 0;
    float xBegin=0, yBegin=0, xPrev=0, yPrev=0, coords[] = new float[6];
    for (PathIterator iterator1 = area.getPathIterator(null, 0.1); !iterator1.isDone(); iterator1.next()){
        switch (iterator1.currentSegment(coords))
        {
            case PathIterator.SEG_MOVETO:
                xBegin = coords[0]; yBegin = coords[1];
                break;
            case PathIterator.SEG_LINETO:
                // the well-known trapez-formula
                sum += (coords[0] - xPrev) * (coords[1] + yPrev) / 2.0;
                break;
            case PathIterator.SEG_CLOSE:
                sum += (xBegin - xPrev) * (yBegin + yPrev) / 2.0;
                break;
            default:
                // curved segments cannot occur, because we have a flattened ath
                throw new InternalError();
        }
        xPrev = coords[0]; yPrev = coords[1];
    }
    return sum;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接