Re: Deprecate GdkGC?
- From: Federico Mena Quintero <federico ximian com>
- To: milosz derezynski <internalerror gmail com>
- Cc: gtk-devel-list gnome org, Murray Cumming <murrayc murrayc com>
- Subject: Re: Deprecate GdkGC?
- Date: Mon, 05 Dec 2005 19:39:30 -0600
On Sat, 2005-12-03 at 03:28 +0100, milosz derezynski wrote:
> http://beep-media-player.org/~mderezynski/cairo.txt
Fantastic. I've started adding DocBook markup to this, and will commit
it to the GTK+ docs when I'm done with that.
Milosz, if you make changes to your text, could you please forward the
diffs to me? That will make it easy to integrate the changes into the
marked-up version.
Thanks for writing this up!
Federico
<chapter id="gtk-migrating-Cairo">
<chapterinfo>
<author>
<firstname>Milosz</firstname>
<surname>Derezynski</surname>
<affiliation>
<address>
<email>internalerror gmail com</email>
</address>
</affiliation>
</author>
</chapterinfo>
<title>Migrating from the GDK drawing functions to Cairo</title>
<para>
Since version 2.8, GTK+ uses the <ulink url="http://www.cairographics.org">Cairo</ulink> library as its preferred drawing backend,
in contrast to the the older drawing functions in <acronym>GDK</acronym> (GTK+ Drawing Kit).
</para>
<para>
When running GTK+ under X11, GDK is tightly based on the
Xlib drawing API. X11 core graphics are very poor, and entirely pixel
based. For example, at the time X11 was designed,
the designers attempted to follow the specification for wide lines in the
Adobe "Red Book"<footnote>
<para>
FIXME: bibliography
</para>
</footnote>. But that specification
turned out to not be what Adobe actually implemented; it is computationally
very difficult. Moreover, it has extremely ugly results, producing "lumpy
lines", rendering wide lines essentially useless to applications.
</para>
<para>
Even in the X11 design meetings, there was an intent to augment the core 2D graphics for X, but it was
not expected this would take 15 years!
</para>
<para>
In other windowing systems, GDK uses the system's default drawing
functions. These are generally similar to the Xlib drawing functions:
they provide non-antialiased primitives, and have very little or no support
for sophisticated drawing operations such as affine transformations or
using a transparency channel.
</para>
<para>
Cairo has a number of advantages:
</para>
<itemizedlist>
<listitem>
<para>
It is device-independent. Cairo can render to Xlib drawables or the
windows on other windowing systems, or
client-side offscreen buffers. Additionally, support is planned for
rendering to PostScript and PDF documents, as well as using hardware
acceleration through OpenGL.
</para>
</listitem>
<listitem>
<para>
Being able to render to Postscript and/or PDF documents also makes it very easy to use Cairo to
render documents for printing. GTK+ will have a printing API in the
future, with the graphics primitives based on Cairo.
</para>
</listitem>
<listitem>
<para>
Cairo is designed to produce consistent output on all output media while taking advantage of
display hardware acceleration when available (eg. through the X Render Extension).
</para>
</listitem>
<listitem>
<para>
Cairo supports antialiasing, transparency channels, gradients, affine
transformations, and features that GDK simply doesn't have as it is
basically a wrapper over Xlib.
</para>
</listitem>
</itemizedlist>
<!-- Below this point, nothing is marked up yet -->
2. GDK drawing primitives+GdkGC vs. Cairo+GdkCairo
2.1 GdkGC
GDK uses GdkGCs. A GdkGC is a Graphics Context. A Graphics Context holds information about the current
foreground color, background color, a clipping mask, a current set font, basically, it holds values
that will or rather might be used for the next drawing operation using this particular GdkGC; "might",
because not all operations need all GdkGC values, e.g. a line draw doesn't need the font information
that the GdkGC has stored as one of it's values.
This fact exactly is the negative of a GdkGC: the fact that it is not an atomic object. It holds information
about a line cap style, about a font and a clipping mask, but not all of them are used in all of your
drawing operations, and hence you are forced to either constantly modify the GC, or create new GdkGCs
that fit the values for your next drawing operation.
2.2 Cairo
With Cairo, a concept such as a GC does not exist. You set the values like the color or pattern, line
cap and join styles, or a clipping mask before the next drawing operation.
While this might at the very first glance look like a drawback, because you can just re-use a GC for
subsequent drawing operation with the same parameters, it actually proves more efficient to hold these
particular values inside some part of your application that you could call "graphics management" (just
to coin a name), and acquire them trough your internal API calls before doing a Cairo drawing operation.
This might be for example a specific color palette. Please don't confuse this with the term
"palette" conventionally used with images/pixmaps; here i simply mean a given set of colors that you use
troughout your app to draw, and you store them somewhere and make them accessible in some kind of
data structure or small abstraction API.
3. GdkCairo
GdkCairo is an auxilliary introduced in GTK+ 2.8 which makes it possible to use cairo operations
directly to draw on GdkDrawables (GdkWindows and GdkPixmaps [GdkBitmaps?]).
When having a GdkDrawable, drawing with Cairo on it using GdkCairo is as easy as:
...
cairo_t *cr;
cr = gdk_cairo_create (drawable);
(proceed with cairo drawing operations)
cairo_destroy (cr);
...
Note at this point that:
* Creating a cairo context isn't particular expensive and is suitable for e.g.
re-creating one inside every expose handler call, for example.
* Once you have acquired the Cairo context, you use the normal set of cairo operations with it to draw
onto the drawable, so don't be surprised if no calls to GDK will appear in the examples that show how
to accomplish a certain drawing operation with Cairo instead of with GDK.
Also note, for those who are not familiar with this concept, that drawing onto a GtkWidget's GdkWindow
(which is a GdkDrawable) must *only* happen inside the expose handler's processing queue ("processing
queue" as there might be several handlers connected in a row attached to expose-event).
Drawing onto off-screen drawables such as GdkPixmap can happen at any time though. Basically, switching
to GdkCairo/Cairo constitutes no change in this regard.
4. gdk_draw_*() operations and equivalents using GdkCairo/Cairo
In this chapter, we will not be working with complete function examples, as there is basically no
difference where or when you draw onto a drawable using GdkCairo with Cairo, but merely show code
sequences that constitute an equivalent of a particular gdk_draw_*() operation.
You can find fully fledged code examples below in the examples section.
Also at this point there is one important note to make: Cairo provides antialiasing by default, while
the gdk_draw_*() operations do not antialias and do not provide such a facility.
In case you want to exactly emulate the drawing of the GDK drawing API, you have to turn off antialiasing
temporarily (or persistantly) for a particular Cairo context, which works like this:
...
cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
...
Don't worry for now too much about it as we're going to come back later to this in the specific parts
covering Cairo drawing equivalents of GDK drawing API operations.
NOTE: In all examples, we assume the following:
* A GdkDrawable which we will refer to by the variable name 'drawable'
* A Cairo context that you have acquired like this:
cr = gdk_cairo_create (GdkDrawable *drawable);
and to which we will refer by the variable name 'cr'.
* A GdkGC that you have created the usual way, or gotten trough the widget's GtkStyle, but as it is
not of importance here, we're merely going to refer to it by the variable name 'gc', and not go into
details as on how to create or acquire it (please consult GDK docs if you are really still interested
in that :P)
4.2 Important notes regarding data types used in GDK and in Cairo
4.2.1 Coordinate pairs
While GDK specifies coordinates in integers (int or gint),
Cairo requires them as double-precision floating point variables (of type double or gdouble).
4.3 Statefulness vs. statelessness
In GDK, a drawing operation is stateless, that means once you have finished e.g. gdk_draw_line(),
it will be executed immediately and the line is immediately drawn.
With Cairo, drawing acts like a canvas which keeps a state. That is, you perform various drawing
operations, but the result is not immediately drawn onto the target surface (GdkDrawable in this
case), but you rather need to run cairo_stroke (), cairo_fill () or cairo_paint () whenever you
are done with drawing on the canvas.
We're going to come back to this later, please read the examples first (for the interested, you
might jump to that section right now though and just read it, it won't hurt :P)
4.4 GDK drawing operation equivalents done with Cairo
4.4.1 gdk_draw_line
4.4.1.1 gdk_draw_line, simple
GDK:
gint x_start,
y_start,
x_end,
y_end;
...
gdk_draw_line (drawable,
gc,
x_start,
y_start,
x_end,
y_end);
Cairo:
double x_start,
y_start,
x_end,
y_end;
...
cairo_move_to (cr,
x_start,
y_start);
cairo_line_to (cr,
x_end,
y_end);
cairo_stroke (cr);
4.4.1.2 gdk_draw_line, elaborate
Let's assume the GdkGC used in the GDK operation had set certain parameters for drawing the line
before the actual operation
GDK:
gdk_gc_set_line_attributes (gc,
1, /* line width */
GDK_LINE_SOLID, /* GdkLineStyle */
GDK_CAP_ROUND, /* GdkCapStyle */
GDK_JOIN_ROUND); /* GdkJoinStyle */
gdk_draw_line (drawable,
gc,
x_start,
y_start,
x_end,
y_end);
Cairo:
cairo_move_to (cr,
x_start,
y_start);
cairo_line_to (cr,
x_end,
y_end);
cairo_set_line_width (cr, 1.0); /* line width */
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); /* line cap, one of _CAP_ROUND, _CAP_BUTT, _CAP_SQUARE */
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); /* line join style, one of _JOIN_ROUND, _JOIN_MITER, _JOIN_BEVEL */
<!--BREAK!
Now for the line style, which in GDK specifies the dashing style (GdkLineStyle, one of GDK_LINE_SOLID,
GDK_LINE_ON_OFF_DASH, GDK_LINE_DOUBLE_DASH), we have much more loose control over with Cairo.
With cairo, we can specify the dashing custom-wise with cairo_set_dash (), but since the control is very loose,
the API isn't very trivial as you need to create an array of double type values, and use it in cairo_set_dash();
Please consult this for on how to use cairo_set_dash(), i would be only repeating it here:
http://www.cairographics.org/manual/cairo-cairo-t.html#id2646765
!BREAK-->
cairo_stroke (cr);
4.4.1.3 gdk_draw_line, the missing factor
Now something was missing there, right? Yes indeed, the color! You want to color your drawings, so here's how
you have to proceed, again as two examples, one with GDK and the other one using Cairo.
We have split this out of the two examples above because setting the color for a Cairo drawing operation is always
the same, as it is for GDK. It is also explained in a more generic and reference-like fashion in the reference section below,
but we don't want to leave our example unfinished.
So, here is the gdk_draw_line simple example again, this time setting a color on the line explicitely (in the previous code,
4.4.1.1, you could have assumed the color on the GdkGC was already set before that, but that doesn't work out quite like that with Cairo)
GDK:
gint x_start,
y_start,
x_end,
y_end;
GdkColor color;
...
color->red = 0;
color->green = 0;
color->blue = 65535;
<!--BREAK!
We draw a purely blue line. Colors with GdkColor are specified in 16bits resolution per sample [FIXME: is this the correct term, 'sample'?]
!BREAK-->
gdk_gc_set_rgb_fg_color (gc, &color);
<!--BREAK
Also what needs to be noted here that you can handle GdkColors with a GC in 2 ways: allocated colors and unallocated ones. For details on this
i'd like to redirect you once again to GDK documentation. For the sake of simplicity we use an unallocated color and use _set_rgb_fg_color ()
!BREAK-->
gdk_draw_line (drawable,
gc,
x_start,
y_start,
x_end,
y_end);
Cairo:
double x_start,
y_start,
x_end,
y_end;
...
cairo_move_to (cr,
x_start,
y_start);
cairo_line_to (cr,
x_end,
y_end);
cairo_set_source_rgb (cr, 0.0 /*red*/, 0.0 /*green*/, 1.0 /*blue*/);
cairo_stroke (cr);
The interesting part here is of course cairo_set_source_rgb(). It sets the color (what is also possible is to set a pattern
or a gradient, or an RGBA color, but.. later) on the _source_.
The source is whatever you have drawn after the last drawing/flushing operation. That is, you run a cairo_stroke (), or
cairo_fill () or cairo_paint (), which executes the drawing operations and clears the state and "unsets" all sources.
[FIXME: Someone please correct this to be somewhat more technically correct sounding]
Here's another example to show what this actually means, this time Cairo only:
Cairo:
double x_start,
y_start,
x_end,
y_end;
...
x_start = 0.0;
y_start = 0.0;
x_end = 50.0;
y_end = 0.0;
cairo_move_to (cr,
x_start,
y_start);
cairo_line_to (cr,
x_end,
y_end);
cairo_set_source_rgb (cr, 0.0 /*red*/, 0.0 /*green*/, 1.0 /*blue*/);
cairo_stroke (cr);
...
x_start = 50.0;
y_start = 0.0;
x_end = 100.0;
y_end = 0.0;
cairo_move_to (cr,
x_start,
y_start);
cairo_line_to (cr,
x_end,
y_end);
cairo_set_source_rgb (cr, 1.0 /*red*/, 0.0 /*green*/, 0.0 /*blue*/);
cairo_stroke (cr);
Now we have in effect (visibly) a line that stretches 100 pixels wide, and is blue in the first half, and red in the second half.
</chapter>
<!--
Local variables:
mode: sgml
sgml-parent-document: ("gtk-docs.sgml" "book" "part" "chapter")
End:
-->
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]