G'MIC Tutorial Fragments

Addendum -fill & -input: The tutorial doesn’t cover an aspect of -store and the math expression function store() which can stuff selected images into a command line variable, removing the selected images from the image list. That command line variable can be a pixel source for -fill and -input.

In light of that, a tutorial on -store and the math expression function store() are in order sooner rather than later, because the two variants, in conjunction with -fill and -input can set up the passing of wholesale data quantities between the two environments. Tutorials are still written along the line that the two environments are rather closed-off, in the matter of data sharing. That limitation no longer is true.

OK… details!
Note that in gmic-py, G’MIC image list objects (ie. list()) are emptied then refilled on each Python run, but the list reference stays the same.

1 Like

Haven’t forgotten you! cloned your repository locally and am sitting on the cookbook → origin/cookbook branch, with about 2.1K words in cookbook.rst. If I push my commits to origin/cookbook, I won’t trigger any publish event will I?

1 Like

Super good news!!! Thanks Garry!!
I think I have omitted a few commands in my Github issue explanations on local building. Nonetheless, “lazy is clever” and pushing just to the origin branch will really trigger a publish but in the documentation website’s ‘cookbook’-branch version, which is a proper sandboxing place for now. So here: https://gmic-py.readthedocs.io/en/cookbook/cookbook.html (you can change the branch by clicking some popping-up list at the bottom left on v:cookbook).

@grosgood: What do you think of bringing up morphological math evaluator coding? Kinda hard to explain, but it is a technique where one inserts a string into variable, and then when one use it in a math evaluator code, the math evaluator reads the string as a code and then run it. When a variable don’t exist, it is just empty. So, in other words, gmic can run dynamic codes. I think this is what set the scripting language apart from most other, the huge flexibility in dynamic coding.

‘rep_tfrac’ has a basic form of this. ‘rep_pfrac’ as well. Chirikov-Taylor filter of mine utilize a bit more convolutated form of this. I’m working on a new strange attractor filter that makes heavy use of morphological math evaluator coding.

1 Like

You’re probably familiar with L-Systems from Aristid Lindenmayer, part of that whole visual cloud centering on The Algorithmic Beauty of Plants that was such a Siggraph darling ages ago (1990 or so). The fun idea behind all that is a coded string that is both morphed by a process, (stage n) and then goes on to drive the next generation of that process (n+1), which further morphs the coded string, and so on, maybe forever. Somewhere in the loop is a “camera” that snaps a picture of the stage; all of the snapshots become an animation.

So, I’m thinking, as an Example for a -store tutorial, a morphing string that gets tossed back and forth between the command line and math evaluator environments, with state residing both in the coded string and the images, and state is conveyed back and forth through variables, their contents exchanged between the two environments via -store and store() So - yes - I think in this regard we’re both noodling along in similar directions. The coded string is dynamically composed. It both drives - and is re-coded by - the process.

I’m not married to L-systems specifically; that’s just where the germ of the idea came from. Mainly, I want to illustrate (1) exchanging state between math evaluator and command line environment, and (2) do more G’MIC animations, a step beyond Caudron, which I think is of interest to people who want to animate textures for Blender models, a la Daniel Bystedt.

Big chunks of Real Life work to do, though. However, looks like some G’MIC time will open up for this weekend. Maybe we can play a bit in this area.

1 Like

Speaking of which… Just a big “Thanks!” to @grosgood for the latest tutorial updates. It looks great and I know that represents a lot of work, that should not keep hidden :slight_smile:

1 Like

