如何在Cairo中沿任意路径实现线条的透明度渐变?
在Cairo中实现路径线条沿路径方向的透明度渐变
方法一:线性渐变蒙版(简单近似)
这种方法适合路径整体走向接近直线的场景,通过将描边作为底层,再用线性渐变结合CAIRO_OPERATOR_DEST_IN实现透明度渐变,代码简洁,无需复杂计算。
代码示例
#include <cairo.h> #include <math.h> int main(int argc, char *argv[]) { cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 200, 200); cairo_t *cr = cairo_create(surface); // 绘制背景 cairo_set_source_rgb(cr, .1, .1, .1); cairo_paint(cr); // 设置线条属性 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); cairo_set_line_width(cr, 20.0); // 定义路径 int x = 10, y = 10; cairo_move_to(cr, x, y); double start_x = x, start_y = y; x += 150; cairo_line_to(cr, x, y); x -= 50; y += 150; cairo_line_to(cr, x, y); double end_x = x, end_y = y; // 创建临时组,绘制不透明的描边 cairo_push_group(cr); cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); cairo_stroke(cr); // 创建从路径起点到终点的线性渐变(仅alpha变化) cairo_pattern_t *grad = cairo_pattern_create_linear(start_x, start_y, end_x, end_y); // 起点透明度0.6,终点透明度0.4 cairo_pattern_add_color_stop_rgba(grad, 0.0, 0, 0, 0, 0.6); cairo_pattern_add_color_stop_rgba(grad, 1.0, 0, 0, 0, 0.4); // 使用DEST_IN操作符,将渐变的alpha应用到描边上 cairo_set_source(cr, grad); cairo_set_operator(cr, CAIRO_OPERATOR_DEST_IN); cairo_paint(cr); // 将处理后的组绘制到目标表面 cairo_pop_group_to_source(cr); cairo_paint(cr); // 清理资源 cairo_pattern_destroy(grad); cairo_destroy(cr); cairo_surface_write_to_png(surface, "gradient_mask.png"); cairo_surface_destroy(surface); return 0; }
优缺点
- 优点:代码简洁,无需计算路径长度,支持任意路径的描边渐变。
- 缺点:渐变方向是路径起点到终点的直线方向,而非严格沿路径的折线/曲线走向,对于弯折较大的路径,渐变效果会有偏差。
方法二:分段渐变绘制(严格沿路径方向)
这种方法通过计算路径每一段的长度,为每一段创建沿自身方向的渐变,确保透明度严格沿路径长度变化,同时处理衔接避免重叠。
代码示例
#include <cairo.h> #include <math.h> // 计算路径中线段的总长度(支持直线和贝塞尔曲线近似) double compute_path_length(cairo_path_t *path) { double total = 0.0; cairo_path_data_t *data = path->data; int i = 0; double prev_x, prev_y; while (i < path->num_data) { cairo_path_data_type_t type = data[i].header.type; int points = data[i].header.length; if (type == CAIRO_PATH_MOVE_TO) { prev_x = data[i+1].point.x; prev_y = data[i+1].point.y; i += points; } else if (type == CAIRO_PATH_LINE_TO) { double x = data[i+1].point.x; double y = data[i+1].point.y; total += hypot(x - prev_x, y - prev_y); prev_x = x; prev_y = y; i += points; } else if (type == CAIRO_PATH_CURVE_TO) { // 贝塞尔曲线分段近似计算长度 double x0 = prev_x, y0 = prev_y; double x1 = data[i+1].point.x, y1 = data[i+1].point.y; double x2 = data[i+2].point.x, y2 = data[i+2].point.y; double x3 = data[i+3].point.x, y3 = data[i+3].point.y; for (int t = 1; t <= 10; t++) { double u = t / 10.0; double um = 1 - u; double x = um*um*um*x0 + 3*um*um*u*x1 + 3*um*u*u*x2 + u*u*u*x3; double y = um*um*um*y0 + 3*um*um*u*y1 + 3*um*u*u*y2 + u*u*u*y3; total += hypot(x - prev_x, y - prev_y); prev_x = x; prev_y = y; } i += points; } else if (type == CAIRO_PATH_CLOSE_PATH) { // 闭合路径连接到起点,这里简化处理 i += points; } } return total; } int main(int argc, char *argv[]) { cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 200, 200); cairo_t *cr = cairo_create(surface); // 绘制背景 cairo_set_source_rgb(cr, .1, .1, .1); cairo_paint(cr); // 设置线条属性 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); cairo_set_line_width(cr, 20.0); // 定义路径并记录关键点 double start_x = 10, start_y = 10; double mid_x = start_x + 150, mid_y = start_y; double end_x = mid_x - 50, end_y = mid_y + 150; cairo_path_t *path; { cairo_new_path(cr); cairo_move_to(cr, start_x, start_y); cairo_line_to(cr, mid_x, mid_y); cairo_line_to(cr, end_x, end_y); path = cairo_copy_path(cr); } // 计算总长度和各段长度 double total_len = compute_path_length(path); double seg1_len = hypot(mid_x - start_x, mid_y - start_y); double seg2_len = hypot(end_x - mid_x, end_y - mid_y); // 透明度范围:起点0.6,终点0.4 double start_alpha = 0.6; double end_alpha = 0.4; // 绘制第一段:从start到mid,透明度从start_alpha过渡到seg1_end_alpha double seg1_end_alpha = start_alpha + (end_alpha - start_alpha) * (seg1_len / total_len); cairo_pattern_t *grad1 = cairo_pattern_create_linear(start_x, start_y, mid_x, mid_y); cairo_pattern_add_color_stop_rgba(grad1, 0.0, 1.0, 1.0, 0.0, start_alpha); cairo_pattern_add_color_stop_rgba(grad1, 1.0, 1.0, 1.0, 0.0, seg1_end_alpha); cairo_new_path(cr); cairo_move_to(cr, start_x, start_y); cairo_line_to(cr, mid_x, mid_y); cairo_set_source(cr, grad1); cairo_stroke(cr); cairo_pattern_destroy(grad1); // 绘制第二段:从mid到end,透明度从seg1_end_alpha过渡到end_alpha cairo_pattern_t *grad2 = cairo_pattern_create_linear(mid_x, mid_y, end_x, end_y); cairo_pattern_add_color_stop_rgba(grad2, 0.0, 1.0, 1.0, 0.0, seg1_end_alpha); cairo_pattern_add_color_stop_rgba(grad2, 1.0, 1.0, 1.0, 0.0, end_alpha); cairo_new_path(cr); cairo_move_to(cr, mid_x, mid_y); cairo_line_to(cr, end_x, end_y); cairo_set_source(cr, grad2); cairo_stroke(cr); cairo_pattern_destroy(grad2); // 清理资源 cairo_path_destroy(path); cairo_destroy(cr); cairo_surface_write_to_png(surface, "segmented_gradient.png"); cairo_surface_destroy(surface); return 0; }
优缺点
- 优点:透明度严格沿路径长度方向渐变,支持任意路径(直线、曲线),效果准确。
- 缺点:需要计算路径各段长度,代码稍复杂;对于多段路径需要逐一处理,贝塞尔曲线需要近似计算长度。
注意事项
- 确保使用的Cairo版本支持相关API(如
cairo_push_group、cairo_pattern_create_linear等,Cairo 1.8及以上版本基本支持)。 - 若路径包含大量曲线段,可调整贝塞尔曲线长度计算的分段数(示例中为10段),平衡精度与性能。
内容的提问来源于stack exchange,提问作者user52366
相关产品推荐
相关产品推荐

