Cairo problem

116 views
Skip to first unread message

david allen

unread,
Jul 27, 2015, 12:22:27 PM7/27/15
to fltk.general
Some time ago, Ian posted a class derived from Fl_Box that displayed a Cairo graphic. In the demo program he created three instances of the class in nested groups. The code below is derived from his program in an effort to suite my sense of aesthetics. My intent was not to alter the functionality. However, things have gone wrong. The figures are clipped and/or translated. The second figure does not appear until the window is resized horizontally. The third figure does not appear at all.  Help appreciated!

/***** Start cut Makefile *****
INCLUDES    = `fltk-config --use-images --cxxflags`\
              `pkg-config --cflags cairo`
LIBRARY     = `fltk-config --use-images --ldstaticflags`\
              `pkg-config --libs cairo`
TARGET      =  triangle
.PHONY   : clean

${TARGET} : triangle.o
    g++ -g  $^ ${LIBRARY}  -o $@

triangle.o : triangle.cpp
    g++ -g -Wall ${INCLUDES} -c $< -o $@

clean:
    rm *.o ${TARGET}
****** End cut Makefile   *****/

/* Derived from Ian's demo of a simple CairoBox in a (nested) group */
#include <FL/Fl.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/x.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Button.H>

#include <cairo.h>

#ifdef WIN32
#  include <cairo-win32.h>
#elif defined (__APPLE__)
#  include <cairo-quartz.h>
#else
#  include <cairo-xlib.h>
#endif

Fl_Double_Window *main_win=(Fl_Double_Window *)0;
Fl_Group *outer_group=(Fl_Group *)0;
Fl_Group *inner_grp1 =(Fl_Group *)0;
Fl_Group *inner_grp2 =(Fl_Group *)0;
Fl_Group *inner_grp3 =(Fl_Group *)0;

/*************************************************************/
class CairoBox : public Fl_Box
  {
  void draw(void); // draw method
  cairo_surface_t *surface;
  cairo_t         *cairo_context;
  float rr, gg, bb, aa;
  void set_cairo_cxt(int wo, int ho);
  public:
// constructor
  CairoBox(int x, int y, int w, int h) : Fl_Box(x,y,w,h)
    {
    surface       = NULL;
    cairo_context = NULL;
    rr = gg = bb = aa = 0.0;
    }

  void set_rgb(float RR, float GG, float BB, float AA)
    {
    rr = RR;
    gg = GG;
    bb = BB;
    aa = AA;
    }
  };

// In Linux, this is wrapper for cairo_xlib_surface_create
void CairoBox::set_cairo_cxt(int wo, int ho)
  {
#ifdef WIN32
#warning win32 mode
  /* Get a Cairo surface for the current DC */
  HDC dc = fl_gc;         /* Exported by fltk */
  surface = cairo_win32_surface_create(dc);
#elif defined (__APPLE__)
#warning Apple Quartz mode
  /* Get a Cairo surface for the current CG context */
  CGContext *ctx = fl_gc;
  surface = cairo_quartz_surface_create_for_cg_context(ctx, wo, ho);
#else
#warning X windows mode
  /* Get a Cairo surface for the current display */
  surface = cairo_xlib_surface_create(fl_display, fl_window, fl_visual->visual, wo, ho);
#endif
  /* Store the cairo context */
  cairo_context = cairo_create(surface);

  /* All Cairo co-ordinates are shifted by 0.5 pixels to correct anti-aliasing */
  cairo_translate(cairo_context, 0.5, 0.5);
  }

void CairoBox::draw(void)
  {
  int xo = x(); // origin is current window position for Fl_Box
  int yo = y();
  int wo = w();
  int ho = h();

  /* draw the base widget */
  fl_push_no_clip();            // remove any clipping region set
  fl_push_clip(xo, yo, wo, ho); // reset local clipping

  fl_color(FL_CYAN);
  fl_rectf(xo, yo, wo, ho);
  // set cairo context
  set_cairo_cxt(wo, ho);
  // draw a triangle pointing upwards
  int x1, y1, x2, y2, x3, y3;
  x1 = xo + wo/2;       // top center
  y1 = yo;
  x2 = xo + wo;         // bottom right
  y2 = yo + ho;
  x3 = xo;              // bottom left
  y3 = y2;
  cairo_set_source_rgba(cairo_context, rr, gg, bb, aa);
  cairo_new_path(cairo_context);
  cairo_move_to(cairo_context, x1, y1);
  cairo_line_to(cairo_context, x2, y2);
  cairo_line_to(cairo_context, x3, y3);
  cairo_line_to(cairo_context, x1, y1);
  cairo_fill(cairo_context);
  // release the cairo context
  cairo_destroy(cairo_context);
  cairo_surface_destroy(surface);
  cairo_context = NULL;
  fl_pop_clip(); // remove the local clip region
  fl_pop_clip(); // remove the "no_clip" region
  } // draw method

CairoBox *cairo1 = (CairoBox *)0;
CairoBox *cairo2 = (CairoBox *)0;
CairoBox *cairo3 = (CairoBox *)0;

const int w = 150;  // width of CairoBox
const int h = 450;  // height of CairoBox
const int s =  10;  // space around elements

int main(int argc, char **argv)
  {
  int width  = 12*s+3*w;
  int height =  4*s+h;
  main_win   = new Fl_Double_Window(width, height);
  main_win->begin();

  int l = s;
  outer_group = new Fl_Group(l, s, width-2*s, height-2*s);
  outer_group->begin();

  inner_grp1 = new Fl_Group(l+s, 2*s, w+2*s, h+2*s);
  inner_grp1->begin();
    cairo1 = new CairoBox(l+2*s, 3*s, w,     h);
    cairo1->box(FL_FLAT_BOX);
    cairo1->set_rgb(1.0, 0, 0, 0.7);
  inner_grp1->end();
  inner_grp1->box(FL_DOWN_BOX);
  inner_grp1->resizable(cairo1);
 
  l += w+3*s;
  inner_grp2 = new Fl_Group(l+s, 2*s, w+2*s, h+2*s);
  inner_grp2->begin();
    cairo2 = new CairoBox(l+2*s, 3*s, w,     h);
    cairo2->box(FL_FLAT_BOX);
    cairo2->set_rgb(0, 1.0, 0, 0.9);
  inner_grp2->end();
  inner_grp2->box(FL_DOWN_BOX);
  inner_grp2->resizable(cairo2);

  l += w+3*s;
  inner_grp3 = new Fl_Group(l+s, 2*s, w+2*s, h+2*s);
  inner_grp3->begin();
    cairo3 = new CairoBox(l+2*s, 3*s, w, h);
    cairo3->box(FL_FLAT_BOX);
    cairo3->set_rgb(0, 0, 1.0, 0.3);
  inner_grp3->end();
  inner_grp3->box(FL_DOWN_BOX);
  inner_grp3->resizable(cairo3);

  outer_group->end();
  outer_group->resizable(inner_grp2);
  main_win->end();
  outer_group->resizable(inner_grp2);
  main_win->resizable(outer_group);
  main_win->label("Cairo Box Test");
  main_win->show(argc, argv);

  return Fl::run();
  }
/* end of file */

MacArthur, Ian (Selex ES, UK)

unread,
Jul 27, 2015, 12:44:56 PM7/27/15
to fltkg...@googlegroups.com
What platform?

Runs OK for me on Win7 - though some oddness when resizing, so I just commented out all the:

widget->resizable(whatever);

calls in the code and then it seemed even more fine...

I couldn't be bothered to figure out if the values computed from the programmatic widget sizing arithmetic looked sensible, but the code runs fine for me.




Selex ES Ltd
Registered Office: Sigma House, Christopher Martin Road, Basildon, Essex SS14 3EL
A company registered in England & Wales. Company no. 02426132
********************************************************************
This email and any attachments are confidential to the intended
recipient and may also be privileged. If you are not the intended
recipient please delete it from your system and notify the sender.
You should not copy it or use it for any purpose nor disclose or
distribute its contents to any other person.
********************************************************************

david allen

unread,
Jul 27, 2015, 12:58:34 PM7/27/15
to fltk.general, ian.ma...@selex-es.com
Debian 8.1, 64 bit

chris

unread,
Jul 27, 2015, 1:09:17 PM7/27/15
to fltkg...@googlegroups.com
Hi,

I also had played with Ian's program that time (Ubuntu 14.04) and the
effect was as you describe it.

Did some refactoring then and was able to make it work. I also extended
it to display other things (button: Mode).

Here's the version I created that time.

Cheers,
Chris
cairo_demo.cxx

david allen

unread,
Jul 27, 2015, 3:37:45 PM7/27/15
to fltk.general, wc...@gmx.net
Thanks Chris, Your version works perfectly for me. Now to study it! David

Greg Ercolano

unread,
Jul 27, 2015, 11:39:21 PM7/27/15
to fltkg...@googlegroups.com
On 07/27/15 09:22, david allen wrote:
> /* Derived from Ian's demo of a simple CairoBox in a (nested) group */
> #include <FL/Fl.H>
> [..]

I made these changes to your original program, and it seemed
to work for me (comments follow the code). I *only* tested on linux.
______________________________________________________________________

--- triangle.cxx.orig 2015-07-27 20:21:37.026579602 -0700
+++ triangle.cxx 2015-07-27 20:20:48.731579643 -0700
@@ -30,7 +30,7 @@
cairo_surface_t *surface;
cairo_t *cairo_context;
float rr, gg, bb, aa;
- void set_cairo_cxt(int wo, int ho);
+ void set_cairo_cxt(int X,int Y,int wo, int ho);
public:
// constructor
CairoBox(int x, int y, int w, int h) : Fl_Box(x,y,w,h)
@@ -50,7 +50,7 @@
};

// In Linux, this is wrapper for cairo_xlib_surface_create
-void CairoBox::set_cairo_cxt(int wo, int ho)
+void CairoBox::set_cairo_cxt(int X, int Y, int wo, int ho)
{
#ifdef WIN32
#warning win32 mode
@@ -61,11 +61,11 @@
#warning Apple Quartz mode
/* Get a Cairo surface for the current CG context */
CGContext *ctx = fl_gc;
- surface = cairo_quartz_surface_create_for_cg_context(ctx, wo, ho);
+ surface = cairo_quartz_surface_create_for_cg_context(ctx, wo, ho); // ERCO: might need X+wo,Y+wo too?
#else
#warning X windows mode
/* Get a Cairo surface for the current display */
- surface = cairo_xlib_surface_create(fl_display, fl_window, fl_visual->visual, wo, ho);
+ surface = cairo_xlib_surface_create(fl_display, fl_window, fl_visual->visual, X+wo, Y+ho); // ERCO: included X,Y
#endif
/* Store the cairo context */
cairo_context = cairo_create(surface);
@@ -88,7 +88,7 @@
fl_color(FL_CYAN);
fl_rectf(xo, yo, wo, ho);
// set cairo context
- set_cairo_cxt(wo, ho);
+ set_cairo_cxt(x(), y(), wo, ho);
// draw a triangle pointing upwards
int x1, y1, x2, y2, x3, y3;
x1 = xo + wo/2; // top center
@@ -171,4 +171,3 @@
return Fl::run();
}
/* end of file */
______________________________________________________________________


I focused on the set_cairo_cxt() function.

Looking at the behavior, it seemed the cairo drawing code
was confused about where the x,y origin for the middle
and right boxes were for drawing.

Seemed like the set_cairo_cxt() function was just taking
a width and height without sense of x and y.

Added what I /thought/ might be right: including FLTK's x/y
values into the widths passed to cairo's context function
so that they're relative to fltk's window, not the box.

I did not check cairo's docs, but based on the behavior alone,
that seemed to be right. Note my comment regarding the OSX code,
a change I didn't make, but might be needed as well.


