Java - 循环中心x,y和半径的2D圆中的所有像素?

时间:2016-11-24 06:24:31

标签: java algorithm

我想要循环播放BufferedImage。我想循环遍历半径为radius的圆圈内的所有像素,其中心x和y位于xy

我不想以方方式循环它。 如果我能做到这一点并在此过程中削减O复杂性也会很好,但这不是必需的。由于圆圈的面积为pi * r^2而方形为4 * r^2,这意味着如果我在一个完美的圆圈中循环,我可以获得4 / pi更好的复杂性。如果圆圈在x,半径为y的{​​{1}}恰好大于radius的尺寸,然后阻止越界(这可以通过if来完成)声明我相信会阻止每次检查的出界。)

示例:BufferedImage表示已记录的像素,而O表示未循环播放。

半径1

X

Radius 2

X   O   X
O   O   O
X   O   X

我认为正确的方法是使用三角函数,但我无法理解它。我知道一个简单的部分是添加了来自原点的X X O X X X O O O X O O O O O X O O O X X X O X X 中的所有像素,向左,向右和向下。想要任何人有任何建议。

radius

2 个答案:

答案 0 :(得分:1)

拥有圆圈的中心O(x,y)和半径r,以下坐标(j,i)将覆盖圆圈。

for (int i = y-r; i < y+r; i++) {
    for (int j = x; (j-x)^2 + (i-y)^2 <= r^2; j--) {
        //in the circle
    }
    for (int j = x+1; (j-x)*(j-x) + (i-y)*(i-y) <= r*r; j++) {
        //in the circle
    }
}

方法描述:

  1. 从顶部到底部垂直穿过穿过圆心的线。
  2. 水平移动,直到到达圆圈外的坐标,因此您只能点击每行中圆圈外的两个像素。
  3. 移至最低行。
  4. 因为它只是圆的近似值,所以准备它可能看起来像小r的正方形

    啊,就Big-O而言,减少4倍的操作并不会改变复杂性。

    Big-O =/= complexity

答案 1 :(得分:1)

虽然xentero的答案有效,但我想根据OP认为过于复杂的算法(inCircle1)检查其实际效果(inCircle2):

public static ArrayList<Point> inCircle1(Point c, int r) {
    ArrayList<Point> points = new ArrayList<>(r*r); // pre-allocate
    int r2 = r*r;
    // iterate through all x-coordinates
    for (int i = c.y-r; i <= c.y+r; i++) {
        // test upper half of circle, stopping when top reached
        for (int j = c.x; (j-c.x)*(j-c.x) + (i-c.y)*(i-c.y) <= r2; j--) {
            points.add(new Point(j, i));
        }
        // test bottom half of circle, stopping when bottom reached
        for (int j = c.x+1; (j-c.x)*(j-c.x) + (i-c.y)*(i-c.y) <= r2; j++) {
            points.add(new Point(j, i));
        }
    }
    return points;
}

public static ArrayList<Point> inCircle2(Point c, int r) {
    ArrayList<Point> points = new ArrayList<>(r*r); // pre-allocate
    int r2 = r*r;
    // iterate through all x-coordinates
    for (int i = c.y-r; i <= c.y+r; i++) {
        int di2 = (i-c.y)*(i-c.y);
        // iterate through all y-coordinates
        for (int j = c.x-r; j <= c.x+r; j++) {
            // test if in-circle
            if ((j-c.x)*(j-c.x) + di2 <= r2) {
                points.add(new Point(j, i));
            }
        }
    }
    return points;
}

public static <R extends Collection> R timing(Supplier<R> operation) {
    long start = System.nanoTime();
    R result  = operation.get();
    System.out.printf("%d points found in %dns\n", result.size(),
            TimeUnit.NANOSECONDS.toNanos(System.nanoTime() - start));
    return result;
}

public static void testCircles(int r, int x, int y) {
    Point center = new Point(x, y);
    ArrayList<Point> in1 = timing(() -> inCircle1(center, r));
    ArrayList<Point> in2 = timing(() -> inCircle2(center, r));
    HashSet<Point> all = new HashSet<>(in1);
    assert(all.size() == in1.size()); // no duplicates
    assert(in1.size() == in2.size()); // both are same size
    all.removeAll(in2);
    assert(all.isEmpty());            // both are equal
}

public static void main(String ... args) {
    for (int i=100; i<200; i++) {
        int x = i/2, y = i+1;
        System.out.println("r = " + i + " c = [" + x + ", " + y + "]");
        testCircles(i, x, y);
    }
}

虽然这绝不是一个精确的基准(没有多少热身,机器做其他事情,没有通过n次重复平滑异常值),我的机器上的结果如下:

[snip]
119433 points found in 785873ns
119433 points found in 609290ns
r = 196 c = [98, 197]
120649 points found in 612985ns
120649 points found in 584814ns
r = 197 c = [98, 198]
121905 points found in 619738ns
121905 points found in 572035ns
r = 198 c = [99, 199]
123121 points found in 664703ns
123121 points found in 778216ns
r = 199 c = [99, 200]
124381 points found in 617287ns
124381 points found in 572154ns

也就是说,两者之间没有显着差异,并且&#34;复杂&#34;一个往往更快。我的解释是,整数运算确实非常非常快 - 并且检查落入圆圈的正方形角上的一些额外点是非常快的,与处理所有这些的成本相比落入圈内的点(=昂贵的部分正在调用points.add,并且在两个变体中称为完全相同的次数)。

Knuth的话说:

  程序员花费了太多时间来担心效率问题   错误的地方和错误的时间;过早优化是   编程中所有邪恶(或至少大部分)的根源

然后,如果你真的需要一种迭代圆点的最佳方法,我可以建议使用Bresenham's Circle Drawing Algorithm,它可以用最少的操作提供圆周的所有点。如果你实际上要对圆内的O(n ^ 2)点做任何事情,那么它将再次过早优化。