A quick & dirty guide on how to manage interactive display windows in G'MIC

G’MIC script writers may be interested by this.

As you know, a G’MIC script has the possibility to create its own display windows, and manage user events associated to them. It can be useful sometimes to write commands that allows such interactive displays, and a few of the G’MIC commands and filters use this feature (e.g. display, x_warp, x_morph, x_pacman, and more generally, all the x_something commands…).

Before beginning with the explanations on how it works, be aware that these are pretty basic features, with a lot of limitations. Don’t ever think you can write a full-featured GUI library in G’MIC :stuck_out_tongue: That being said, let us describe the basic principles.


Basically, only three things have to be known to start managing display windows:

  1. Command window (shortcut w), is used to make a display window visible, move it, resize it, set its title, and display an image in it. It’s a all-in-one command :slight_smile:
    There are in fact 10 different versions of this command: window0 (aka w0), window1 (aka w1), …, window9 (aka w9) to manage each of the 10 available display windows in G’MIC (no, you don’t want to open more than 10 windows simultaneously. It’s actually possible but with a lot of tricks, probably not suitable for this doc). Command window (aka w) is actually equivalent to window0 (aka w0). Note that there are no needs to create a display window. There are all already created, just not visible by default.

According to the reference documentation, the arguments to window are:

 window (+):
        _width[%]>=-1,_height[%]>=-1,_normalization,_fullscreen,_pos_x[%],_pos_y[%],_title

They are (almost) self-explanatory.

  • width and height are the desired size you want for the display window (set to -1 to keep their values unchanged from one call of w to another). Set width to 0 to hide/close the display window.
  • normalization sets the normalization mode used when displaying an image into the display window. Most used values here is either 0 (no-normalization, so you have to ensure your image values are in 8bits-range [0-255]), or 1 (which means the display window always normalizes image values in [0,255] before displaying it).
  • fullscreen can be 0 (no fullscreen, default) or 1 (fullscreen window).
  • pos_x and pos_y are the desired (x,y) position of the upper-left corner of the window. These parameters can be ignored if we want to let the system determine this by itself (which is the case most of the time).
  • title is the string that will be displayed on the window title bar.

And most important, the selected image(s) on which the command is applied (i.e. w[img]) tells which image must be displayed (or updated) in the display window. This selection can be empty (i.e. w[]), in which case the content of the display window in unchanged (use an empty selection if you want just to resize/move the display window, or change its title bar, for instance).
This selection can also contain multiple images, in which case all images are displayed side by side on the display window.

  1. Substituting expressions such as {*,feature} or {*0,feature} (replace the 0 by the index of the display window you are working with) : they provide information about display window and what happens in them (in particular, user events). For instance, {*,ESC} is evaluated as 0 or 1 whether the key ESC is being pressed or not. In the same way, {*} is equal to 0 is the window is not visible anymore (it has been closed by the user). The state of mouse buttons and the mouse coordinates are retrieved with {*,b} and {*,x,y}. Have a look to the Substitution Rules section in the reference documentation to see all the info that can be retrieved from a display window. Window position, and size can be also retrieved with such substituting expressions.

  2. The last important thing to know is the command wait . It is used to wait for a user event (when called without parameters), or wait for a certain amount of time (with an argument in milliseconds), which is useful if you want to display animations in your display window.

And that’s it. With these three things, you can do almost everything possible in terms of window management with G’MIC.


Let me show you next a simple example of code, with a mini-drawing application written in G’MIC:

# My first interactive G'MIC script.
foo :
  rm
  512,512,1,3                                    # Create the image[0] that will be our canvas image.
  w. 512,512,0,"My 1st interactive G'MIC script" # Make the default display window #0 visible.

  do
    x,y,b,ww,wh={*,x,y,b,w,h}                    # Retrieve mouse coordinates, button state and window size.

    if $b" && "$x>=0                             # One of the buttons is pressed and mouse is over the canvas?
      if narg($ox)
        line[0] $ox,$oy,$x,$y,1,255              # Draw a segment from previous position, if any.
      fi
      circle[0] $x,$y,5,1,255,128,0              # Draw a circle in image[0] corresponding to the mouse position.
      ox,oy=$x,$y                                # Save coordinates to link with possibly next point.
      w[0]                                       # Refresh the window display with the updated image.
    else
      ox,oy=
    fi

    wait                                         # Wait for a user event.
    if {*,r} w[] 512,512 fi                      # Make sure the display window keeps a size of 512x512.

  while {*}" && "!{*,ESC}                        # Loop while window has not been closed, or key ESC pressed.
  w[] 0                                          # Close/hide the display window.

I won’t comment much here, everything is already commented in the code.
The most common tasks related to display windows (drawing, and managing user events) are illustrated here.

Here is an animation of what you get with that sample code:

anim_gmic

This simple example could be of course improved in several ways, for instance, by allowing the display window to be resized to any size. Here for simplicity, I ensure that the windows keeps a size of 512x512.


Additional tricks:

  • Command cursor can be used to show/hide the pointer icon when mouse is over a display window. Default value is 1 (visible), but you can set it to 0 if you want to draw your own pointer icon on your display window (this is done for instance in display).

  • What is important to understand is that there are no things as ‘event signals’ that can be written in your scripts. The event handling loop is always done explicitly by the script, as we all did before GUI library existed :slight_smile: . As you can see with the example code above, its is actually very simple to achieve, so no worry about that.


Well that’s it for now, if you have any questions about this particular topic, just let me know.

4 Likes

Now it’s time for me to attempt to recreate this filter. Thanks, @David_Tschumperle

Knowing @Reptorian, @bazza and others, a game might emerge. :wink::nerd_face::stuck_out_tongue:


@David_Tschumperle How do I end a command gracefully without displaying, printing or quitting? E.g., if I do gmic sp duck +b 1 w[] 0, it still displays a window at the end with stats in the console.

1 Like

@David_Tschumperle

How do I work with multiple windows? I only see one at once.

foo:
rm
512,512,1,4
tile_size=16
nw={floor(w#-1/$tile_size)*$tile_size}
nh={floor(h#-1/$tile_size)*$tile_size}
i $1

w[0] $nw,$nh,0,"Grid"
w[1] {w#-1},{h#-1},0,"Tileset"

do
    x,y,b,ww,wh={*,x,y,b,w,h}
    wait
while {*}" && "!{*,ESC}

Use d[].

Use w1 instead of w to activate window#1. w only acts on window#0.

Thanks. Also, I would like to enable scrolling. The test image I’m using is way too big to fit on screen. That limits its usability.

There are no easy ways to get scrolling bars on the display windows. You’ll have to program it by yourself (and I can already tell you, that will be probably harder than what you think).

As I just said above : there are a lot of limitations with the G’MIC display windows, just don’t expect you can re-create an entire graphical user interface with the G’MIC display capabilities. In theory, you can do it, but it would be cumbersome. I must confess I wouldn’t risk it myself.

If you need a real customized graphical interface for a GIMP plug-in, don’t use G’MIC, really. Do your plug-in with Qt/GTK/Whatever GUI Lib, and use C++ code, but don’t try doing this in G’MIC.
It is definitely not the right tool to do that.

I think I found my workaround that’ll work. Now, I don’t know where’s the limitation, but can G’MIC detect scroll event? What about keyboard arrow key?

There is not something like ‘scroll event’.
Events you can detect is key press, mouse button press, mouse button motion.
Even if you need a drag&drop, you have to code it by yourself, by :

  • Detecting mouse button press event.
  • Storing the location of the pointer as $x0,$y0
  • Detecting mouse button release event.
  • Get the new location as $x1,$y1
  • Then, do something with your $x0,$y0,$x1,$y1.

That’s really ‘low-level’ programming. It’s still possible to manage user interactions, but things are usually easier to code with a real GUI library.

@David_Tschumperle

A sneak peak of what I’m doing.

foo:
rm
tile_size=32

gw=512
gh=512

nw={floor($gw/$tile_size)*$tile_size}
nh={floor($gh/$tile_size)*$tile_size}

$gw,$gh,1,4,((x%$tile_size==0)||(y%$tile_size==0))||(x==(w-1)||y==(h-1))?1

n 0,255

i $1

l.
    xr={ceil(w#-1/(16*$tile_size))}
    yr={ceil(h#-1/(16*$tile_size))}
    split_tiles. {-16*$tile_size}
    mw=${-max_w}
    mh=${-max_h}
    ti=$!
endl

$tile_size,$tile_size,1,4,0

w[0] {w#0},{h#0},0,"Grid"
w1[1] {w#1},{h#1},0,"Tileset"
w2[-1] {w#-1*2},{h#-1*2},0,"Tile"

ci=0
lr=0
tb=0
tbc=0
ni=1

do
    x,y,b,ww,wh={*,x,y,b,w,h}
    x1,y1,b1,ww1,wh1={*1,x,y,b,w,h}
    
    if {*1,ARROWRIGHT}
        lr+=1
        lr={$lr%$xr}
        ci={($lr+$tb)%$ti}
        ni={$ci+1}
        w1[$ni]
    elif {*1,ARROWLEFT}
        lr-=1
        lr={$lr%$xr}
        ci={($lr+$tb)%$ti}
        ni={$ci+1}
        w1[$ni]
    elif {*1,ARROWDOWN}
        tbc+=1
        tbc={$tbc%$yr}
        tb={$tbc*$xr}
        ci={($lr+$tb)%$ti}
        ni={$ci+1}
        w1[$ni]
    elif {*1,ARROWUP}
        tbc-=1
        tbc={$tbc%$yr}
        tb={$tbc*$xr}
        ci={($lr+$tb)%$ti}
        ni={$ci+1}
        w1[$ni]
    fi
    if $b1" && "$x1>=0" && "$y1>=0
        f. i(#$ni,floor($x1/$tile_size)*$tile_size+x,floor($y1/$tile_size)*$tile_size+y,z,c)
        w2[-1]
    fi
    if $b" &&"$x>=0" && "$y>=0
        sh. 3
        j[0] [-2],{floor($x/$tile_size)*$tile_size},{floor($y/$tile_size)*$tile_size},0,0,1,[-1],255
        rm.
        w[0]
    fi
    wait
while {*}" && "!{*,ESC}
4 Likes

Makings of an RPG or rogue-like. :slight_smile::+1:
repeat
You wake up; slime attacks you for 10 points; you die.
done

1 Like

@David_Tschumperle Question, can window command be modified to have the option to inherently display alpha background as a option? The reason I’m asking is that it would be a lot of work just to enable alpha background.

Not sure what you are requesting here.
window can only display images with 1,2 or 3 channels. If you need some sort of ‘alpha’ support, then probably drgba could help to convert your RGBA image into a RGB representation to be displayed.

Well, dbrga command alone should make this a lot easier. Thanks though ideally, I would like for something like that to be built-in as a option within window so that less lines would be needed.

Not a good idea IMHO. There are probably thousands ways one wants to manage alpha-channel for a display window, so having an option for that (and for a built-in command as window) could not be “complete”.
Of course, you can always define your own custom_window command that manages the alpha-channel just as you want.

Okay, the more I work on the tile mini-program. I did found out that {,e} makes the command switch mode. So, I have to ask you what keys are not available in use for {,?}? I used {*,s} and the mode flickering is gone.

So you mean {*,S} ?
Testing a key is always done with a syntax involving a capital letter.

There’s another thing I might make with more knowledge on interactive filter scripting. I think I can actually theoretically make a MS Paint clone or something like amiga painting softwares. In fact, it would be exciting to make such a entire program with gmic scripting.

A bit like command x_paint ? :stuck_out_tongue: