- 浏览: 107699 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
luluandbobo:
为什么要先释放table[T]呢,它不是一个指针吗?如果释放掉 ...
《算法导论》笔记--平摊分析 -
liuxuejin:
全为理论,我在网上找了N久都找 不到一个具体的B树操作磁盘文件 ...
《算法导论》笔记--B树 -
westice:
非常好!正苦于怎么确定贝塞尔曲线的控制点,很好的思路。
[翻译] AGG 之贝塞尔插值 -
liyiwen007:
dracularking 写道第三种情况的图是不是不对,z成左 ...
《算法导论》笔记--红黑树(一) -
dracularking:
第三种情况的图是不是不对,z成左子节点了?
《算法导论》笔记--红黑树(一)
<<<< 续上文
Collinear Case
感谢 Timothee Groleau, http://www.timotheegroleau.com 里有他的方法,可以很简单地估计出曲率。就是点1234和线1-4的中点之间的距离。这与估算点和线之间的距离完全不同。他的方法清寒给出一个近似的值,虽然这个值仍然不够。但他的方法有一个很重要的优点,即,可以处理下面这种同线的情况。当四个点的顺序是下面这样的时候:
2--------1---------4----------3
所有使用点到线距离的判定方法在这种情况下都会失效。这里只有一条线段1-4 ,而这明显是不对的。Timothee 的判定方法在这种情况下仍然有效,这样我们就有了一个检测共线情况的机制。因此,处理共线情况的代码就相当简单了:
. . .
else
{
// Collinear case
//-----------------
dx = x1234 - (x1 + x4) / 2;
dy = y1234 - (y1 + y4) / 2;
if(dx*dx + dy*dy <= m_distance_tolerance)
{
m_points.add(point_type(x1234, y1234));
return;
}
}
嗯,伪代码如下:
void recursive_bezier(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, unsigned level) { double x12 = (x1 + x2) / 2; double y12 = (y1 + y2) / 2; double x23 = (x2 + x3) / 2; double y23 = (y2 + y3) / 2; double x34 = (x3 + x4) / 2; double y34 = (y3 + y4) / 2; double x123 = (x12 + x23) / 2; double y123 = (y12 + y23) / 2; double x234 = (x23 + x34) / 2; double y234 = (y23 + y34) / 2; double x1234 = (x123 + x234) / 2; double y1234 = (y123 + y234) / 2; if(level) { if(curve_is_flat) { draw_line(x1, y1, x4, y4); return; } } recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1); recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1); }
也可以对递归进行限制,当然,这里看起来已经不必要去限制它了(因为已经有其它的判定方法来停止递归),但是严格的数学证明对我来说非常困难,所以,我直接把递归限定在 32 次以内。在实践中,即使是最恶劣的情况下(非常长的曲线,几千个点,很小的错误,有尖端点),我也没见过超过17次的递归,
Timothee 的判定方法在另一种共线情况下会产生很多的点: 1---2---3-------4 。 如果先检测出这种情况可能会比较,这样就只需要产生两个点(1-4),但我觉得这无关紧要,因为严格共线的情况非常少。所以,实际上这并不会影响性能。 还有另外两种共线的情况: if(d2 > curve_collinearity_epsilon)
{
// p1,p3,p4 are collinear (or coincide), p2 is considerable
. . .
}
else if(d3 > curve_collinearity_epsilon)
{
// p1,p2,p4 are collinear (or coincide), p3 is considerable
. . .
}
The Full Code
最后,我们看起完整的代码:
Class curve4_div, files agg_curves.h, agg_curves.cpp. 上面我说要告诉你的那个细节是关于处理共线情况的一段代码: 这个类有下面的这些参数://------------------------------------------------------------------------
void curve4_div::init(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
m_points.remove_all();
m_distance_tolerance = 0.5 / m_approximation_scale;
m_distance_tolerance *= m_distance_tolerance;
bezier(x1, y1, x2, y2, x3, y3, x4, y4);
m_count = 0;
}
//------------------------------------------------------------------------
void curve4_div::recursive_bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4,
unsigned level)
{
if(level > curve_recursion_limit)
{
return;
}
// Calculate all the mid-points of the line segments
//----------------------
double x12 = (x1 + x2) / 2;
double y12 = (y1 + y2) / 2;
double x23 = (x2 + x3) / 2;
double y23 = (y2 + y3) / 2;
double x34 = (x3 + x4) / 2;
double y34 = (y3 + y4) / 2;
double x123 = (x12 + x23) / 2;
double y123 = (y12 + y23) / 2;
double x234 = (x23 + x34) / 2;
double y234 = (y23 + y34) / 2;
double x1234 = (x123 + x234) / 2;
double y1234 = (y123 + y234) / 2;
if(level > 0) // Enforce subdivision first time
{
// Try to approximate the full cubic curve by a single straight line
//------------------
double dx = x4-x1;
double dy = y4-y1;
double d2 = fabs(((x2 - x4) * dy - (y2 - y4) * dx));
double d3 = fabs(((x3 - x4) * dy - (y3 - y4) * dx));
double da1, da2;
if(d2 > curve_collinearity_epsilon && d3 > curve_collinearity_epsilon)
{
// Regular care
//-----------------
if((d2 + d3)*(d2 + d3) <= m_distance_tolerance * (dx*dx + dy*dy))
{
// If the curvature doesn't exceed the distance_tolerance value
// we tend to finish subdivisions.
//----------------------
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_type(x1234, y1234));
return;
}
// Angle & Cusp Condition
//----------------------
double a23 = atan2(y3 - y2, x3 - x2);
da1 = fabs(a23 - atan2(y2 - y1, x2 - x1));
da2 = fabs(atan2(y4 - y3, x4 - x3) - a23);
if(da1 >= pi) da1 = 2*pi - da1;
if(da2 >= pi) da2 = 2*pi - da2;
if(da1 + da2 < m_angle_tolerance)
{
// Finally we can stop the recursion
//----------------------
m_points.add(point_type(x1234, y1234));
return;
}
if(m_cusp_limit != 0.0)
{
if(da1 > m_cusp_limit)
{
m_points.add(point_type(x2, y2));
return;
}
if(da2 > m_cusp_limit)
{
m_points.add(point_type(x3, y3));
return;
}
}
}
}
else
{
if(d2 > curve_collinearity_epsilon)
{
// p1,p3,p4 are collinear, p2 is considerable
//----------------------
if(d2 * d2 <= m_distance_tolerance * (dx*dx + dy*dy))
{
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_type(x1234, y1234));
return;
}
// Angle Condition
//----------------------
da1 = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
if(da1 >= pi) da1 = 2*pi - da1;
if(da1 < m_angle_tolerance)
{
m_points.add(point_type(x2, y2));
m_points.add(point_type(x3, y3));
return;
}
if(m_cusp_limit != 0.0)
{
if(da1 > m_cusp_limit)
{
m_points.add(point_type(x2, y2));
return;
}
}
}
}
else
if(d3 > curve_collinearity_epsilon)
{
// p1,p2,p4 are collinear, p3 is considerable
//----------------------
if(d3 * d3 <= m_distance_tolerance * (dx*dx + dy*dy))
{
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_type(x1234, y1234));
return;
}
// Angle Condition
//----------------------
da1 = fabs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2));
if(da1 >= pi) da1 = 2*pi - da1;
if(da1 < m_angle_tolerance)
{
m_points.add(point_type(x2, y2));
m_points.add(point_type(x3, y3));
return;
}
if(m_cusp_limit != 0.0)
{
if(da1 > m_cusp_limit)
{
m_points.add(point_type(x3, y3));
return;
}
}
}
}
else
{
// Collinear case
//-----------------
dx = x1234 - (x1 + x4) / 2;
dy = y1234 - (y1 + y4) / 2;
if(dx*dx + dy*dy <= m_distance_tolerance)
{
m_points.add(point_type(x1234, y1234));
return;
}
}
}
}
// Continue subdivision
//----------------------
recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1);
recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1);
}
//------------------------------------------------------------------------
void curve4_div::bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
m_points.add(point_type(x1, y1));
recursive_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0);
m_points.add(point_type(x4, y4));
}
if(da1 < m_angle_tolerance)
{
m_points.add(point_type(x2, y2));
m_points.add(point_type(x3, y3));
return;
}
-
-approximation_scale :最终决定估算的精度。在实际应用中,我们需要从点的世界坐标转换到屏幕坐标,因此总会存在一定的缩放因子。曲线通常是在世界坐标系中处理的,而进行估算时又要转换为像素值。一般看起来会是这样的:m_curved.approximation_scale(m_transform.scale());
这里,m_transform 是一个仿型映射的矩阵,里面包含了所有的转换,包括视点和缩放。 - -angle_tolerance:以弧度来设置。这个值越少,在曲线的转弯处的估算就越精确。不过,如果设置为0,那么表示完全 不考虑角度条件。
- -cusp_limit:一个以弧度来设置的角度。如果是0,那么只有真正的尖端(cusp)才会有 bevel cut。如果大于0,那么它会限制曲线的弯曲度,值越大,曲线锐利的转弯处被切得就越少。一般,这个值不应该大于10-15度。
我上面提到过,通过角度来进行估算开销比较大,而且我们也不是总是需要它。我们只是在有曲线有明显的stroke或是contours的时候才用它。所以,为了优化它,我们可以这样做:
double scl = m_transform.scale();
m_curved.approximation_scale(scl);
// Turn off processing of curve cusps
//-----------------
m_curved.angle_tolerance(0);
if(attr.fill_flag)
{
// Process the fill
. . .
}
if(attr.stroke_flag)
{
// Process the stroke
//---------------------
// If the *visual* line width is considerable we
// turn on processing of sharp turns and cusps.
//---------------------
if(attr.stroke_width * scl > 1.0)
{
m_curved.angle_tolerance(0.2);
}
. . .
}
Quadric Curves
二次曲线处理起来要简单的得多。我们连 cusp_limit 的判定都不需要,因为共线的情况已经可以处理所有退化的情况:
//------------------------------------------------------------------------
void curve3_div::init(double x1, double y1,
double x2, double y2,
double x3, double y3)
{
m_points.remove_all();
m_distance_tolerance = 0.5 / m_approximation_scale;
m_distance_tolerance *= m_distance_tolerance;
bezier(x1, y1, x2, y2, x3, y3);
m_count = 0;
}
//------------------------------------------------------------------------
void curve3_div::recursive_bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
unsigned level)
{
if(level > curve_recursion_limit)
{
return;
}
// Calculate all the mid-points of the line segments
//----------------------
double x12 = (x1 + x2) / 2;
double y12 = (y1 + y2) / 2;
double x23 = (x2 + x3) / 2;
double y23 = (y2 + y3) / 2;
double x123 = (x12 + x23) / 2;
double y123 = (y12 + y23) / 2;
double dx = x3-x1;
double dy = y3-y1;
double d = fabs(((x2 - x3) * dy - (y2 - y3) * dx));
if(d > curve_collinearity_epsilon)
{
// Regular care
//-----------------
if(d * d <= m_distance_tolerance * (dx*dx + dy*dy))
{
// If the curvature doesn't exceed the distance_tolerance value
// we tend to finish subdivisions.
//----------------------
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_type(x123, y123));
return;
}
// Angle & Cusp Condition
//----------------------
double da = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
if(da >= pi) da = 2*pi - da;
if(da < m_angle_tolerance)
{
// Finally we can stop the recursion
//----------------------
m_points.add(point_type(x123, y123));
return;
}
}
}
else
{
// Collinear case
//-----------------
dx = x123 - (x1 + x3) / 2;
dy = y123 - (y1 + y3) / 2;
if(dx*dx + dy*dy <= m_distance_tolerance)
{
m_points.add(point_type(x123, y123));
return;
}
}
// Continue subdivision
//----------------------
recursive_bezier(x1, y1, x12, y12, x123, y123, level + 1);
recursive_bezier(x123, y123, x23, y23, x3, y3, level + 1);
}
//------------------------------------------------------------------------
void curve3_div::bezier(double x1, double y1,
double x2, double y2,
double x3, double y3)
{
m_points.add(point_type(x1, y1));
recursive_bezier(x1, y1, x2, y2, x3, y3, 0);
m_points.add(point_type(x3, y3));
}
Demo Application
你可以从这里下载一个Win32下的Demo程序
载图如下:
这个程度运行起来很慢,不过这不重要。我使用五个缩放尺度计算了最大的距离和角度误差:0.01, 0.1, 1, 10, 和 100(这非常耗时)。距离误差是指设置approximation_scale() 为以上五个值中的一个,计算出最大误差,然后乘以 approximation_scale() 的值。所以,它是屏幕分辨率(像素值)归一化后的最大误差。(原句: So that, it will be the maximal error normalized to the screen resolution (pixels).)
参考曲线是用 Paul Bourke 方法画的,使用的是很小的步进(4096个点)。 你可以清楚地看到递增方法(incremental method)和细分方法(subdivision method)最大误差的差别。不管scale是多少,细分方法的误差基本保持了一致。但递增方法在 scale为10和100时误差变小了(But in the incremental method it decreases on the 10x and 100x scales.)。这种行为明显是不对的,因为我们不需要0.001像素的错误。这意味着曲线的点太多了。我们需要在误差和点数之间取得平衡。 你可以比较下两个渲染的结果:
这个结果看起来差别很小,但是如果你把两张图下下来,使用滑动窗口来转换两张图。这两个例子中,我设置精度的目标是为了让两者产生的点的数目一相近。如果你仔细比较这两个图,你会发生细分方法渲染的结果要精确得多。
当然,递增方法也很好,因为它很简单,很快,可以用在那些性能要求很高的地方。一个典型的情形是,用于字体的渲染。字体通常没有多少复杂的曲线,所有的曲线都像弧线(TrueType字体大部分情况下只用了二次曲线)。
Update 1
在写这篇文章的时候我已经意识到,添加点1234并不够好。In this case the curve is circumscribed and the polyline is escribed. 如果我们添加点23,我们可以得到更好的结果。假设我们只进行一次细分,比较一下两者的结果:
这里,蓝色的线是我们用点1234产生的,而绿色的则是使用点23产生的。很明显,绿色线的误差要小一些,在实际中,大约是1.5或2倍的差别。
Update 2
在 comp.graphics.algorithms 新闻组,我因为对专著的不熟悉而受到了相当严历的批评。完整的讨论在这里可以找到,你也可以使用“Adaptive Subdivision of Bezier Curves McSeem”在 http://groups.google.com 里进行搜索。 在网上可以很容易地找到一篇叫“Adaptive forward differencing for rendering curves and surfaces”的文章。里面介绍的方法很好,不过仍然不能解决平滑 stroke 中遇到的所有问题。
但最有意思是信息来自一个昵称是“Just d'FAQs”的人。下面是他的话:
一个很有名的曲线平坦度检测方法比你使用的方法要简单而且可靠。这种方法是基于这样的观察:如果曲线是一个点在端点到端点之间以匀速运动形成的,那么它的控制点会均匀地分布在两个端点之间。因此,我们计算偏移时应该使用中间控制的距离,而不是用线段的距离(原句:Therefore, our measure of how far we deviate from that ideal uses distance of the middle controls, not from the line itself, but from their ideal *arrangement*.)。点2应该是点1和点3的中点,点3应该是点2和点4的中点。(译注:这段把我弄晕了……) 这个方法仍然可以改进,因为我们可以消除距离检测中的平方根计算,可以保留Euclidean metric,但是taxicab metric会更快,而且一样安全。在这种度量方法中(taxicab metric),(x,y)的长度是:|x|+|y| 。
反应成代码就像下面这样:
完整的代码在这里: curve4_div, 文件:agg_curves.h, agg_curves.cpp. if(fabs(x1 + x3 - x2 - x2) +
fabs(y1 + y3 - y2 - y2) +
fabs(x2 + x4 - x3 - x3) +
fabs(y2 + y4 - y3 - y3) <= m_distance_tolerance_manhattan)
{
m_points.add(point_type(x1234, y1234));
return;
}
m_distance_tolerance_square = 0.5 / m_approximation_scale;
m_distance_tolerance_square *= m_distance_tolerance_square;
m_distance_tolerance_manhattan = 4.0 / m_approximation_scale;
发表评论
-
[翻译]AGG 之适应性细分法描画贝塞尔曲线(上)
2010-07-04 23:04 2001Adaptive Subdivision of Bezier ... -
[翻译] AGG 之贝塞尔插值
2010-07-04 23:03 10386原文地址:http://www.antigrain.com/r ... -
[翻译]AGG 之 Gamma 校正
2010-07-04 23:01 1974Gamma Correction Using Gamma C ... -
[翻译]AGG reference 之 Scanline Containers
2010-07-04 23:00 1489Introduction (译注:这篇 reference ... -
[翻译] AGG Reference 之 Basic Renderers(基础渲染器)(二)
2010-07-04 22:59 1968Alpha-Mask Adaptor Alpha-Mask ... -
[翻译] AGG Reference 之 Basic Renderers(基础渲染器)(一)
2010-07-04 22:54 2036Rendering Buffer 我们先从这里开始:在内存中 ... -
开始学习AGG
2010-07-04 22:46 2810发现一个很“帅”的二维图形库----AGG(Anti-G ...
相关推荐
此外,agg还提供了高级的绘图功能,如贝塞尔曲线、圆弧绘制、投影和光照效果。 **5. 示例代码** agg-2.5压缩包中包含了大量的示例代码,这些示例展示了如何使用agg库的各种功能。开发者可以通过这些示例快速上手,...
在描述中提到,本示例实现了AGG下的各种属性设置,这可能包括线宽、线型、颜色、填充模式等。AGG提供了丰富的设置选项,可以让开发者定制出非常精细的视觉效果。同时,透明度的设置是现代图形绘制中常用的功能,AGG...
3. **路径和路径操作**:AGG支持复杂的路径定义,包括直线、曲线、贝塞尔曲线等。你可以学习如何创建、修改和绘制路径,以及如何进行剪裁、平移、缩放等操作。 4. **颜色和渲染风格**:AGG提供了多种颜色模型和混合...
3. **路径绘制**:AGG支持贝塞尔曲线、直线和其他几何形状的绘制,通过`Path`和`PathScanner`类来构建和解析图形路径。 4. **颜色管理**:AGG提供了丰富的颜色模型和色彩空间转换,包括线性RGB、SRGB、CMYK等,以及...
5. **路径算法**:AGG使用高效的算法处理复杂的路径数据,如贝塞尔曲线、样条曲线等,保证了在处理大量图形时的性能。 6. **混合模式**:AGG支持多种混合模式,可以实现复杂的效果,如叠加、差值、乘法等,这些在...
Agg(Anti-Grain Geometry)是一个开源的2D图形渲染引擎,主要由Alexander Romanov开发,用于创建高质量的矢量图形。这个库提供了一种高效且灵活的方式来处理图形,适用于各种应用程序,如图像处理软件、游戏开发、...
- **Path Processing**:处理图形路径,支持贝塞尔曲线、直线和其他几何形状的构建和操作。 - **Color Models and Blending**:提供了多种颜色模型(RGB、CMYK等)和混合模式,实现复杂的色彩效果。 - **Buffers ...
AGG(Anti-Grain Geometry)是一个开源的2D图形渲染引擎,主要设计用于高性能的图形绘制和处理。这个图形库是由Evgeny Panasenkov开发,它以C++编写,提供了高度优化的算法来处理图形操作,如线条绘制、曲线绘制、...
3. **曲线拟合**:AGG采用高精度的贝塞尔曲线和样条曲线算法,确保曲线的精确绘制。 4. **颜色空间管理**:AGG支持多种颜色空间,如RGB、CMYK、HSV等,并可以进行色彩空间转换,以适应不同的显示设备和输出媒介。 ...
其内部算法采用了抗锯齿技术,确保在各种分辨率下都能呈现出平滑的线条和曲线。此外,AGG支持多种图形模式,如线性渐变、径向渐变、位图纹理等,使得开发者可以创建复杂的图形效果。 在"agg2.5源码"部分,我们可以...
•如果要用AGG的控件和窗体,要加入[AGG]\src\ctrl\*.cpp和[AGG]\src\platform\<OS>\*.cpp,头文件在[AGG]\include\ctrl和[AGG]\include\platform里 •如果要用到TrueType字体显示,要加入[AGG]\font_win32_tt目录下...
在Windows平台上,如果你需要使用AGG库进行图形处理或开发项目,你可能会遇到如何在Visual Studio 2013环境下编译和集成AGG的问题。本篇文章将详细解释如何在Windows上配置和编译AGG库,并指导如何将其成功地应用于...
AGG 的另一个特点在于它极大的灵活性。其作者将它描述为“创建其它工具的工具”。AGG 提供一系列松耦合的算法,而且其所有类均采用模板(template)进行描述,开发者可以自由地组合、改写、替换其中部分或全部算法,...
2. **高性能**:AGG使用了优化的算法,能够在内存和CPU资源有限的情况下,仍然保持快速的图形渲染速度。 3. **高度可配置**:用户可以根据需求调整抗锯齿级别、颜色模式和其他渲染参数,以满足不同应用场景的需求。 ...
对于开发者来说,理解并利用AGG Lite的这些特性,可以在有限的资源下创建出高精度、高性能的2D图形应用,如图表、地图、用户界面等。同时,由于是开源项目,开发者还可以根据需要对库进行定制和扩展,以满足特定项目...
Agg在Windows下的编译与使用 AGG(Anti-Grain Geometry)是一个开源免费的图形库。 官网地址: www.antigrain.com 环境: Win10 x64 Visual Studio 2013 字符集 Unicode 主要是编译称为Lib库,然后提供给其他程序...
- 若需使用AGG提供的控件和窗体功能,需添加`[AGG]\src\ctrl\*.cpp`及对应平台目录下的文件,例如Windows平台下的`[AGG]\src\platform\win32\*.cpp`。 - 若要支持TrueType字体显示,需要添加`[AGG]\font_win32_tt`...
顶点源可以是简单的线性序列,也可以是复杂的贝塞尔曲线。掌握如何创建和操作顶点源是绘制复杂图形的基础。 6. **练习和细节**:AGG库提供了丰富的功能,但同时也需要对图形渲染有深入的理解。通过实际的练习,你...
扫描线光栅化器是AGG中的核心组件之一,负责将图形数据转化为一组水平扫描线。 - **扫描线** (`Scanline`):表示图像中的每一行像素。 - **头文件**:`agg_scanline_u.h` - **类型**:`agg::scanline_u8` - **...
一个很优秀的2D图形引擎. Anti-Grain Geometry (AGG) - Version 2.5 A high quality rendering engine for C++ Copyright (C) 2002-2006 Maxim Shemanarev