FLTK Built-in screen shot?

51 views
Skip to first unread message

Rob McDonald

unread,
Oct 29, 2021, 8:14:15 PM10/29/21
to fltk.general
I'm looking to improve some automatic documentation generation capabilities for my program.

I have all my program's windows in a vector<Fl_Double_Window*>.  I would like to traverse that vector, show() each window, and do a screen grab.  Eventually, I'd like to find any groups of tabs, show() every tab and take screen grabs of each.  Ideally, I'd extract a file name from the Window's title and Tab's title, etc.

Is there a built-in screen grab capability in FLTK?  Any suggestions on how to achieve this in as cross-platform a way as possible?

I can handle getting from a buffer of pixels to a file, but I'm not seeing a FLTK way of capturing the buffer.

Any suggestions are appreciated,

Rob


Rob McDonald

unread,
Oct 29, 2021, 8:23:25 PM10/29/21
to fltk.general
A bit more searching (with different keywords) and I found  fl_read_image().  I certainly did not think to look in the 'drawing stuff' part of the docs...

However, I'm not immediately certain what context to run it in such that it knows the current window or off screen buffer.  Does anyone have an example of using fl_read_image to grab a Fl_Window?

Rob


 

Rob McDonald

unread,
Oct 30, 2021, 12:23:37 AM10/30/21
to fltk.general
I eventually got it working with fl_read_image.

For a bit, I was trying to also get fl_capture_window_part to work, but was not able to.

Rob

 

Albrecht Schlosser

unread,
Oct 30, 2021, 9:41:20 AM10/30/21
to fltkg...@googlegroups.com
The recommended way in FLTK 1.4 is to use one of the new "surface" devices, usually Fl_Image_Surface and to draw() the entire window onto that surface. Fl_Copy_Surface is another "interesting" target device. The only "official" way to write directly to an image is Fl_SVG_File_Surface which can be used to write directly to an SVG image file.You can find examples of these (and more) in test/device.cxx. Writing a PNG file is not that difficult but I think you know how to write the image file anyway.

Are you maybe working, or can you do this, on macOS? There should be a button in the system menu of all FLTK applications titled "Print Front Window". On Windows and Unix/X11 you can '#define USE_PRINT_BUTTON' when compiling FLTK and there should be a similar function available: a mini window at the top left of the desktop with buttons "Print front window" and "Copy front window". I'm not sure if this is very helpful though for mass production of screenshots, but anyway.

For testing (if you're using the "copy" function) I recommend using the examples/clipboard demo to show the clipboard when it changes. I believe this should work cross-platform.

What I would likely do is to define a global shortcut key that can be used to trigger the "print" function in your app. If you can create filenames automatically like screen_shot_nnn.png writing all screenshots to files should be a boring but easy task. ;-)

Albrecht Schlosser

unread,
Oct 30, 2021, 10:26:27 AM10/30/21
to fltkg...@googlegroups.com
On 10/30/21 3:41 PM Albrecht Schlosser wrote:

> What I would likely do is to define a global shortcut key that can be
> used to trigger the "print" function in your app...

FWIW: I had an unfinished demo program for a screenshot (copy to
clipboard) lying around. Feel free to use parts of it (attached:
screenshot.cxx).

It's still WIP but you can see what I mean and how it works. If you're
running examples/clipboard(.cxx) in the background you can see the clock
changing with every screenshot (button click or ctrl/alt/command + s).

Have fun!

screenshot.cxx

Rob McDonald

unread,
Oct 30, 2021, 2:13:34 PM10/30/21
to fltk.general
Thanks for the tips Albrecht, I will check those things out.

Right now, I am having trouble doing a for-loop across all tabs in a particular GUI.  Despite calling show(), I get repeated images of the default tab rather than of each one....

How can I programmatically cycle through displaying tabs?

My code does this -- I realize there are parts relating to my larger application, but hopefully it should make enough sense that someone can help  me.

Rob

void TabScreen::ScreenDump()
{
    Fl_Window * win = dynamic_cast<Fl_Window*> (m_FLTK_Window);

    if ( win )
    {
        int wasshown = win->visible();

        Show();   // Ends up calling win->show();

        int w, h, alpha;
        alpha = 0;
        w = win->w();
        h = win->h();

        for ( int itab = 0; itab < ( int )m_TabGroupVec.size(); itab++ )
        {
            Fl_Group* tab = m_TabGroupVec[itab];

            tab->show();
            Fl::wait();

            if ( win->visible() != 0 )
            {
                win->make_current();

                const char *tab_lbl = tab->label();
                char fname[100];
                sprintf( fname, "%s_%s.png", m_Title.c_str(), tab_lbl );

                uchar *p = fl_read_image( NULL, 0, 0, w, h, alpha );
                if( p )
                {
                    stbi_write_png( fname, w, h, 3, p, w * 3 );
                    delete p;
                }
            }
        }
        if ( wasshown == 0 )
        {
            Hide();
        }
    }
}




