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>[EMAIL PROTECTED]</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: -->
_______________________________________________ gtk-devel-list mailing list gtk-devel-list@gnome.org http://mail.gnome.org/mailman/listinfo/gtk-devel-list