为什么我的cairo画出的直线不同角度宽度不同???

Posted 从善若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我的cairo画出的直线不同角度宽度不同???相关的知识,希望对你有一定的参考价值。

一、为什么我的cairo画出的直线不同角度宽度不同???

       前一段时间研究通感一体化,需要使用Cairo绘制波形图,设置了统一的线宽,但是绘制出的图形线段的宽度却不一样,原始code如下:

/****************************************************/
/***********  gtk_line_chart_custom.c   *************/

/* 定义 Properties */
enum {
    PROP_0,
    PROP_AXIS_X_RANGE, // X轴的范围
    PROP_AXIS_Y_RANGE, // Y轴的范围
    PROP_ORIGINAL_X,   // 原点X的坐标
    PROP_ORIGINAL_Y,   // 原点Y的坐标
    PROP_MAX
    };
    
static GParamSpec *line_chart_props[PROP_MAX]={NULL};

// 客制化控件,用于绘制折线图
#define GTK_TYPE_LINE_CHART (gtk_line_chart_get_type ())
G_DECLARE_FINAL_TYPE (GtkLineChart, gtk_line_chart, GTK, LINE_CHART, GObject)

struct _GtkLineChart{
    GObject parent_instance;
};

typedef struct {
    /*坐标轴范围,及原点坐标*/
    double axis_x_range;
    double axis_y_range;
    double original_x;
    double original_y;
}GtkLineChartPrivate;

/***************************** 实现GdkPaintable 接口***********************/
void
gtk_line_chart_snapshot (GdkPaintable *paintable,
                         GdkSnapshot  *snapshot,
                         double        width,
                         double        height)
{
    cairo_t *cr;
    GtkLineChart *lineChart = GTK_LINE_CHART(paintable);
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);

    gtk_snapshot_append_color (snapshot,
            &priv->bg_color,
            &GRAPHENE_RECT_INIT (0, 0, width, height));
    
    cr = gtk_snapshot_append_cairo (snapshot,
            &GRAPHENE_RECT_INIT (0,0,width, height));
    
   	// 变换CTM将坐标轴缩放在 
   	// X:0 ~ priv->axis_x_range
   	// Y:0 ~ priv->axis_y_range
    cairo_scale(cr,width/priv->axis_x_range,height/priv->axis_y_range);

	// 变换CTM ,将坐标的原点移动到(priv->original_x,priv->original_y)
    cairo_translate(cr,priv->original_x,priv->original_y);
    
    //设置线宽
    cairo_set_line_width(cr,80);
    
    // 水平绘制一条直线
    cairo_move_to(cr,-priv->original_x,0.);
    cairo_line_to(cr,priv->axis_x_range-priv->original_x,0.);
    cairo_stroke(cr);
    
    // 对角线绘制一条直线
    cairo_move_to(cr,-priv->original_x,priv->axis_y_range-priv->original_y);
    cairo_line_to(cr,priv->axis_x_range-priv->original_x,-priv->original_y);
    cairo_stroke(cr);

  
    cairo_destroy (cr);
}

static GdkPaintable *
gtk_line_chart_rt_get_current_image (GdkPaintable *paintable)
{
    GtkLineChart *lineChart = GTK_LINE_CHART(paintable);
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);

    return gtk_line_chart_new (priv->axis_x_range,priv->axis_y_range);
}

static void
gtk_line_chart_paintable_init (GdkPaintableInterface *iface)
{
    iface->snapshot = gtk_line_chart_snapshot;
    iface->get_current_image = gtk_line_chart_rt_get_current_image;
}
/***************************** 实现客制化控件***********************/
G_DEFINE_TYPE_WITH_CODE (GtkLineChart , gtk_line_chart, G_TYPE_OBJECT,
        {G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,gtk_line_chart_paintable_init)
        G_ADD_PRIVATE (GtkLineChart)})

