Discussions about the G'MIC script language

Hello G’MIC scripters,

I propose to open this thread to discuss about the design of the G’MIC script language, in the most general sense.

For instance, I’d be really interested to get feedback from script developers, and thoughts on:

  • What you think about the language : what’s good, what’s wrong, what needs to be improved?
  • How do the language compare with other similar tool/language (e.g. ImageMagick, bash, …).
  • Propositions of possible evolutions of the language ?
  • Discussions about the best way to write “classical” programming patterns in G’MIC.
  • etc.

As a reminder, I developed the G’MIC language with the following ideas in mind:

  • It had to be a scripting language, for quick prototyping of image processing algorithms. The idea was to optimize my developing pipeline, which is basically : write some code, test code, correct code, test code, write some code, test code, … I didn’t want to wait anymore for a compilation process before I can test my code (as I did for years, using C++).
    G’MIC is then an interpreted language and it will probably remain so :slight_smile:

  • It had to be a concise language, again, to allow quick prototyping. I think this goal has been achieved (maybe even too much ?). Most of the time, IP algorithms written in G’MIC will be shorter than those written in other languages, to the detriment of the overall readability of the code (comments are sometimes essential to understand what a code does).

Now, my personal pro/cons list:

What I like in the language:

  • it is defined only by a few “simple” rules (but not necessarily obvious to handle).
  • It allow to test some new ideas from scratch very quickly.
  • It allows to create new image processing filters and distribute them easily, now for a wide variety of digital illustration/image retouching applications : GIMP, Krita, Paint.NET, Photoshop, PaintShop Pro, Photoline, …
  • The embedded math evaluator is wonderful and allows to write custom “per-pixel” code that runs reasonnably fast most of the time (particularly when using parallelization tricks).
  • Adding new commands make them automatically available to be used in other pipelines.

What I don’t like so much:

  • The documentation : we miss some clear tutorials to teach the language. @grosgood has made a fantastic job a few years ago with these pages, but unfortunately the language has evolved a bit since then, and some things do not work exactly as described in these pages now.
  • I miss the speed of C++. That was expected of course, but it’s sometimes really embarrassing to think that a filter could run 5-10x faster if it was coded in C++.

Idea of possible evolutions:

  • At the moment, my main goal is actually optimizing the performance of the interpreter (memory footprint and execution speed). The current number of commands written in G’MIC is already large, and any useful optimization could benefit to all these commands.
  • Concerning the syntax of the language itself, I don’t have any ideas to improve it right now. Maybe you could help ?

Do not hesitate to share your thoughts about all this.

I’ll expand on this later, but a few things important to myself:

  • I find the g’mic language fantastic as it is for quick prototyping, but only once you’re very familiar with it. There are quirks which take a long time to get used to (e.g. macro stuff, learning which commands are native). It’s not so optimal for prototyping if you are a newcomer. The math interpreter language does slightly better there.
  • Usually clean and consistent code style is required for working in teams and code maintenance. Is that a problem for community filters? Not sure, but it does affect code re-use.
  • I always dreamed of a code formatter tool (like some of those available for C++) which would expand to long commands and indent. It would make reading other peoples filters quite a lot easier in some cases. In fact I often do that myself manually.
2 Likes

Definitely interested by examples here! Looks like a good start to improve the language syntax in the future! (and 100% confident about what you will show).

This will come in portions because my free time just ran out for the week. Starting with examples of macro style code (to readers: 10 points for each one you know offhand!):

$"*"
$!
${}
{@1}
{-1,t}
{-1,^}
{``string}
${"u "$string}
l[$>]

Now I’m not saying any of this is bad for me. In fact once you know them, they’re very convenient. But in my opinion it’s not a good thing for a newcomer who just wants to do some really quick prototyping! I think that’s a great shame when it’s one of the best purposes of g’mic.

Not only are they obfuscated (meaning is hard if not impossible to grasp from the command itself), but they are hard to search for in any help text. Most of those are only learned by reading the reference line by line, or by looking at existing filters.

Edit: and sorry yes, I’m only pointing out a possible problem with no solution!

1 Like

I have tried and failed to get to grips with the G’MIC language.