imm

unread,
Oct 30, 2021, 2:28:59 PM10/30/21
to General FLTK
On Sat, 30 Oct 2021, 19:13 Rob McDonald wrote:
Thanks for the tips Albrecht, I will check those things out.

Right now, I am having trouble doing a for-loop across all tabs in a particular GUI.  Despite calling show(), I get repeated images of the default tab rather than of each one....

How can I programmatically cycle through displaying tabs?

My code does this -- I realize there are parts relating to my larger application, but hopefully it should make enough sense that someone can help  me.

I'm on my phone so can't check, but I think I remember that calling show() may re-order the children (the tabs) of the enclosing tab group... (IIRC the shown tab maybe gets moved to the start of the list)

So your loop is probably getting messed about by that. Maybe show the last child tab on each iteration or something? Dunno, best I've got off the cuff!

--
Ian
From my Fairphone FP3

Albrecht Schlosser

unread,
Oct 30, 2021, 2:36:54 PM10/30/21
to fltkg...@googlegroups.com
On 10/30/21 8:13 PM Rob McDonald wrote:
> Thanks for the tips Albrecht, I will check those things out.
>
> Right now, I am having trouble doing a for-loop across all tabs in a
> particular GUI.  Despite calling show(), I get repeated images of the
> default tab rather than of each one....

Looking at your code below I believe that my proposals would fix your
issues and I strongly recommend to change your code before you waste
time pursuing your current strategy. I'll explain the details below.

> How can I programmatically cycle through displaying tabs?