static void
gtk_line_chart_set_property (GObject         *object,
                             guint            prop_id,
                             const GValue    *value,
                             GParamSpec      *pspec)
{
    GtkLineChart *lineChart = GTK_LINE_CHART(object);
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);
    
    switch (prop_id){
        case PROP_AXIS_X_RANGE:
            priv->axis_x_range = g_value_get_double(value);
            break;
        case PROP_AXIS_Y_RANGE:
            priv->axis_y_range = g_value_get_double(value);
            break;
        case PROP_ORIGINAL_X:
            priv->original_x = g_value_get_double(value);
            g_return_if_fail(priv->original_x <= priv->axis_x_range);
            break;
        case PROP_ORIGINAL_Y:
            priv->original_y = g_value_get_double(value);
            g_return_if_fail(priv->original_y <= priv->axis_y_range);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
    gdk_paintable_invalidate_contents(GDK_PAINTABLE(lineChart));
}

static void
gtk_line_chart_get_property (GObject         *object,
                             guint            prop_id,
                             GValue          *value,
                             GParamSpec      *pspec)
{
    GtkLineChart *lineChart = GTK_LINE_CHART(object);
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);
    
    switch (prop_id){
        case PROP_AXIS_X_RANGE:
            g_value_set_double (value, priv->axis_x_range);
            break;
        case PROP_AXIS_Y_RANGE:
            g_value_set_double (value, priv->axis_y_range);
            break;
        case PROP_ORIGINAL_X:
            g_value_set_double (value, priv->original_x);
           break;
        case PROP_ORIGINAL_Y:
            g_value_set_double (value, priv->original_y);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
gtk_line_chart_class_init (GtkLineChartClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = gtk_line_chart_finalize;
    gobject_class->set_property = gtk_line_chart_set_property;
    gobject_class->get_property = gtk_line_chart_get_property;
    
    line_chart_props[PROP_AXIS_X_RANGE] =
            g_param_spec_double("x-range",
                    "Axis X range value",
                    "Axis X range value",
                    0,
                    G_MAXDOUBLE,
                    1,
                    G_PARAM_READWRITE);
    
    line_chart_props[PROP_AXIS_Y_RANGE] =
            g_param_spec_double("y-range",
                    "Axis Y range value",
                    "Axis Y range value",
                    0,
                    G_MAXDOUBLE,
                    1,
                    G_PARAM_READWRITE);
    
    line_chart_props[PROP_ORIGINAL_X] =
            g_param_spec_double("original-x",
                    "original-x",
                    "original-x",
                    0,
                    G_MAXDOUBLE,
                    0,
                    G_PARAM_READWRITE);
    
    line_chart_props[PROP_ORIGINAL_Y] =
            g_param_spec_double("original-y",
                    "original-y",
                    "original-y",
                    0,
                    G_MAXDOUBLE,
                    0,
                    G_PARAM_READWRITE); 
  
    g_object_class_install_properties(gobject_class,PROP_MAX,line_chart_props);
}

static void
gtk_line_chart_init (GtkLineChart *lineChart)
{
   GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);

   priv->original_x = 0.;
   priv->original_y = 0.;
}

GdkPaintable *
gtk_line_chart_new (double axis_x_range,double axis_y_range)
{
    GtkLineChart *lineChart;

    lineChart = g_object_new (GTK_TYPE_LINE_CHART,
            "x-range",axis_x_range,
            "y-range",axis_y_range,
            NULL);

    return GDK_PAINTABLE (lineChart);
}

绘制效果如下图:

大家肯定会发现设置一样宽度的直线线宽(cairo_set_line_width(cr,80);)但是为什么绘制的直线宽度却不一样???


二、再看Cairo官方API介绍

cairo_set_line_width ()

Sets the current line width within the cairo context. The line width value specifies the diameter of a pen that is circular in user space, (though device-space pen may be an ellipse in general due to scaling/shear/rotation of the CTM).

在cairo上下文中设置当前的线宽度。线宽值指定用户空间圆形笔的直径(尽管由于CTM的缩放/剪切/旋转设备空间笔通常可能是椭圆形)。

       那问题很清晰了,主要是因为我们对于CTM的变换,导致设备空间的画笔变成了椭圆形,从而导致不同角度绘制的直线宽度不一致的问题。


