我已经编写了Bresenham的圆绘制算法的实现。 此算法利用了圆的高度对称属性(它只计算第一个八分圆的点,并利用对称性绘制其他点)。因此我期待它非常快。图形编程黑皮书,第35章标题为“ Bresenham快速,快速好”,虽然它是关于线条绘制算法,但我可以合理地期望圆形绘制算法也是快(因为原理是一样的)。
这是我的java,swing实现
public static void drawBresenhamsCircle(int r, double width, double height, Graphics g) {
int x,y,d;
y = r;
x = 0;
drawPoint(x, y, width, height,g);
d = (3-2*(int)r);
while (x <= y) {
if (d <= 0) {
d = d + (4*x + 6);
} else {
d = d + 4*(x-y) + 10;
y--;
}
x++;
drawPoint(x, y, width, height,g);
drawPoint(-x, y, width, height,g);
drawPoint(x, -y, width, height,g);
drawPoint(-x, -y, width, height,g);
drawPoint(y, x, width, height,g);
drawPoint(-y, x, width, height,g);
drawPoint(y, -x, width, height,g);
drawPoint(-y, -x, width, height,g);
}
}
此方法使用以下drawPoint
方法:
public static void drawPoint(double x, double y,double width,double height, Graphics g) {
double nativeX = getNativeX(x, width);
double nativeY = getNativeY(y, height);
g.fillRect((int)nativeX, (int)nativeY, 1, 1);
}
两种方法getNativeX和getNativeY用于将坐标从原点位于屏幕的左上角切换到一个系统,该系统的原点位于面板的中心,具有更经典的轴方向。
public static double getNativeX(double newX, double width) {
return newX + (width/2);
}
public static double getNativeY(double newY, double height) {
return (height/2) - newY;
}
我还创建了基于三角公式(x=R*Math.cos(angle)
和y= R*Math.sin(angle)
)的圆绘制算法的实现,以及使用对标准drawArc方法的调用的第三个实现(在Graphics对象上可用) 。这些额外的实现仅用于将Bresenham的算法与它们进行比较。
然后我创建了绘制一堆圆圈的方法,以便能够很好地衡量花费的时间。这是我使用Bresenham算法绘制一堆圆圈的方法
public static void drawABunchOfBresenhamsCircles(int numOfCircles, double width, double height, Graphics g) {
double r = 5;
double step = (300.0-5.0)/numOfCircles;
for (int i = 1; i <= numOfCircles; i++) {
drawBresenhamsCircle((int)r, width, height, g);
r += step;
}
}
最后,我覆盖了我正在使用的JPanel的paint方法,绘制一堆圆圈并测量每种类型绘制的时间。这是绘画方法:
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D)g;
g2D.setColor(Color.RED);
long trigoStartTime = System.currentTimeMillis();
drawABunchOfTrigonometricalCircles(1000, this.getWidth(), this.getHeight(), g);
long trigoEndTime = System.currentTimeMillis();
long trigoDelta = trigoEndTime - trigoStartTime;
g2D.setColor(Color.BLUE);
long bresenHamsStartTime = System.currentTimeMillis();
drawABunchOfBresenhamsCircles(1000, this.getWidth(), this.getHeight(), g);
long bresenHamsEndTime = System.currentTimeMillis();
long bresenDelta = bresenHamsEndTime - bresenHamsStartTime;
g2D.setColor(Color.GREEN);
long standardStarTime = System.currentTimeMillis();
drawABunchOfStandardCircles(1000, this.getWidth(), this.getHeight(),g);
long standardEndTime = System.currentTimeMillis();
long standardDelta = standardEndTime - standardStarTime;
System.out.println("Trigo : " + trigoDelta + " milliseconds");
System.out.println("Bresenham :" + bresenDelta + " milliseconds");
System.out.println("Standard :" + standardDelta + " milliseconds");
}
这是它将生成的渲染类型(绘制每种类型的1000个圆圈)
不幸的是,我的Bresenham实施非常缓慢。我采取了许多比较措施,Bresenham的实施不仅比Graphics.drawArc
慢,而且比三角法更慢。对于绘制的各种圆圈,请查看以下度量。
我的实施的哪一部分更耗时?我可以用它来改进吗?谢谢你的帮助。
[EDITION] :根据@higuaro的要求,这是我绘制圆形的三角算法
public static void drawTrigonometricalCircle (double r, double width, double height, Graphics g) {
double x0 = 0;
double y0 = 0;
boolean isStart = true;
for (double angle = 0; angle <= 2*Math.PI; angle = angle + Math.PI/36) {
double x = r * Math.cos(angle);
double y = r * Math.sin(angle);
drawPoint((double)x, y, width, height, g);
if (!isStart) {
drawLine(x0, y0, x, y, width, height, g);
}
isStart = false;
x0 = x;
y0 = y;
}
}
用于绘制一堆三角圆的方法
public static void drawABunchOfTrigonometricalCircles(int numOfCircles, double width, double height, Graphics g) {
double r = 5;
double step = (300.0-5.0)/numOfCircles;
for (int i = 1; i <= numOfCircles; i++) {
drawTrigonometricalCircle(r, width, height, g);
r += step;
}
}
答案 0 :(得分:3)
你的Bressenham方法本身并不慢,只是相对较慢。
Swing的drawArc()
实现与机器有关,使用本机代码。你永远不会用Java来打败它,所以不要费心去尝试。 (我真的很惊讶Java Bressenham方法与drawArc()
相比速度一样快,这证明了执行Java字节码的虚拟机的质量。)
然而,你的三角法不必要地快,因为你没有在平等的基础上将它与Bressenham比较。
trig方法的设定角度分辨率为PI/36
(~4.7度),如for
语句末尾的此操作所示:
angle = angle + Math.PI/36
同时,您的Bressenham方法依赖于半径,计算每个像素变化的值。当每个八分圆产生sqrt(2)
个点时,将其乘以8
并除以2*Pi
将得到等效的角度分辨率。因此,为了与Bressenham方法保持平等,您的trig方法应该具有:
resolution = 4 * r * Math.sqrt(2) / Math.PI;
在循环外的某个地方,并按照以下方式递增for
:
angle += resolution
由于我们现在将回到像素级分辨率,您实际上可以改进trig方法并删除后续的drawline
调用以及x0
和y0
的分配,从而消除不必要的强制转换,并进一步减少对Math
的调用。这是完整的新方法:
public static void drawTrigonometricalCircle (double r, double width, double height,
Graphics g) {
double localPi = Math.PI;
double resolution = 4 * r * Math.sqrt(2) / Math.PI;
for (double angle = 0; angle <= localPi; angle += resolution) {
double x = r * Math.cos(angle);
double y = r * Math.sin(angle);
drawPoint(x, y, width, height, g);
}
}
根据r
的大小,trig方法现在将更频繁地执行几个数量级。
我有兴趣看到你的结果。
答案 1 :(得分:2)
你的问题在于Bresenham的算法根据圆的大小进行可变数量的迭代,而你的三角方法总是进行固定的迭代次数。
这也意味着Bresenham的算法将始终产生一个平滑的圆形,而随着半径的增加,你的三角学方法会产生更糟的外观。
为了使它更均匀,改变三角学方法产生与Bresenham实现大致相同的点,你会看到它的速度有多快。
我写了一些代码来对此进行基准测试,并打印出产生的点数,这是最初的结果:
三角函数:181毫秒,平均73分 Bresenham:120毫秒,平均867.568分
修改三角函数类以对更平滑的圆进行更多迭代:
int totalPoints = (int)Math.ceil(0.7 * r * 8);
double delta = 2 * Math.PI / totalPoints;
for (double angle = 0; angle <= 2*Math.PI; angle = angle + delta) {
结果如下:
三角函数:2006 ms,平均854.933分
Bresenham:120毫秒,平均867.568分
答案 2 :(得分:0)
我最近为一个sprite光栅化器自己编写了一个bresenham圆图实现,并尝试对其进行一些优化。我不确定是否会比您做的更快或更慢,但是我认为它应该有相当不错的执行时间。
不幸的是,它是用C ++编写的。如果明天有时间,我可以使用移植的Java版本和示例图片来编辑我的答案,但现在,如果需要,您必须自己做(或其他愿意花时间编辑的人) )
基本上,它的作用是使用bresenham算法获取圆的外边缘的位置,然后对圆的1/8执行该算法,并通过从中绘制直线来镜像其余7个部分的算法中心到外边缘。
Color
只是一个rgba值
Color* createCircleColorArray(const int radius, const Color& color, int& width, int& height) {
// Draw circle with custom bresenham variation
int decision = 3 - (2 * radius);
int center_x = radius;
int center_y = radius;
Color* data;
// Circle is center point plus radius in each direction high/wide
width = height = 2 * radius + 1;
data = new Color[width * height];
// Initialize data array for transparency
std::fill(data, data + width * height, Color(0.0f, 0.0f, 0.0f, 0.0f));
// Lambda function just to draw vertical/horizontal straight lines
auto drawLine = [&data, width, height, color] (int x1, int y1, int x2, int y2) {
// Vertical
if (x1 == x2) {
if (y2 < y1) {
std::swap(y1, y2);
}
for (int x = x1, y = y1; y <= y2; y++) {
data[(y * width) + x] = color;
}
}
// Horizontal
if (y1 == y2) {
if (x2 < x1) {
std::swap(x1, x2);
}
for (int x = x1, y = y1; x <= x2; x++) {
data[(y * width) + x] = color;
}
}
};
// Lambda function to draw actual circle split into 8 parts
auto drawBresenham = [color, drawLine] (int center_x, int center_y, int x, int y) {
drawLine(center_x + x, center_y + x, center_x + x, center_y + y);
drawLine(center_x - x, center_y + x, center_x - x, center_y + y);
drawLine(center_x + x, center_y - x, center_x + x, center_y - y);
drawLine(center_x - x, center_y - x, center_x - x, center_y - y);
drawLine(center_x + x, center_y + x, center_x + y, center_y + x);
drawLine(center_x - x, center_y + x, center_x - y, center_y + x);
drawLine(center_x + x, center_y - x, center_x + y, center_y - x);
drawLine(center_x - x, center_y - x, center_x - y, center_y - x);
};
for (int x = 0, y = radius; y >= x; x++) {
drawBresenham(center_x, center_y, x, y);
if (decision > 0) {
y--;
decision += 4 * (x - y) + 10;
}
else {
decision += 4 * x + 6;
}
}
return data;
}
//编辑
哦,我刚刚意识到这个问题有多大了。