G'MIC Tutorial Fragments

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

Or use the fillholes=1 parameter, to stuff the holes with odd-shaped tiles. In that case, the image won’t have an alpha channel. With an alpha channel (and holes) you can use Gimp, G’MIC, or whatever floats your boat to composite the tile image over something that looks like cement, or bricks or whatever.
Making spread larger generates larger holes. Making spread very large gives rise to gaps between tile rows. Play with disrupt, th and spread for a variety of tilings.

Just noticed that lightangle is broken and fixed at 45°. Didn’t map the command line variable to the implementation code - silly mistake. Fix it next commit, today or tomorrow.

gtutor_tileit:
      0<=_disrupt<=10,0<_th,0.5<=_spread,0<=_soft,_fillholes,0<=_lightangle<=180,_basec,_offsc

    Generate a mosaic from a binary black & white image.
    disrupt=5      Twists and offsets tiles. Suggest 0-10.
    th=16          Tile height, absolute pixels. Fixme: Effect changes with image size.
    spread=1.5     Size of gaps between tiles at corners, junctions and runs. Suggest 0-3.
    soft=0.5       Soften and dull shadows and highlights. Suggest 0-10.
    fillholes=1    True:  Fill gaps. No alpha channel.
                   False: Leave gaps transparent; image has alpha channel.
    lightangle=45  Degrees: 0-180. Azimuth from where light streams.
                   45° renders upper left lighting.
                   Provisional coloring. FIXME: sample from input image instead.
    basec=-60      Provisional: base color, degrees. 0° = green
    offsc=90       Provisional: offset color, degrees ± angle from basec 0°: no figure-
                   ground distinction.

OK, thanks for the clarification, waiting to see it available on Gimp-Gmic repository, and extended to act on any posterized image (n colored areas, not only black and white). Great job!

gtutor_tileit has been updated in preparation for an upcoming cookbook article on tiling surfaces, primarily a math expression oriented recipe. Commit 05a01d18575c has the current script. Sun May 16 14:57:19 2021 UTC. It will work its way to the server shortly for people with current gmic cli. No gimp/Krita/8bf plug in yet. Cookbook article first. Commit notes:

modified:   include/garry_osgood.gmic

gtutor_tileit:
1. Removed provisional coloring. Mosaics read color and luminosity from selected images
   See ccount. Parameters basec and offsc have been removed.
2. Generate tilings with or without light/shadow rendering. Use fcolor switch.
   Defaults to False: render light/shadow on tiles.
3. Set tile size relative to image size. relative size 1.0 → 2.5% of the image diagonal.
4. Bug: lightangle not being used. Fixed. Set light angle from -180° to 180° inclusive.

gtutor_tileit help:
Generate a mosaic from an image. Works best with line art cartoons with flat color.
disrupt=5      Rotates, scales and displaces tiles. Suggest 0-10.
tsize=1.0      Tile size relative to image. 1.0 → tile diagonal is 2.5% of the image diagonal.
spread=1.5     Increasing promotes dropped tiles, gaps at junctions and runs. Suggest 0-3.
soft=0.5       Soften and dull shadows and highlights. Suggest 0-5.
fillholes=1    True:  Fill gaps and holes. No alpha channel.
               False: Leave gaps and holes. Image has alpha channel.
lightangle=45° Degrees: -180° – 180°. Lighting angle.
               45°: Light appears to stream from viewer's upper left.
ccount=4       Set number of dominant colors, taken from those most frequently occurring in source.
fcolor=0       True:  Flat color tiling without lighting and shading.
               False: Do tile lighting and shading.

Play files:

  1. ampersand_bluegold.svg. Continues my ongoing obsession(s) with the Goudy 1911 ampersand.
    ampersand_bluegold
  2. pompeiimask.svg. Loosely based on theatre mask mosaics seen in some villas in the Roman city of Pompeii.
    pompeiimask