Well, I haven’t totally failed. I spent much of 2020 with GMIC (I really can’t be bothered with that apostrophe, sorry) and writing scripts and writing notes to remind myself that “repeat $! local[$>]” loops through the outer list images, one image at a time. Experiments showed me the difference between “local”, “local[2]”, “+local” and “+local[2]”. Yes, I know you guys would not spell out “local”.

I built filters that I can call from Gimp, and I get controls such as sliders, and I can immediately see the result. Good stuff.

But it is great struggle for me. For example, numeric arguments:

+resize[-1] 120%,120%,1,3,0,0,0.2,0.2

One of those numbers is the boundary condition, but which one? I think it’s the second “0”, but I’m not sure. If we had “dirichlet” instead of “0”, it would be obvious.

I am not good with symbols, such as @garagecoder shows:

$"*"
$!
${}
{@1}
$>

… etc. In a multi-language world, I can see the advantages of symbols (and numbers, see above) over words in a particular language such as English. But I really struggle to understand (and memorise!) the meaning of “$!” etc.

I could go on about GMIC missing same concepts that I am accustomed to. For example, there is no numeric definition of “white”. So how does “negate” work? I think it transforms so what was the highest value becomes zero, and what was the lowest becomes (highest minus lowest). But this doesn’t seem to be documented. Anyhow, concepts are a different topic.

1 Like

Thanks for your feedback @snibgo and @garagecoder .

You are right, these so-called “substituting expressions” are quite strange things for the newcomer. And at the same time, they are soooo useful in practice. But as @garagecoder you have to be used to these expressions before taking full advantage of them.

I must admit I’m so familiar with them that I completely forgot this tough aspect of the language.

My opinion is that the documentation should be made more developed about these particular expressions, with more examples of use, and maybe give an idea of which are the most useful in practice.

Personally, I like the language as-is though I don’t like a couple of quirks or better named as pet-peeves since they’re no big deals to get around. Right now, I only have few pet peeves.

Pet-Peeve

Having to define a string into func() within math evaluator to have dynamic func() while func(a,b,c) can be subjected to changes. See rep_tr_pixel_sharpener

skip ${2=0},${3=0},${4=0},${5=0}
if $3==5
    fnx=x+rot_x(j,k)
    fny=y+rot_y_alt(j,k)
.........
else
    fnx=x+j
    fny=y+k
fi
f "
begin(.......
fnx()="$fnx";
fny()="$fny";
);
........
"

Pet-Peeve #2 - The absence of vector of vectors. I did thought of this while making up a theory on how to code blend_mode_shape0.

Pet-Peeve #3 - If x else y in math evaluator has to have the same time. Again, no big deal getting around this though I think it would be easier if they didn’t require the same type.

As for symbols, I can see why users dislike the scripting language. It requires close attention to symbols to fully master.

Coming from someone for whom coding will never come naturally, G’MIC is very hard to understand. 25% is staring at the console, 25% is staring at the text editor, 10% is examining GitHub code, 40% is pestering @David_Tschumperle or @garagecoder and feeling bad about it. Of all of the hobbies I have taken, G’MIC consumes hours of my day.

The main suggestion I would give to the community is to maintain a Wiki or something with snippet (unit testing, or whatever else it may be called) examples. Each should be self-contained, be able to run on its own and illustrate one or two concepts or uses of the language. Something that anyone can contribute. And David can oversee and improve if he sees some that could be simplified or clarified. As this grows, it would be wise to add tagging and make the document more searchable.

2 Likes

I don’t understand what you are trying to achieve here. Could you tell a bit more details ?

The same trick I gave to @garagecoder two days ago also applies here:

eval "
   tab = vector(#3*100);                   # Array of 100 vectors of size 3.
   tab_get(p) = tab[3*p,3];                # Get vector3 at position tab[p].
   tab_set(p,vec) = copy(tab[3*p],vec,3);  # Set vector3 at position tab[p].

That’s mainly because the return value of all expressions in the math evaluator can be theoretically assigned to a variable. And the type of a variable must be known at compilation time. Obviously,

eval "
   X = if (u<0.5, pi, vector3());
"

won’t let the compiler decide what is the type of X.

I’m sorry to hear that :frowning: I don’t want to be responsible for the time you waste during the day!

Yes, you’re right, documentation is not enough detailed at the moment.
I don’t really know how I can improve that by the way. I’m not a really good documentation writer TBH.

On #1, it about assigning expressions to func(). To do that dynamically, I have to use if func_string=expr elif func_string=expr2 outside of fill. Then, assign func_string to func().func(a,b,c) can be changed within fill, eval…

On #2, trick noticed.

On #3, I acknowledge why. Just saying that it would be easier if it wasn’t a requirement though that’s not possible to do. In other programming language, if else can take just about any arguments.

Something like this could work, maybe ?

foo :
  eval "
    $1?(
      func(x) = 3*x;
    ):(
      func(x) = pi + x;
    );

    print(func(5));
  "

and then:

$ gmic foo 0
[gmic]-0./ Start G'MIC interpreter.
[gmic_math_parser] func(5) = 8.1415926535897931
[gmic]-0./ End G'MIC interpreter.
$ gmic foo 1
[gmic]-0./ Start G'MIC interpreter.
[gmic_math_parser] func(5) = 15
[gmic]-0./ End G'MIC interpreter.

EDIT: this will work as long as the checked expression is a constant (here $1).

Gonna move the discussion to my g’mic thread since now it’s going offtopic.

For this, I have a possible solution :

def_enum_resize : 
  _i_memory,_i_none,_i_nearest,_i_average,_i_linear,_i_grid,_i_bicubic,_i_lanczos=-1,0,1,2,3,4,5,6
  _b_dirichlet,_b_neumann,_b_periodic,_b_mirror=0,1,2,3

foo : 
  def_enum_resize
  resize[0] 256,256,1,3,$_i_none,$_b_mirror

It’s actually quite the same as using enum in C when defining function arguments. Personnally, I don’t like it that much.

I might then write:

resize[0] 256,256,1,3,$_b_mirror,$_i_none

… with the interpolation and boundary parameters inverted. The interpreter would not flag my mistake.

In my opinion, the interpreter should accept one of the following for boundary: 0 1 2 3 dirichlet neumann periodic mirror.

Anything else should be rejected.

Ideally it would not accept the numbers, but that would break existing scripts, so is not a feasible solution.

Then, this command basically does the trick:

resize_with_enum : skip ${2=100%},${3=100%},${4=100%},${5=nearest},${6=neumann},${7=0},${8=0},${9=0},${10=0}
  resize ${1-4},\
    {s=['$5'];s=='memory'?-1:s=='none'?0:s=='nearest'?1:s=='average'?2:s=='linear'?3:s=='grid'?4:s=='cubic'?5:s=='lanczos'?6:nan},\
    {s=['$6'];s=='dirichlet'?0:s=='neumann'?1:s=='periodic'?2:s=='mirror'?3:nan},\
    ${7-10}

and then:

$ gmic sp colorful resize_with_enum 200%,200%,1,3,none,mirror

The code snippets idea would be crowd supported, so the onus won’t have to be on you. I will give you an example, based on 1 of thousands of questions I asked you.

# foo_by demonstrates CLI echoing and one kind of substituting.
# Outputs the message: 'Filter with a 3x3 filter'.
# Note. Without the { }, we get: 'Filter with a 3 filter' instead.
foo_by:
  t=3 e[^-1] "Filter with a "${t}"x"${t}" filter"

I think that’s a good demonstration of the “macro” problem. On the one hand, we have a very flexible way to redefine commands to work how we please. But on the other, it’s (much?) more difficult for people other than yourself to do so!

Some elements of g’mic are practically like bash or even regular expressions, so it’s hard to reconcile power with simplicity.

That’s true, and somehow intended.
G’MIC being a script language, it has been inspired a bit from bash (and also a bit from sed).
But is there any script language that do not have this kind of expressions ?
(don’t anwer perl :slight_smile: ).

Speaking of substitution, (at least) two pages talk about it, not one:
https://gmic.eu/reference/substitution_rules.html
https://gmic.eu/reference/adding_custom_commands.html

I know I originally asked the documentation to be separated into separate manageable chunks but it can in some ways make searching for the relevant info more difficult. I am still waiting for a search function with suggestions. :crossed_fingers:

Before, when commands and examples were quoted or more distinct than CSS styles, they were easier to find using the browser search. E.g., when ‘resize’ was quoted like this or something like that, I could find it in a heartbeat without going through all the times the word appeared in the document. I mentioned this before but styled text also interferes with screen readers and other aids that people with disabilities use.