三、修改code实现不修改CTM情况下的坐标变换

       我们这里不使用Cairo提供的CTM变换函数,实现逻辑坐标系到物理坐标系的映射过程,例如如下图:

蓝色部分是我们Gtk应用的窗口,我们绘图的时候Cairo都会将用户空间的坐标转换成设备空间的坐标,默认情况下(不修改CTM)设备空间和用户空间的画笔都是圆形,所以我们这边自定义一个函数,实现用户自定义的坐标空间到应用窗口空间坐标的变换,函数实现如下图:

/* 用户自定义的坐标空间到应用窗口空间坐标的变换函数 */
static void
gtk_line_chart_user_to_device(GtkLineChart *lineChart,
                              double width,double height,
                              double user_x,double user_y,
                              double *device_x , double *device_y)
{
    g_return_if_fail(device_x != NULL);
    g_return_if_fail(device_y != NULL);
    
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);
    
    double x_step = width/priv->axis_x_range;
    double y_step = height/priv->axis_y_range;
 
    *device_x = (priv->original_x+user_x)*x_step;
    *device_y = (priv->original_y-user_y)*y_step;
}

/* 应用窗口空间坐标到用户自定义的坐标空间的变换函数 */
static void
gtk_line_chart_device_to_user(GtkLineChart *lineChart,
                              double width,double height,
                              double device_x,double device_y,
                              double *user_x , double *user_y)
{
    g_return_if_fail(user_x != NULL);
    g_return_if_fail(user_y != NULL);
    
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);
    
    double x_step = priv->axis_x_range/width;
    double y_step = priv->axis_y_range/height;
    
    *user_x = (device_x*x_step)-priv->original_x;
    *user_y = priv->original_y-(device_y*y_step);
}

修改上述code,实现不同角度下画出的线条长度都一致,

......

void
gtk_line_chart_snapshot (GdkPaintable *paintable,
                         GdkSnapshot  *snapshot,
                         double        width,
                         double        height)
{
	cairo_t *cr;
    double device_x=0.;
    double device_y=0.;
    double user_x=0.;
    double user_y=0.;
    GtkLineChart *lineChart = GTK_LINE_CHART(paintable);
    GtkLineChartPrivate *priv = gtk_line_chart_get_instance_private(lineChart);

    gtk_snapshot_append_color (snapshot,
            &priv->bg_color,
            &GRAPHENE_RECT_INIT (0, 0, width, height));
    
    cr = gtk_snapshot_append_cairo (snapshot,
            &GRAPHENE_RECT_INIT (0,0,width, height));
    
    
    cairo_set_line_width(cr,20);
   
    gtk_line_chart_user_to_device(lineChart, width, height,
            -priv->original_x,
            0.,
            &device_x , &device_y);
    cairo_move_to(cr,device_x,device_y);
    
    gtk_line_chart_user_to_device(lineChart, width, height,
            priv->axis_x_range-priv->original_x,
            0.,
            &device_x , &device_y);
    cairo_line_to(cr,device_x,device_y);
    cairo_stroke(cr);

    gtk_line_chart_user_to_device(lineChart, width, height,
            -priv->original_x,
            priv->original_y-priv->axis_y_range,
            &device_x , &device_y);
    cairo_move_to(cr,device_x,device_y);
    
    gtk_line_chart_user_to_device(lineChart, width, height,
            priv->axis_x_range-priv->original_x,priv->original_y,&device_x , &device_y);
    cairo_line_to(cr,device_x,device_y);
    cairo_stroke(cr);

	cairo_destroy (cr);
}
......

效果图如下(没有对锯齿进行优化):


以上是关于为什么我的cairo画出的直线不同角度宽度不同???的主要内容,如果未能解决你的问题,请参考以下文章

团队项目:状态图

团队项目:状态图更新

matlab中画直线用啥函数

R语言画图,根据正负值画不同颜色,并且画水平线或者垂直线

为啥三角形内角和是180度?

设置matlab画出的figure图像导出图片的大小