Independent of your code below, if 'tabs' is the Fl_Tabs widget, you
would show() all individual children as you probably tried with your
m_TabGroupVec (whose contents I don't know).

A simple loop would be like:

int nc = tabs->children();
for (int i = 0; i< nc; i++) {
  tabs->child(i)->show();
}

But that alone doesn't solve your problem ...

> My code does this -- I realize there are parts relating to my larger
> application, but hopefully it should make enough sense that someone
> can help  me.
>

[some code removed]

>         for ( int itab = 0; itab < ( int )m_TabGroupVec.size(); itab++ )
>         {
>             Fl_Group* tab = m_TabGroupVec[itab];
>
>             tab->show();
>             Fl::wait();

Calling Fl:.wait() once does not guarantee that drawing occurs and that
the window contents gets updated ...

>                 uchar *p = fl_read_image( NULL, 0, 0, w, h, alpha );

... and therefore this fl_read_image() call will very likely read the
"old" contents from the window.

If you *really* wanted to follow this route you'd better do something
like ending your callback and trigger a timeout (which would occur after
drawing the window) and then ... But I don't recommend this, it makes
things only complicated.

I think it's easier to do it like test/device.cxx which uses a direct
draw() call to draw the window to an offline image and then you can use
that image. This can supposedly be done in a loop w/o any side effects
(other than taking some time and making the user interface unresponsive).

My demo does a similar thing with Fl_Copy_Surface as an example but
Fl_Image_Surface works very similar and would give you what you need.
Instead of calling fl_read_image() you should use Fl_Image_Surface to
draw onto and then use Fl_Image_Surface::image() to get the RGB image as
in the test/device demo.

Whether your tabs logic does the right thing I can't tell, but see above.

Albrecht Schlosser

unread,
Oct 30, 2021, 2:46:51 PM10/30/21
to fltkg...@googlegroups.com
On 10/30/21 8:36 PM Albrecht Schlosser wrote:
> On 10/30/21 8:13 PM Rob McDonald wrote:
>> Thanks for the tips Albrecht, I will check those things out.
>>
>> Right now, I am having trouble doing a for-loop across all tabs in a
>> particular GUI.  Despite calling show(), I get repeated images of the
>> default tab rather than of each one....
>
> Looking at your code below I believe that my proposals would fix your
> issues and I strongly recommend to change your code before you waste
> time pursuing your current strategy. I'll explain the details below.
>
>> How can I programmatically cycle through displaying tabs?
>
> Independent of your code below, if 'tabs' is the Fl_Tabs widget, you
> would show() all individual children as you probably tried with your
> m_TabGroupVec (whose contents I don't know).
>
> A simple loop would be like:
>
> int nc = tabs->children();
> for (int i = 0; i< nc; i++) {
>   tabs->child(i)->show();
> }
>
> But that alone doesn't solve your problem ...

... and sorry, it is not correct. :-(

After reading Ian's reply I noticed that calling show() here would be
wrong. The correct call would be value() like this:

int nc = tabs->children();
for (int i = 0; i< nc; i++) {
  tabs->value(child(i));
}

https://www.fltk.org/doc-1.4/classFl__Tabs.html#a188bef71735c167e44af095ddd4922b6

"Sets the widget to become the current visible widget/tab. Setting the
value hides all other children, and makes this one visible, if it is
really a child."

Sorry for the confusion.

BTW: show()ing or hide()ing individual children must not be done with
Fl_Tabs, you should always use value(). But show() and hide() don't
reorder children.

Manolo

unread,
Oct 30, 2021, 3:10:02 PM10/30/21
to fltk.general


Le samedi 30 octobre 2021 à 15:41:20 UTC+2, Albrecht Schlosser a écrit
The recommended way in FLTK 1.4 is to use one of the new "surface" devices, usually Fl_Image_Surface and to draw() the entire window onto that surface. Fl_Copy_Surface is another "interesting" target device. The only "official" way to write directly to an image is Fl_SVG_File_Surface which can be used to write directly to an SVG image file.You can find examples of these (and more) in test/device.cxx. Writing a PNG file is not that difficult but I think you know how to write the image file anyway.

Some more info on this subject:
FL_PostScript_Surface, Fl_EPS_Surface, and Fl_Copy_Surface allow to draw any widget (including windows) to PostScript, EPS,
or the clipboard, respectively. The first 2 are in vectorial format, and even all 3 are, under macOS or Windows. The EPS format is interesting
to publish figures in a book because the result is much nicer on a printed page, because it has a high resolution, than what is obtained
printing a copy of the display, usually at 75 dpi.

Ian MacArthur

unread,
Oct 30, 2021, 4:42:17 PM10/30/21
to Fltk General
FWIW (probably not much) I did a version of this when I was capturing a series of window and group views, where I basically used the Fl_Printer class, and contrived to “print” each group to a separate page of the output stream.

I then had the OS “print” the pages to PDF, and that gave me the images I needed for my document.
Might be worth a shot (though, that said, PDF isn’t always the most convenient format to work with either!)


Rob McDonald

unread,
Oct 30, 2021, 7:32:49 PM10/30/21
to fltk.general
On Saturday, October 30, 2021 at 11:46:51 AM UTC-7 Albrecht Schlosser wrote:
>
> Independent of your code below, if 'tabs' is the Fl_Tabs widget, you
> would show() all individual children as you probably tried with your
> m_TabGroupVec (whose contents I don't know).

After reading Ian's reply I noticed that calling show() here would be
wrong. The correct call would be value() like this:

int nc = tabs->children();
for (int i = 0; i< nc; i++) {
  tabs->value(child(i));
}

https://www.fltk.org/doc-1.4/classFl__Tabs.html#a188bef71735c167e44af095ddd4922b6

"Sets the widget to become the current visible widget/tab. Setting the
value hides all other children, and makes this one visible, if it is
really a child."

Sorry for the confusion.

Thanks a ton -- no idea how long it would have taken me to figure that out.  I'd looked at the Fl_Tab documentation table of contents and didn't see anything there that looked promising -- not sure how I missed it.

I'd tried show(), activate(), damage(), set_active(), set_changed(), and some others -- all in various combinations.  I was barking up every wrong tree I could find...

Thanks much, things are 'working' now.

Thanks also for the other messages about the difference using the other ways of drawing -- it makes sense and I will give it a try.  I posted what I had when I received your last message because I felt like there were two problems -- doing the capture best and getting tabs to show.  Looks like that was true and now the latter is taken care of.

The documentation for Fl::wait() says (abridged) "What this really does is ..... call Fl::flush() to get the screen to update, and then wait some time ...... It then ..... returns."

So, this made me think that it gave sufficient guarantee that the screen would update.  In practice (on my MacOS machine), it seems to be working.

That said -- it is a super ugly solution and I did not like calling Fl::wait() from 'normal' code in the first place.  So, I much prefer your solutions of rendering the GUI to a separate image or vector file.

Rob

 

Rob McDonald

unread,
Oct 30, 2021, 7:34:05 PM10/30/21
to fltk.general
On Saturday, October 30, 2021 at 12:10:02 PM UTC-7 Manolo wrote:
Some more info on this subject:
FL_PostScript_Surface, Fl_EPS_Surface, and Fl_Copy_Surface allow to draw any widget (including windows) to PostScript, EPS,
or the clipboard, respectively. The first 2 are in vectorial format, and even all 3 are, under macOS or Windows. The EPS format is interesting
to publish figures in a book because the result is much nicer on a printed page, because it has a high resolution, than what is obtained
printing a copy of the display, usually at 75 dpi.

Thanks Manolo.

The superiority of vector formats is not lost on me.  I will check it all out and see what I can do.

Best,

Rob


 

Rob McDonald

unread,
Oct 31, 2021, 1:50:34 AM10/31/21
to fltk.general
When using the Fl_Surface_Device approach, what is the importance of the Fl_Surface_Device::push_current(surf); Fl_Surface_Device::pop_current(); pair?  It seems that these are a newer alternative to Fl_Surface_Device::set_current().

Everything seems to be working fine without using them (push/pop or set_current) -- however, I only have one Fl_Surface_Device going at a time.  I create it, use it, and delete it before moving to the next screen.

I can see keeping a multi-page PS file device around for longer, but I'm not immediately seeing the use case for the more complex API.  In particular when commands like draw_decorated_window are methods of a Fl_Widget_Device, it is hard to see where the ambiguity of which surface you want to draw to arises...

In any case, is it OK if I omit these?  Everything seems OK with them left out.

When I loop over a _lot_ of captures (in particular when I grab both PNG and EPS versions of everything), my program triggers IMKClient Stall detected, *please Report* your user scenario attaching a spindump (or sysdiagnose) that captures the problem errors from the MacOS side of things -- do we have any clue where this is coming from?  

Rob


Manolo

unread,
Oct 31, 2021, 5:47:06 AM10/31/21
to fltk.general
Le dimanche 31 octobre 2021 à 06:50:34 UTC+1, rob.a.m...@gmail.com a écrit :
When using the Fl_Surface_Device approach, what is the importance of the Fl_Surface_Device::push_current(surf); Fl_Surface_Device::pop_current(); pair?  It seems that these are a newer alternative to Fl_Surface_Device::set_current().
Yes. These are an improved alternative to set_current because you don't have to memorize what was the current surface
in use before when you stop using  a surface : just push_current to start using it and pop_current to stop.
Don't use set_current ever if you use push_current/pop_current.

Everything seems to be working fine without using them (push/pop or set_current) -- however, I only have one Fl_Surface_Device going at a time.  I create it, use it, and delete it before moving to the next screen.

I can see keeping a multi-page PS file device around for longer, but I'm not immediately seeing the use case for the more complex API.  In particular when commands like draw_decorated_window are methods of a Fl_Widget_Device, it is hard to see where the ambiguity of which surface you want to draw to arises...
With push_current/pop_current you can keep a multi-page PS surface alive as follows
- create the PS surface; begin_job(); pop_current (because begin_job does an implicit push_current)
loop:
- with the display as current device, bring the window or tab of interest to front
- push_current to the PS surface
- begin_page(); draw the widget of interest to the PS surface; end_page()
-  if (more draw needed) { pop_current; goto loop;}
- end_job()
- delete PS surface

In any case, is it OK if I omit these?  Everything seems OK with them left out.
My view is that push_current/pop_current is a better API. set_current is there only for compatibility with earlier FLTK versions.

When I loop over a _lot_ of captures (in particular when I grab both PNG and EPS versions of everything), my program triggers IMKClient Stall detected, *please Report* your user scenario attaching a spindump (or sysdiagnose) that captures the problem errors from the MacOS side of things -- do we have any clue where this is coming from?  
Of course, it's difficult to know what may happen in a complex program with these words only.
Using push_current/pop_current may help, though.

Rob


Albrecht Schlosser

unread,
Oct 31, 2021, 8:53:45 AM10/31/21
to fltkg...@googlegroups.com
On 10/31/21 6:50 AM schrieb Rob McDonald wrote:
> When using the Fl_Surface_Device approach, what is the importance of
> the Fl_Surface_Device::push_current(surf);
> Fl_Surface_Device::pop_current(); pair?  It seems that these are a
> newer alternative to Fl_Surface_Device::set_current().

Manolo said it already, but I'd like to emphasize that these methods are
used to replace the *current* drawing device so you can call simple FLTK
drawing primitives like fl_rect() to draw into these surfaces. This is
usually done in a drawing context, e.g. in a widget's or window's draw()
method. As Manolo explained the pair of push_current/pop_current is for
simlification in such cases and should be used rather than set_current().

> Everything seems to be working fine without using them (push/pop or
> set_current) -- however, I only have one Fl_Surface_Device going at a
> time.  I create it, use it, and delete it before moving to the next
> screen. [...] I'm not immediately seeing the use case for the more
> complex API.  In particular when commands like draw_decorated_window
> are methods of a Fl_Widget_Device, it is hard to see where the
> ambiguity of which surface you want to draw to arises...
> In any case, is it OK if I omit these?  Everything seems OK with them
> left out.

You're right. If you only use Fl_Widget_Surface::draw(Fl_Widget* widget,
...) and the related draw_window*() methods you are fine w/o changing
the *current* drawing context. These methods do this for you when they
are called. They check if the device is current before pushing a new
context, hence there's no additional overhead (whether you do it or not).

Insofar my demo code used them unnecessarily and you can omit these
calls in your application.

Thanks for the heads-up, I'm changing this in my project as well.

Albrecht Schlosser

unread,
Oct 31, 2021, 9:13:51 AM10/31/21
to fltkg...@googlegroups.com
On 10/31/21 1:32 AM Rob McDonald wrote:
On Saturday, October 30, 2021 at 11:46:51 AM UTC-7 Albrecht Schlosser wrote:

The correct call would be value() like this:

int nc = tabs->children();
for (int i = 0; i< nc; i++) {
  tabs->value(child(i));
}
[...]


Thanks a ton -- no idea how long it would have taken me to figure that out.

Welcome.


The documentation for Fl::wait() says (abridged) "What this really does is ..... call Fl::flush() to get the screen to update, and then wait some time ...... It then ..... returns."

So, this made me think that it gave sufficient guarantee that the screen would update.  In practice (on my MacOS machine), it seems to be working.

Calling Fl::wait() just once is not reliable particularly on Unix/Linux with X11. It does likely work as you expected on macOS and on Windows. The reason is the asynchronous event processing when using X11. It may take one or more server message turnarounds to complete a window show() message (which is not the concern here) but you never know.

Fl::wait() waits for the *next* message coming in, processes the message queue until it's empty, and then returns. If you call it only once there may be messages being processed by the X server (particularly if it's not a local server) and not yet returned to the user program. Imagine there is already one message pending (e.g. a timeout), then this message will be processed immediately and then the queue may be empty before the X server returns its reply. Therefore Fl::wait() *may* return before everything you expect has been processed by FLTK.

As you can see this depends on the platform, configuration (local vs remote X server) and timing. It's far better to avoid this in the first place.

Manolo

unread,
Oct 31, 2021, 9:29:04 AM10/31/21
to fltk.general
Le dimanche 31 octobre 2021 à 10:47:06 UTC+1, Manolo a écrit :
Le dimanche 31 octobre 2021 à 06:50:34 UTC+1, rob.a.m...@gmail.com a écrit :
I can see keeping a multi-page PS file device around for longer, but I'm not immediately seeing the use case for the more complex API.  In particular when commands like draw_decorated_window are methods of a Fl_Widget_Device, it is hard to see where the ambiguity of which surface you want to draw to arises...
With push_current/pop_current you can keep a multi-page PS surface alive as follows
- create the PS surface; begin_job(); pop_current (because begin_job does an implicit push_current)
loop:
- with the display as current device, bring the window or tab of interest to front
- push_current to the PS surface
- begin_page(); draw the widget of interest to the PS surface; end_page()
-  if (more draw needed) { pop_current; goto loop;}
- end_job()
- delete PS surface

I've seen that Fl_Printer and Fl_PostScript_File_Device where not consistent in the
location of Fl_Surface::push_current. This is now changed in the git repository.
Fl_Paged_Device::begin_page() now push the surface current and
Fl_Paged_Device::end_page() pop the surface.

The above algorithm is simpler :
Keep a multi-page PS or printer surface  alive as follows
- create the PS or printer surface; begin_job();
loop:
- with the display as current device, bring the window or tab of interest to front
- begin_page(); (this sets the PS/printer current)
- draw the widget of interest to the PS surface;
- end_page() (this returns the display as current surface)
-  if (more draw needed) goto loop;
- end_job()
- delete PS/printer surface


Reply all
Reply to author
Forward
0 new messages