Some results:

  1. Defaults
    pompeiimask_tiled
    Disruption: 5. Tile size: 1.0. Tile spread: 1.5. Highlight softening: 0.5. Holes: filling. Light angle: 45° Color count: 4 and shaded color.

  2. Smaller tiling and more colors
    pompeiimask_tinytiles
    Disruption: 0.25. Tile size: 0.25. Tile spread: 0.75. Highlight softening: 8. Holes: leaving. Light angle: 90° Color count: 16 and shaded color.

  3. Increasing disruption and shrinking tile size, also blurring to introduce intermediary color tiles. Flat, unshaded color (Light angle, soft: accepted but ignored).
    amp_flatnoisytiles
    Disruption: 5. Tile size: 0.25. Tile spread: 0.5. Highlight softening: 8. Holes: leaving. Light angle: 135° Color count: 32 and unshaded color.

  4. Setting spread to a high value creates beaded effect. Consider compositing such beaded results with other tiled images of the same source, but made with different settings.
    amp_widespread
    Disruption: 0.125. Tile size: 0.25. Tile spread: 2. Highlight softening: 0. Holes: leaving. Light angle: -75° Color count: 32 and shaded color.

gtutor_tileit: 05a01d18575c Sun May 16 14:57:19 2021 UTC

“”"
#@cli gtutor_tileit : 0<=_disrupt<=10,0.05<_tsize,0.25<=_spread,0<=_soft,_fillholes:True,-180<=_lightangle<=180,2<=_ccount<=32,_fcolor:False
#@cli : Generate a mosaic from an image. Works best with line art cartoons with flat color.
#@cli : disrupt=5 Rotates, scales and displaces tiles. Suggest 0-10.
#@cli : tsize=1.0 Tile size relative to image. 1.0 → tile diagonal is 2.5% of the image diagonal.
#@cli : spread=1.5 Increasing promotes dropped tiles, gaps at junctions and runs. Suggest 0-3.
#@cli : soft=0.5 Soften and dull shadows and highlights. Suggest 0-5.
#@cli : fillholes=1 True: Fill gaps and holes. No alpha channel.
#@cli : False: Leave gaps and holes. Image has alpha channel.
#@cli : lightangle=45° Degrees: -180° – 180°. Lighting angle.
#@cli : 45°: Light appears to stream from viewer’s upper left.
#@cli : ccount=4 Set number of dominant colors, taken from those most frequently occuring in source.
#@cli : fcolor=0 True: Flat color tiling without lighting and shading.
#@cli : False: Do tile lighting and shading.
gtutor_tileit : -check “${1=5}>=0 && 1<=10 && {2=1.0}>=0.05 && {3=1.5}>=0.25 && {4=0.5}>=0 && isbool({5=1}) && {6=45}>=-180 && {6}<=180 && {7=4}>=2 && {7}<=32 && isbool({8=0})”
-echo[^-1] "Applying faux tiling to selected images. Disruption: “{1}". Tile size: "{2}”. Tile spread: “{3}". Highlight softening: "{4}”. Holes: "${arg\ 1+!5,filling,leaving}". Light angle: "{6}“° Color count: “{7}" and "{arg\ 1+!$8,unshaded,shaded}” color.”

Parameters

disrupt=$1
tsize=$20.025sqrt(w^2+h^2)
spread=$3
soft=$4
fillholes=$5
lightangle=$6
ccount=$7
fcolor=$8

-repeat ! -local[>]
# Color quantization + black
-remove_opacity.
+colormap. ${ccount},1,2
-index… .,0,1
-round 1
1
-append[-2,-1] x
-name. pallette
-move[pallette] 0
-name. qcolor
+luminance[qcolor]
-name. qlumin
-normalize[qlumin] 0.6,1

  # Outline qcolor
  -input 100%,100%,1,1,'I(#-2,x,y)==I(#-3,0,0)?1:0'
  +deriche. 1,1,x
  -deriche.. 1,1,y
  -sqr[-2,-1]
  -add[-2,-1]
  -fill. 'i>={iM/3.0}?1:0'
  -name[-1] outline

  # Cost: perturbs distance to outline measure, a basis of tile irregularity
  -input 100%,100%,1,1
  -name[-1] cost
  -plasma[cost] 4,4,{$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[-3,-2]

  # True distance: basis for marking off distance by tile height
  [-1]
  -name[-1] truedistance

  # Orient: source of tile rotation
  -name[-2] orienter

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

  #  Measure out and scribe tile center-to-center lines
  -round[truedistance] {$spread*$tsize}
  +deriche[truedistance] 1,1,x
  -deriche[truedistance] 1,1,y
  -sqr[-2,-1]
  -add[-2,-1]
  -fill. 'i>{iM/4.5}?1:0'

  # Compute orientation field
  -gradient[orienter] xy
  -append[-3,-2] c
  -orientation[orienter]

  # Plotting field:
  [-1],[-1],1,4,'[-1,-1,-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,-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,[-"$tsize",-"$tsize"/2.0,1],1);
                             tr=mul(xfrm,[ "$tsize",-"$tsize"/2.0,1],1);
                             bl=mul(xfrm,[-"$tsize", "$tsize"/2.0,1],1);
                             br=mul(xfrm,[ "$tsize", "$tsize"/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,[[I#-7]*i#-6,255]);
                             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,[0,0,0,255])
                           );I"

  -cut[plottingfield] 0,255

  # Make isovalue targets for lighting height map
  100%,100%,1,1,'I#-1==[0,0,0,255]?1:0'
  -name. heightmap

  # Distort artwork
  -warp[plottingfield,heightmap] [warper],1,2,2

  # Clean up
  -remove[-3--6]
  -split_opacity[plottingfield]
  -index[plottingfield] [pallette],0,1
  -remove[pallette]
  -fill.. 'i>=127?255:0'
  -fill. 'i>0.05?1:0'
  -fill. 'i#-2==0?0:i'
     
  -if $fcolor # Do light and shade?
     -if $fillholes
        # Fill holes in the plotting field
        -fill[plottingfield] 'i#-2==0?I#-5*i#-4:I'
    -remove[-2]
 -else
    -append[-3,-2] c
 -fi
     -keep[plottingfield]
  -else
     # Distance from edges: proxy for
     # tile height. Shape height with
     # power function
     -distance[heightmap] 1
     -normalize[heightmap] 0,1
     -pow. 0.25
     -blur. 0.875,1,1
     -normalize[heightmap] 0,1

     -if {$fillholes==0}
        # Poke holes in the height map
        -fill[heightmap] 'i#-2==0?0:i'
     -else
        # Fill holes in the plotting field
        -fill[plottingfield] 'i#-2==0?I#-5*i#-4:I'
     -fi
     -remove[qcolor,qlumin]

     # Cheap Fingerpaint Lighting
     # Derive shadow, hightlight, color

     -normalize[plottingfield] 0,1

     # Light
     +gradient[heightmap] xy
     -append[-2,-1] c
     ({cos($lightangle*pi/180)}^{sin($lightangle*pi/180)})
     -resize. [-2],[-2],[-1],[-1],1
     +mul[-2,-1]
     -compose_channels. add
     -name. light
     -cut. {ia},{iM}
     -normalize. 0,1
     -pow. 0.5
     if $soft>0
        -blur. {$soft},1,1
     fi

     # Shadow
     -mul.. -1
     -mul[-3,-2]
     -compose_channels.. add
     -name.. shadow
     -cut.. {ia#-2},{iM#-2}
     -normalize.. 0,1
     -oneminus..
     -pow.. 2

     # Composite
     -mul[plottingfield,shadow]
     -mul[light] '{iM#0}'
     -add[plottingfield,light]
     -normalize[plottingfield] 0,255
     -if {$fillholes==0}
        -append[-3,-2] c
     -else
        -remove..
     -fi
     -keep[0]
  -fi # else doing light and shade

-endlocal
-done

“”"

Off to work on the cookbook recipe. Everybody have fun.

3 Likes

Pixl.us G’MIC rumbles - These need tutorial support somewhere

Only for .png and .tiff files.

1 Like