基于Xiaolin Wu抗锯齿算法的圆角矩形低半径像素异常排查
修复Xiaolin Wu抗锯齿圆角矩形小半径下的外部像素不透明问题
问题场景
- 绘制小半径(如
r=5)的抗锯齿圆角矩形时,圆角外部像素出现不透明异常,无法实现平滑透明的边缘效果 - 调用参数:
DrawRoundedRectangle(600, 300, 140, 140, 5, 0xEE222222)(对应RGBA(34,34,34,238)) - 推测原因:迭代绘制8个像素导致小半径下像素冲突
原实现代码
void DrawRoundedRectangle(int cx , int cy , int width , int height , int r, uint32_t color) { uint32_t alpha1 , alpha2; const uint32_t color2 = color & 0x00FFFFFF; int x = r; int y = 0; float t = 0; while ( x > y ) { const float curDist = 1.0f - fmodf( sqrtf( static_cast<float>( r * r - y * y ) ) , 1.0 ); if ( curDist < t ) x--; alpha1 = color2 | static_cast<uint8_t>( 127 * curDist ) << 24; alpha2 = color2 | static_cast<uint8_t>( 127 - 127 * curDist ) << 24; // top left dot( cx - y , cy - x + 1 , alpha1 ); dot( cx - y , cy - x - 1 , alpha2 ); dot( cx - x + 1 , cy - y , alpha1 ); dot( cx - x - 1 , cy - y , alpha2 ); // bot left dot( cx - x + 1 , cy + y + height , alpha1 ); dot( cx - x - 1 , cy + y + height , alpha2 ); dot( cx - y , cy + x - 1 + height , alpha1 ); dot( cx - y , cy + x + 1 + height , alpha2 ); // bot right dot( cx + y + width , cy + x - 1 + height , alpha1 ); dot( cx + y + width , cy + x + 1 + height , alpha2 ); dot( cx + x - 1 + width , cy + y + height , alpha1 ); dot( cx + x + 1 + width , cy + y + height , alpha2 ); // top right dot( cx + x - 1 + width , cy - y , alpha1 ); dot( cx + x + 1 + width , cy - y , alpha2 ); dot( cx + y + width , cy - x + 1 , alpha1 ); dot( cx + y + width , cy - x - 1 , alpha2 ); // last iteration only if ( y == x ) { dot( cx - y, cy - x , color ); dot( cx - x , cy + y + height , color ); dot( cx + y + width , cy + x + height , color ); dot( cx + x + width , cy - y , color ); dot( cx - y , cy - x - 1, alpha2 ); dot( cx - x , cy + y + height - 1, alpha2 ); dot( cx + y + width , cy + x + height - 1, alpha2 ); dot( cx + x + width , cy - y - 1, alpha2 ); } // skip 1st iteration else if ( y > 0 ) { dot( cx - y , cy - x , color ); dot( cx - x , cy - y , color ); dot( cx - x , cy + y + height , color ); dot( cx - y , cy + x + height , color ); dot( cx + y + width , cy + x + height , color ); dot( cx + x + width , cy + y + height , color ); dot( cx + x + width , cy - y , color ); dot( cx + y + width , cy - x , color ); } t = curDist; y++; } // rectangle lines setColor( color ); DrawLine( cx , cy - r + 1 , cx + width + 1, cy - r + 1 ); DrawLine( cx - r + 1 , cy , cx - r + 1 , cy + height + 1 ); DrawLine( cx , cy + height + r - 1 , cx + width + 1, cy + height + r - 1 ); DrawLine( cx + width + r - 1 , cy + height + 1, cx + width + r - 1 , cy ); // rectangle outer shadow auto half_opacity = ( ( ( color & 0xFF000000 ) >> 24 ) / 2 ) << 24; setColor( half_opacity | color2 ); DrawLine( cx , cy - r , cx + width + 1, cy - r ); DrawLine( cx - r , cy , cx - r , cy + height + 1 ); DrawLine( cx + 1 , cy + height + r , cx + width + 1, cy + height + r ); DrawLine( cx + width + r , cy + height + 1, cx + width + r , cy ); }
问题根源分析
- Alpha值缩放逻辑错误:原代码固定用
127作为Alpha基准值,未基于输入颜色的原始Alpha(238)做比例缩放,导致抗锯齿像素的Alpha范围与主颜色不匹配,小半径下重叠后边缘生硬。 - 像素重复覆盖:小半径迭代时,部分坐标会被多次调用
dot函数绘制,若dot是直接赋值而非Alpha混合,后绘制的不透明像素会覆盖之前的半透明像素。 - 阴影线范围不合理:外部阴影线的绘制范围与圆角抗锯齿区域重叠,进一步加剧边缘不透明问题。
不破坏现有逻辑的修复方案
1. 修正Alpha缩放计算
基于输入颜色的原始Alpha值计算抗锯齿像素的透明度,替换原Alpha计算代码:
uint8_t original_alpha = (color >> 24) & 0xFF; alpha1 = color2 | static_cast<uint8_t>( original_alpha * curDist ) << 24; alpha2 = color2 | static_cast<uint8_t>( original_alpha * (1.0f - curDist) ) << 24;
2. 确保dot函数支持Alpha混合
如果dot当前是直接覆盖像素,修改为Alpha混合逻辑:
// 示例Alpha混合实现(需根据实际渲染上下文调整) void dot(int x, int y, uint32_t color) { uint8_t src_r = (color >> 16) & 0xFF; uint8_t src_g = (color >> 8) & 0xFF; uint8_t src_b = color & 0xFF; uint8_t src_a = (color >> 24) & 0xFF; uint32_t dst_color = get_pixel(x, y); // 获取目标像素颜色 uint8_t dst_r = (dst_color >> 16) & 0xFF; uint8_t dst_g = (dst_color >> 8) & 0xFF; uint8_t dst_b = dst_color & 0xFF; uint8_t dst_a = (dst_color >> 24) & 0xFF; float src_weight = src_a / 255.0f; float dst_weight = 1.0f - src_weight; uint8_t new_r = static_cast<uint8_t>(src_r * src_weight + dst_r * dst_weight); uint8_t new_g = static_cast<uint8_t>(src_g * src_weight + dst_g * dst_weight); uint8_t new_b = static_cast<uint8_t>(src_b * src_weight + dst_b * dst_weight); uint8_t new_a = static_cast<uint8_t>(src_a + dst_a * dst_weight); set_pixel(x, y, (new_a << 24) | (new_r << 16) | (new_g << 8) | new_b); }
3. 调整阴影线绘制范围
避免阴影线与圆角抗锯齿区域重叠,修改阴影线绘制代码:
// 修正后的外部阴影线(仅绘制圆角之间的直线部分) DrawLine( cx + r , cy - r , cx + width - r , cy - r ); DrawLine( cx - r , cy + r , cx - r , cy + height - r ); DrawLine( cx + r , cy + height + r , cx + width - r , cy + height + r ); DrawLine( cx + width + r , cy + r , cx + width + r , cy + height - r );
4. 优化循环终止条件
避免重复绘制相同坐标,将循环条件改为while(y <= x)并调整x递减逻辑:
while (y <= x) { const float curDist = 1.0f - fmodf( sqrtf( static_cast<float>( r * r - y * y ) ) , 1.0 ); if (curDist < t && x > 0) { x--; } // ... 其余绘制逻辑不变 ... t = curDist; y++; }
内容的提问来源于stack exchange,提问作者Svotin
相关产品推荐
相关产品推荐

