Re: Deprecate GdkGC?



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]