MacArthur, Ian (Selex ES, UK)

unread,
Jul 28, 2015, 7:13:14 AM7/28/15
to fltkg...@googlegroups.com
> I made these changes to your original program, and it seemed
> to work for me (comments follow the code). I *only* tested on
> linux.


This is interesting - I was "sure" my example worked on Linux, but testing on the F17 VM I have to hand, it does indeed mess up...

As to why, I *think* the crux is how I am creating cairo_xlib_surface_create(...) here.

Analysis follows, quite possibly flawed!

Note that in Chris's example (very nice, BTW), he derived the cairo_xlib_surface_create() from the main window, and it is sized to fit the main window.

However, in my code I am setting the cairo_xlib_surface_create() based on the dimensions of the derived Fl_Box, but still based on the main window.

So the "origin" of this surface is that of the window, but the width/height is based on the box.

And so the created surface is "too small" or "in the wrong place" for the drawing I want to do, and a nasty mess ensues.

I think Greg's fix works because it forces the created surface to be "big enough" and then it works again. Maybe. Something like that.

I offer an alternate solution below, based on my analysis here, that appears to be working nicely on my F17 VM and on Win7. I do not have a Mac to hand, will test later.


> Note my comment regarding the OSX code,
> a change I didn't make, but might be needed as well.

I think I tested this on OSX and it was OK as-is. But then I *thought* the Linux variant was OK...


Here's the tweaked version of my code, which seems good so far...

--------------------------------------

/* Demo of a simple cairo_box in a group */

#include <FL/Fl.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/x.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Button.H>

#include <cairo.h>

#ifdef WIN32
# include <cairo-win32.h>
#elif defined (__APPLE__)
# include <cairo-quartz.h>
#else
# include <cairo-xlib.h>
#endif

Fl_Double_Window *main_win=(Fl_Double_Window *)0;
Fl_Group *outer_group=(Fl_Group *)0;
Fl_Group *inner_grp1=(Fl_Group *)0;
Fl_Group *inner_grp2=(Fl_Group *)0;
Fl_Group *inner_grp3=(Fl_Group *)0;

/*************************************************************/
class cairo_box : public Fl_Box {
void draw(void); // draw method
cairo_surface_t *surface;
cairo_t *cairo_context;
float rr, gg, bb, aa;
void set_cairo_cxt(int wo, int ho);
public:
// constructor
cairo_box(int x, int y, int w, int h) : Fl_Box(x,y,w,h) {
surface = NULL; cairo_context = NULL;
rr = gg = bb = aa = 0.0;}

void set_rgb(float RR, float GG, float BB, float AA)
{rr = RR; gg = GG; bb = BB; aa = AA;}
};
/*************************************************************/
void cairo_box::set_cairo_cxt(int wo, int ho) {
#ifdef WIN32
#warning win32 mode
/* Get a Cairo surface for the current DC */
HDC dc = fl_gc; /* Exported by fltk */
surface = cairo_win32_surface_create(dc);
#elif defined (__APPLE__)
#warning Apple Quartz mode
/* Get a Cairo surface for the current CG context */
CGContext *ctx = fl_gc;
surface = cairo_quartz_surface_create_for_cg_context(ctx, wo, ho);
#else
#warning X windows mode
// find the window enclosing this widget - probably main_win in this case!
Fl_Group *pt = this->parent();
while ((pt) && !(pt->as_window()))
{
pt = pt->parent();
}
if (!pt)
{
puts("No window context");
return;
}
/* Get a Cairo surface for the box, based on the enclosing window */
surface = cairo_xlib_surface_create(fl_display, fl_window, fl_visual->visual, pt->w(), pt->h());
#endif
/* Store the cairo context */
cairo_context = cairo_create(surface);

/* All Cairo co-ordinates are shifted by 0.5 pixels to correct anti-aliasing */
#ifndef __APPLE__ // Apple Quartz does not need the offset nudge, it seems...
cairo_translate(cairo_context, 0.5, 0.5);
#endif

}
/*************************************************************/
void cairo_box::draw(void) {
int xo = x(); // origin is current window position for Fl_Box
int yo = y();
int wo = w();
int ho = h();

/* draw the base widget */
fl_push_no_clip(); /* remove any clipping region set by the expose events... */
fl_push_clip(xo, yo, wo, ho); /* reset local clipping */

fl_color(FL_CYAN);
fl_rectf(xo, yo, wo, ho);
// set cairo context
set_cairo_cxt(wo, ho);

// draw a triangle pointing upwards
int x1, y1, x2, y2, x3, y3;
x1 = xo + wo/2; y1 = yo;
x2 = xo + wo; y2 = yo + ho;
x3 = xo; y3 = y2;
cairo_set_source_rgba(cairo_context, rr, gg, bb, aa);
cairo_new_path(cairo_context);
cairo_move_to(cairo_context, x1, y1);
cairo_line_to(cairo_context, x2, y2);
cairo_line_to(cairo_context, x3, y3);
cairo_line_to(cairo_context, x1, y1);
cairo_fill(cairo_context);

// release the cairo context
cairo_destroy(cairo_context);
cairo_surface_destroy(surface);
cairo_context = NULL;

fl_pop_clip(); // remove the local clip region
fl_pop_clip(); // remove the "no_clip" region
} // draw method

