为什么我的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画出的直线不同角度宽度不同???的主要内容,如果未能解决你的问题,请参考以下文章