New and renewed in tutorials, from when @afre poked a stick in my cave and bought me out of hibernation (15-August-2017 - 06-March-2021). He should count the number of arms that he has; I might have bitten one off:

  1. -store New, 19-April-2021
  2. -name Refurbished, 18-April-2021. Didn’t include ghost cat as an example, but continued my obsession with the Goudy 1911 ampersand instead.
  3. -mix_rgb Refurbished 16-April-2021
  4. Beginner’s Cookbook Refurbished Cookbook, in collaboration with @David_Tschumperle; 1.6x anachronisms. 06-April-2021
  5. Cauldron Refurbished Cookbook, in collaboration with @David_Tschumperle; finished the example 04-April-2021
  6. -luminance Refurbished 04-April-2021 Now featuring overly loud people!
  7. Tutorial Directory New 02-April-2021
  8. Command Decorations Refurbished Basics, in collaboration with @David_Tschumperle; 01-April-2021. Aligned with 2.9x notation. Finally. Maybe.
  9. Basics Refurbished Basics, in collaboration with @David_Tschumperle; Removed some 1.6x anachronisms. 01-April-2021.
  10. -compose_channels Refurbished 23-March-2021
  11. -eigen2tensor Refurbished 23-March-2021
  12. Fingerpainting Upgraded to 2.9x notation 21-March-2021
  13. -roundify New, 18-March-2021
  14. -norm Refurbished 17-March-2021
  15. -orientation Refurbished 17-March-2021
  16. -fill Refurbished 14-March-2021
  17. -map Refurbished 14-March-2021
  18. -threshold Refurbished 13-March-2021
  19. Contribute New 12-March-2021 Just contribute, darn it.

Credit where credit is due. @David_Tschumperle was also scurrying around during this time bringing other tutorial pages into line. Do Your Own Diffusion Tensor Fields was dusted off almost entirely by him, and he conjured 3D Bouncing Balls from whole cloth. Probably most significant, however, was grounding the entire tutorial writing enterprise on G’MIC Markdown and housing content in the gmic-community GitHub repository. The previous approach was considerably more haphazard…

On tap: My side of the collaboration with @Reptorian on Quantum Wells has the makings of a Cookbook; more notes on that to go to that thread. Busy week just past, and a busy weekend, but maybe some G’MIC time is in store. Have fun…

4 Likes

Math Expression Parser Department: const foo=3;, constant scalar and the relationship of these constant positive integer variable data types to ‘Just In Time’ compilation need elucidation, particularly for pythonistas. For example, this will not obtain a dynamically sized vector in the Math Expression Parser environment:

$ gmic -input '(0,1024)' -store. 'param' -eval "ref(get('param',2),myp);ref(resize([0],myp[1]),dynamicallysized)"
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Input image at position 0, with values (0,1024) (1 image 2x1x1x1).
[gmic]-1./ Store image [0] as  variable 'param'
[gmic]-0./ Evaluate expression 'ref(get('param',2),myp);ref(resize([0],myp[1]),dynamicallysized)' and assign it to status.
[gmic]-0./ *** Error *** Command 'eval': Function 'resize()': Second argument (of type 'scalar') is not a strictly positive integer constant, in expression '...ref(resize([0],myp[1]),dynamicallysized)...'.

The contents of vector myp, cannot be constant, as, in general, the Math Expression Parser has no hint as to the content of storage variables like param ( See tutorial -store ) at the moment that a JIT compiler digests the math expression. Consequently the the notion of a dynamically sized zero vector, dynamicallysized, based upon circumstances-of-the-moment such as the contents of myp[1], is not well supported. A constant scalar or a strictly positive constant integer is a type to which a JIT-compiler can assign a specific numeric value at the point of compilation. In particular, striving for some dynamically sized vector based upon a quantity not knowable until the expression runs is a bit wistful. Some math parser functions are not alarmed by dynamically set values at runtime, ( rot(u,v,w,angle) ) but those involving run-time sizing seem generally adverse to parameters other than constant scalar.

1 Like

The solution for stimulating dynamic vector size is to do it outside of eval, fill, input block, and then assign it within the eval, fill, input block. Another solution is to use dar_insert(). I think I remember having to do that, but don’t remember which script I had to do that for. Possibly a test.

1 Like

Thank you for pointing such out. This:

G’MIC math evaluator now deals with vectors (and matrices, and custom functions!)

With a specific mention in Post 20.

Goes the route of ‘image-assisted’ dynamic storage, no? That’s the alternative for the poor moth fluttering toward the shining light of “dynamically sized, determined at run-time” storage, allocated and released wholly in the MEP.

So. tutorial: Runtime allocated and free-ed storage. That doesn’t happen entirely in the context of MEP. Instead, steal an image for something like dynamic storage. dar_***() and heap_***(), which reference (pre-existing) images for backing store. There are also crop(...) and copy(...) for image ↔MEP data exchange. But one also needs to be mindful that this “dynamic space” is coming from fixed, pre-existing images and be mindful of overrunning (per dar_insert() macro advisory):

# Inserts new element 'elt' into dynamic array #ind, at index [pos] ('pos' must be in [0,dar_size(#ind)]).
  dar_insert(ind,elt,pos) = (...)

So. Also. tutorial: eval ${-math_lib} notation which pulls these dar_*** macros in.

And. tutorial: const declarations (which doesn’t automagically convert run time numbers into compile time integers) constant scalar and the MEP functions which require those kind of parameters.

More than enough fragments to roll up into tutorials some day…

Thanks for pointing that out.

Tee, hee. Except I can overrun. :astonished:
memstuff.gmic:

foo:
   1
   -name. onegraypixel
   -print.
   -eval ${-math_lib}" for(j=0,j<10,++j,dar_insert(#0,10*j););for(k=0,k<dar_size(#0),++k,print(I[#0,k]););"
   -display.

From shell:

gosgood@bertha ~ $ gmic -command memstuff.gmic -foo
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Import commands from file 'memstuff.gmic' (1 new, total: 4352).
[gmic]-1./foo/ Print image [0] = 'onegraypixel'.
[0] = 'onegraypixel':
  size = (1,1,1,1) [4 b of floats].
  data = (0).
  min = 0, max = 0, mean = 0, std = 0, coords_min = (0,0,0,0), coords_max = (0,0,0,0).

[gmic_math_parser] I[#0,k] = [ 0 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 10 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 20 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 30 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 40 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 50 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 60 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 70 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 80 ] (size: 1)
[gmic_math_parser] I[#0,k] = [ 90 ] (size: 1)
[gmic]-1./foo/ Print image [0] = 'onegraypixel'.
[0] = 'onegraypixel':
  size = (1,16,1,1) [64 b of floats].
  data = (0;10;20;30;40;50;60;70;80;90;0;0;0;0;0;10).
  min = 0, max = 90, mean = 28.75, std = 32.0156, coords_min = (0,0,0,0), coords_max = (0,9,0,0).
[gmic]-1./foo/ Display image [0] = 'onegraypixel', from point (0,8,0).
[0] = 'onegraypixel':
  size = (1,16,1,1) [64 b of floats].
  data = (0;10;20;30;40;50;60;70;80;90;0;0;0;0;0;10).
  min = 0, max = 90, mean = 28.75, std = 32.0156, coords_min = (0,0,0,0), coords_max = (0,9,0,0).
[gmic]-1./ End G'MIC interpreter.

onegraypixel
So.
But one also needs to be mindful that this “dynamic space” is coming from fixed, pre-existing images and be mindful of overrunning
Tee. Hee.

Maybe some additional notes about static and dynamic vectors in the MEP :

  • Vector variables, just as scalar variables are indeed allocated during the compilation time, not during the evaluation time. This means that MEP variables always have a const size (equivalent to double array[N] in C, where N is a constant integer). Storing vector variables like this prevents the MEP to deal with memory allocation/deallocation during expression evaluation, which is often not desired (at least for efficiency reasons). Indeed, most of the time, math expressions won’t require the use of ‘dynamic’ vector objects.

  • But, when you really have to deal with a dynamic-size vector, you have two solutions :

    1. Allocate a ‘big’ vector (like vector65536()) and store its ‘real’ size in a variable, then manage its values and size. Valid only if you have a reasonable bound for the vector size.

    2. Or, use the dar_* functions from math_lib (dar means dynamic array). This is based on the storage of array values inside an image, whose size is dynamically expanded if needed (done with function resize()). The trick here is that the implementation of functions dar_*() use the functions h(#ind) which returns the current size of the image #ind (while the constant value h#ind returns the initial size of the image #ind when enterin the MEP).
      Most of the time, in a math expression, the height h(#ind) is indeed a value that stays constant, so it’s more efficient (and short) to use h#ind rather than h(#ind), but with dar_*() of course, it’s not always the case.

1 Like

Following -eval, I gather that onegraypixel's dynamic allocation growth is (at least) to the nearest even float32 increment, and that the 0x0a value at position 1,15 in the image list representation is a ‘behind-the-curtains’ marker that (on the MEP side) indicates a vector10[…]. I also just gathered that dar_insert(…) has two argument signatures: dar_insert(ind,elt,pos) and dar_insert(ind,elt) and I was using the latter in -foo ( gmic_stdlib.gmic ):

25730:   # Inserts new element 'elt' at the end of dynamic array #ind.
25731:  dar_insert(ind,elt) = (
25732:    _dar_siz = dar_size(#ind);
25733:    _dar_siz>=h(#ind) - 1?resize(#ind,1,_dar_siz*2 + 2,1,s(#ind),0);
25734:    unref(_dar_elt); _dar_elt = elt;
25735:    copy(i[#ind,_dar_siz],_dar_elt,max(1,size(_dar_elt)),h(#ind),1);
25736:    (dar_size(ind#)) = ++_dar_siz;
25737:  );

I gather that the resize(…) call on 25733: dictates the ‘grow-by-row’ character of the change in image size of onegraypixel.

It was with this other flavor of resize(A, size, _interpolation, _boundary_conditions) that I became aware that parameters like size must be, at compile time, traceable back to a constant positive integer, else the JIT compiler is left at sea, and that started my ruminations on this Tutorial fragment (Fragmentary? indeed it is. Gentle Reader: “Tutorial Fragments” are just that, posted here so that people like @Reptorian, @David_Tschumperle and others can drop thought bombs on them. And so that I can figure out, eventually, what it is I’m supposed to write. Don’t come here expecting coherency…)

Another tutorial idea though brief. Assigning a random value as a const in the JIT compiler. Courtesy of David.

This one doesn’t work:

rep_rand_to_const:
f "begin(const rv=u(0,1););rv;"

However, this does work:

rep_rand_to_const:
rv={u(0,1)}
f "begin(const rv="$rv";);rv;"

Basically, this bypass issue.

Also, things like rot(u,v,w,angle) can also be express with rot_x, and rot_y, and these are what I use in place of rot(u,v,w,angle).

1 Like

Explain. Are these commands, macros, or…?

Macros. rot_x(a,b)=… or rot_x(a,b,c)=… The latter is used when you want non-constant rotation while the former is for constant rotation.

Thanks for the example!

In the second, working, case, command line parsing has the opportunity to substitute "$rv" prior to the invocation of the math expression parser (with JIT == ‘Just In Time’ mode…). JIT never ‘sees’ the "$rv" construct, but instead an explicit numeric string, the contents of command line variable "$rv". So, from the point of view of the math expression parser, that explicit numeric string, substituted from "$rv" before the JIT was invoked, qualifies as a constant scalar because it is a specific numeric quantity known at the outset of the compilation of the math expression.

In the first, non-working case, the math expression parser cannot possibly get a hint at compile time what the value of rv is going to be, since u(0,1) can’t possibly run until after compilation. This violates the terms of the const qualifier.

From a tutorial-writing perspective, the key question for the student is: ‘Will what I’m writing appear to the math expression parser as an explicit, numeric string at the time of compilation’?
If they are writing explicit strings like ‘3.14159’, the answer is ‘yes.’ If they are writing $foo, it is best to check what $foo represents: a simple numeric string (the answer would be ‘yes’) or an image storage variable (the answer would be ‘no’).

Cookbook on making mosaics in the offing.

gmic -input ampersand.svg r2dx. 50% -gtutor_tileit. 10,50,2,0,0,60,0,40 -output. ampersand_tiled.png should be available for play on your next update.

Here is the play file:
ampersand

Main topics:

  1. drawing with the math expression parser,
  2. oriented tiling
  3. further and continued misuse of structure tensors.

Motivating discussion at Deformation (warped fill)
Current implementation (gmic-community @537d948f6fc2 Sat May 8 11:04:21 2021 -0400 (EDT)

gtutor_tileit
#@cli gtutor_tileit : 0<=_disrupt<=10,0<_th,0.5<=_spread,0<=_soft,_fillholes,0<=_lightangle<=180,_basec,_offsc
#@cli : Generate a mosaic from a binary black & white image.
#@cli : disrupt=5      Twists and offsets tiles. Suggest 0-10.
#@cli : th=16          Tile height, absolute pixels. Fixme: Effect changes with image size. 
#@cli : spread=1.5     Size of gaps between tiles at corners, junctions and runs. Suggest 0-3.
#@cli : soft=0.5       Soften and dull shadows and highlights. Suggest 0-10.
#@cli : fillholes=1    True:  Fill gaps. No alpha channel.
#@cli :                False: Leave gaps transparent; image has alpha channel.
#@cli : lightangle=45  Degrees: 0-180. Azimuth from where light streams.
#@cli :                45° renders upper left lighting.
#@cli :	               Provisional coloring. FIXME: sample from input image instead.
#@cli : basec=-60      Provisional: base color, degrees. 0° = green
#@cli : offsc=90       Provisional: offset color, degrees ± angle from basec 0°: no figure-
#@cli :                ground distinction.
gtutor_tileit : -check "${1=5}>=0 && $1<=10 && ${2=16}>0 && ${3=1.5}>=0.5 && ${4=0.5}>=0 && isbool(${5=1}) && ${6=45}>=0 && $6<=180" -skip ${7=0},${8=90}

# Selected: black/white binary value shapes. To do: simple color field input images instead
# Parameters

-verbose -
disrupt=$1
th=$2
spread=$3
soft=$4
fillholes=$5   
lightangle=$6	               
basec=$7
offsc=$8

# Outline shapes
+deriche. 1,1,x
+deriche.. 1,1,y
-sqr[^0]
-add[^0]
-fill. 'i>3?1:0'
-channels. 0
-name. outline

# Cost: perturbs distance to outline measure, a basis of tile irregularity
[-1],[-1],1,1
-name. cost
-noise_perlin[cost] {10*$disrupt}
-normalize[cost] 0,{$disrupt/4.0}

# Distance to outline + distance perturbation
+distance[outline] 1,[cost],1
-name. distvar
+distance[outline] 1
+add[-1,-2]
-remove[distvar]

# True distance: basis for marking off distance by tile height
-name. truedistance

# Orient: source of tile rotation
-name.. orienter
-channels[^1] 0

# Make Warper: distorts tile shapes
-gradient[cost] xy
-append[-4,-3] c
-normalize... {-1.5*$disrupt},{1.5*$disrupt}
-name... warper

#  Measure out and scribe tile center-to-center lines
-round[truedistance] {$spread*$th}
+deriche. 1,1,x
-deriche.. 1,1,y
-sqr[-2,-1]
-add[-2,-1]
-fill. 'i>0.5?1:0'
-channels[truedistance] 0
 
# Compute structure tensors
# Make an inpaint patch to
# fill in uncertain ridge values
-structuretensors[orienter] 0
+norm[orienter]
-name. patcher
-fill[patcher] 'i<=2?1:0'

# Patch structure tensors
-inpaint[orienter] [patcher],0,1
-remove[patcher]

# Extract orientation angles
# from structure tensors
-eigen[orienter]

# Plotting field:
[-1],[-1],1,2,'[-1,0]'    
-name. plottingfield

# Draw tiles on a plotting field:
# (1) xfrm: encodes tile translations and rotations
# for tile corner points. (2) tl, tr, bl, br:
# tile corners, expressed in homogeneous
# coordinates.

-fill[plottingfield] ">if(
                           0<i#-2 && I#-1==[-1,0],
                           xfrm=eye(3);
                           xfrm[0]=I#-3[1];
                           xfrm[1]=I#-3[0];
                           xfrm[2]=x;
                           xfrm[3]=-I#-3[0];
                           xfrm[4]=I#-3[1];
                           xfrm[5]=y;
                           tl=mul(xfrm,[-"$th",-"$th"/2.0,1],1);
                           tr=mul(xfrm,[ "$th",-"$th"/2.0,1],1);
                           bl=mul(xfrm,[-"$th", "$th"/2.0,1],1);
                           br=mul(xfrm,[ "$th", "$th"/2.0,1],1);
                           polygon(#-1,5,
                                   [tl[0],tl[1]],
                                   [tr[0],tr[1]],
                                   [br[0],br[1]],
                                   [bl[0],bl[1]],
                                   [tl[0],tl[1]],
                                   1,[0,1]);
                           polygon(#-1,-5,
                                   [tl[0],tl[1]],
                                   [tr[0],tr[1]],
                                   [br[0],br[1]],
                                   [bl[0],bl[1]],
                                   [tl[0],tl[1]],
                                   1,0xffffffff,[1,1])
                         );I"
# Distort artwork
-warp[0,outline,plottingfield] [warper],1,2,2
-if {$fillholes==0}
   -shared. 0
   -round. 1
   -rm.
-else
   -remove_opacity.
   -round. 1
-fi
-dilate_oct. 2

# Clean up z-axis data shifts
-cut[0] 0,255
-threshold[outline] 50%
-shared[plottingfield] 0
-fill. 'i#1>0?1:i#-1'

# Make height map for lighting. 
# Distance from edges: proxy for
# tile height. Shape height with
# power function
-distance. 1
-normalize. 0,1
-pow. 0.25
-rm.

# Drop used data sets,
# Normalize for lighting
-remove[^0,-1]
-normalize[0,-1] 0,255
-if {s#-1>1}
   -split_opacity[-1]
   -fill. '[i<127?0:255]'
   -move. 0
-fi
 
# Cheap Fingerpaint Lighting
# Derive shadow, hightlight, color
# Fixme: provisional coloring. Ought to
# be sampled from input image.
-blur. {$soft},1,1
+gradient. xy
-append[-2,-1] c
({cos(45*pi/180)}^{sin(45*pi/180)})
-resize. [-2],[-2],[-1],[-1],1
+mul[-2,-1]
-compose_channels. add

# Shadow 
-cut. {ia},{iM}
-normalize. 0,1
-pow. 0.5
-blur. {$soft},1,1
-mul.. -1 
-mul[-3,-2]

# Highlight
-compose_channels.. add
-cut.. {ia#-2},{iM#-2}
-normalize.. 0,1
-oneminus..
-pow.. 2

# Colorize. To do: sample color
# from input. For now, just mapping
# and colorspace twisting.
-blur.. {$soft},1,1
-normalize[-3] 0,1
-oneminus[-3]
-normalize[-3] 127,255
-map[-3] algae

# Provisional coloring
# Translate black/white of input into base° ± offset°
# colorspace rotations
-fill[-3] "begin(wv=vector3(1/sqrt(3)));
          if(i(#-4,x,y,0,0),ang="$basec"+"$offsc",ang="$basec"-"$offsc");
          rot(wv,ang)*I#-3"

# Composite 
-mul[-3,-2]
-normalize. 0,255
-add[-2,-1]
-normalize. 0,255
-if {$fillholes==0}
   -move[0] {$!}
   -append[-2,-1] c
-fi
2 Likes

Great!
Just fill the white holes (if you like) with the area colour or a kind of supplementary tile.
It’s a common problem of other mosaic stroke paths methods