/*************************************************************/
cairo_box *cairo1=(cairo_box *)0;
cairo_box *cairo2=(cairo_box *)0;
cairo_box *cairo3=(cairo_box *)0;
/*************************************************************/
static void cb_Quit(Fl_Button*, void*) {
main_win->hide();
}

/*************************************************************/
int main(int argc, char **argv) {
main_win = new Fl_Double_Window(500, 500);
main_win->begin();

Fl_Button* o = new Fl_Button(430, 460, 60, 30, "Quit");
o->box(FL_THIN_UP_BOX);
o->callback((Fl_Callback*)cb_Quit);

outer_group = new Fl_Group(5, 5, 490, 450);
outer_group->begin();

inner_grp1 = new Fl_Group(5, 5, 160, 450);
inner_grp1->begin();
cairo1 = new cairo_box(7, 7, 156, 446);
cairo1->box(FL_FLAT_BOX);
cairo1->set_rgb(1.0,0,0,0.7);
inner_grp1->end();
inner_grp1->box(FL_DOWN_BOX);

inner_grp2 = new Fl_Group(165, 5, 160, 450);
inner_grp2->begin();
cairo2 = new cairo_box(167, 7, 156, 446);
cairo2->box(FL_FLAT_BOX);
cairo2->set_rgb(0,1.0,0,0.9);
inner_grp2->end();
inner_grp2->box(FL_DOWN_BOX);

inner_grp3 = new Fl_Group(325, 5, 170, 450);
inner_grp3->begin();
cairo3 = new cairo_box(327, 7, 166, 446);
cairo3->box(FL_FLAT_BOX);
cairo3->set_rgb(0,0,1.0, 0.3);
inner_grp3->end();
inner_grp3->box(FL_DOWN_BOX);

outer_group->end();

main_win->end();
main_win->resizable(outer_group);
main_win->label("Cairo Box Test");
main_win->show(argc, argv);

return Fl::run();
}

/* end of file */



Ian MacArthur

unread,
Jul 28, 2015, 4:56:08 PM7/28/15
to fltkg...@googlegroups.com
On Tue Jul 28 2015 12:13:09, MacArthur, Ian (Selex ES, UK) wrote:
>
>
>> Note my comment regarding the OSX code,
>> a change I didn't make, but might be needed as well.
>
> I think I tested this on OSX and it was OK as-is. But then I *thought* the Linux variant was OK...


Tested my code on OSX, and it seems to be fine, so I guess that means that Greg’s proposed tweak is not necessary.

Cheers,
--
Ian






Greg Ercolano

unread,
Jul 28, 2015, 5:34:32 PM7/28/15
to fltkg...@googlegroups.com
Hey, wanna add one of these cairo examples to the fltk/examples directory?

It's much easier to add code to examples than test, since you don't
have to make IDE files for it. (Just tweak the Makefile)

david allen

unread,
May 17, 2017, 11:47:15 AM5/17/17
to fltk.general, wc...@gmx.net
This program has been an excellent learning medium for me. I have successfully taken elements of it for my own programs.  However, one statement puzzles me:

    CairoWindow * cw = static_cast<CairoWindow *>(window());

The object of the cast , window(), does not seem to be anywhere in the program. Can you explain what is going on?



On Monday, July 27, 2015 at 1:09:17 PM UTC-4, chris wrote:
On Monday, July 27, 2015 at 1:09:17 PM UTC-4, chris wrote:

Albrecht Schlosser

unread,
May 17, 2017, 1:31:20 PM5/17/17
to fltkg...@googlegroups.com
On 17.05.2017 17:47 david allen wrote:
> This program has been an excellent learning medium for me. I have
> successfully taken elements of it for my own programs. However, one
> statement puzzles me:
>
> CairoWindow * cw = static_cast<CairoWindow *>(window());
>
> The object of the cast , window(), does not seem to be anywhere in the
> program. Can you explain what is going on?
>
>
> On Monday, July 27, 2015 at 1:09:17 PM UTC-4, chris wrote:

Checked Chris' post for context ...

window() is a method of Fl_Widget, and it is used inside

void cairo_box::draw(void) { .. }

so I guess it's getting the (directly) including, i.e. the parent window
of the cairo_box widget (which is derived from Fl_Box).

Does this help?


BTW: if your coding conventions and environment allow dynamic_cast I'd
suggest to use dynamic_cast instead, which would assure that you really
get a CairoWindow:

CairoWindow * cw = dynamic_cast<CairoWindow *>(window());
assert( cw );
cairo_t *cr = cw->cairo_context();
...

Greg Ercolano

unread,
May 17, 2017, 2:00:54 PM5/17/17
to fltkg...@googlegroups.com
On 05/17/17 08:47, david allen wrote:
> This program has been an excellent learning medium for me. I have successfully taken elements of it for my own programs. However, one statement puzzles me:
>
> CairoWindow * cw = static_cast<CairoWindow *>(window());
>
> The object of the cast , window(), does not seem to be anywhere in the program. Can you explain what is going on?


You appear to be looking at code inside the void cairo_box::draw(void) method.
cairo_box is a class inheriting from public Fl_Box, an FLTK widget.

The inheritance access to window() for cairo_box is:

cairo_box::Fl_Box::Fl_Widget::window()

So within cairo_box::draw(), all public methods of Fl_Box
are available to that method's code. (And by extension, all
of Fl_Widget's, since Fl_Box derives from public Fl_Widget)

Because of the public inheritance, FLTK widgets provide variety of
useful methods, including window(), which returns a pointer to the
parent Fl_Window. The window() method is declared in FL/Fl_Widget.H,
and implemented in src/Fl_Window.cxx

The assumption in the code you cited is that the parent window
is actually a CairoWindow, which is itself derived from an
Fl_Double_Window. (See FL/Fl_Cairo_Window.H). So, the static_cast
is needed to translate the Fl_Window* -> CairoWindow*, so as to
access all of CairoWindow's methods.

At least I think that's what's going on.

Oh, and I see Albrecht already replied with some extra info
about dynamic_cast.


Allen, David

unread,
May 17, 2017, 2:18:23 PM5/17/17
to fltkg...@googlegroups.com

Thanks so much! I think I understand.


From: fltkg...@googlegroups.com <fltkg...@googlegroups.com> on behalf of Greg Ercolano <er...@seriss.com>
Sent: Wednesday, May 17, 2017 2:00:44 PM
To: fltkg...@googlegroups.com
Subject: Re: [fltk.general] Cairo problem
 
--
You received this message because you are subscribed to the Google Groups "fltk.general" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkgeneral...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages