Hello there,
I’ve written a lot of code these last days on a specific feature of the G’MIC framework, namely the math expression evaluator, and I guess it’s time for me to write a first report about the advances I’ve made.
This is still work in progress, so any comments are welcome to improve it before the release of version 1.6.9.
All this has been mainly motivated by this post by Martin (aka Souphead), a Gimpchat member. Basically, he wanted the math evaluator of G’MIC to be able to deal with complex numbers rather than only scalar numbers. So that’s what I’ve tried to improve
The cool thing is, I didn’t break anything, just added some new stuff to the math evaluator while keeping a full compatibility with the previous syntax. To get an idea of the work I’ve done, this required me to add approx 3500
lines of C++ code to the math parser (that has now 5000
loc instead of 1500
). So, this was not really a cakewalk!
So, a G’MIC math expression is now able to manage two different kinds of ‘objects’ : scalars and vectors.
A vector can have an arbitrary size, but its size must be known at ‘compile time’ (when the math expression is transformed into a serie of bytecodes before being evaluated). Basically, the vector dimension is a fixed constant and cannot be modified once a vector variable with that size has been declared. Of course, you can declare several vector variables having different dimensions.
A new vector can be constructed (and optionally assigned to a variable) using either the new operator [...]
, or the new function vector()
:
A = [ 1,2,3,4 ]; # Construct a 4-dimensional vector, with values (1,2,3,4)
or
B = vector4(1,2,3,4); # Does the same as 'A'
C = vector(1,2,3,4); # Does the same as 'A'
The vector()
function can be useful to construct large vectors, without having to type all the coefficients, like in this example:
A = vector256(0); # A 256-dimensional vector filled with '0'
Another example:
vector8(0,1,2); # This corresponds to the vector '[ 0,1,2,0,1,2,0,1 ]'
Well, as you see, that is quite easy. Now, you have to know that a math expression in G’MIC can thus return either a scalar, or a vector:
-
When a vector is returned in a substituting expression as
{formula}
, the expression substituted by the string composed of the vector values, separated by commas. For instance:$ gmic image.jpg -echo Dimensions={";[w,h,d,s]"} [gmic]-0./ Start G'MIC interpreter. [gmic]-0./ Input file 'image.jpg' at position 0 (1 image 512x512x1x3). [gmic]-1./ Dimensions=512,512,1,3
(I’ve put a ;
as the first character of the math expression here to make clear the substituting expression is a math formula rather than another possible substituting expression, see doc for more info).
-
And if you use a vector-valued math expression in the
-fill
or-input
commands, then it actually fills each of your image pixel directly with the corresponding vector. For instance,$ gmic 256,256,1,3,'[x,y,(x+y)/2]'
generates this 256x256 color image, with red=x
,green=y
and blue=(x+y)/2
:
Note that it was of course possible to do the same before, but with a more ‘cryptic’ expression (scalar-valued), for instance:
$ gmic 256,256,1,3,'!c?x:c==1?y:(x+y)/2'
This principle works also for all commands accepting math expressions to modify image values (so all arithmetic and boolean operators, etc…).
Note also that in an expression, you can access the pixel of an image directly as a vector value with expressions I
or I(x,y,z)
. Actually, all previous expressions that was defined to allow pixel access (as scalar values) have been extended to their vector-valued counterparts. You just have to use capital letters I
and J
rather than i
and j
to make them ‘vector-valued’.
Also, all operators and functions of the math evaluator have been extended as well to deal with vector-valued parameters. For instance, expression abs([ -1,-3,2,-4,5 ])
returns the vector [ 1,3,2,4,5 ]
.
Managing complex numbers : So, now, a complex number can be defined simply as a 2-dimensional vector [ real,imaginary ]
. I’ve added some new functions and operators to deal specifically with complex numbers, that is the operators **
(complex multiplication), //
(complex division), ^^
(complex exponentiation), **=
(self-complex multiplication), //=
(self-complex division), ^^=
(self-complex exponentiation), and the functions cabs()
(complex modulus), carg()
(complex argument), cexp()
(complex exponential), clog()
(complex logarithm), cconj()
(complex conjugate).
With these tools, writing the rendering of a Mandelbrot fractal becomes very simple. That’s the function you can try:
mandelbrot:
1024,1024,1,1,"
z = 2.4*[ x/w, y/h ] - 1.2;
for (iter = 0, cabs(z)<=2 && iter<256, ++iter, z = z**z + [ 0.4,0.2 ]);
iter
"
-n. 0,255 -map. 7
Other new functions: Some new functions have been also introduced specifically to deal with vector-valued arguments : sort(A,is_increasing)
(to sort the values of a vector A
in increasing or decreasing order), cross()
to compute the cross product between two 3d-vectors, size(A)
which returns the number of values in vector A
.
Some functions signatures have been also extended to accept vector-valued arguments, for instance you can access pixel values using vector-valued coordinates:
X = [ x,y,z ]; pixel_value = i(X);
or you can find the minimum value in a vector, like this
X = [ 3,5,7,1,1,2,8,3 ]; min_value = min(X);
The operator []
also allows to access a specific vector value now (a scalar):
X = [ 1,2,0,4 ]; zero = X[2];
(indexes starts from 0
, as in C++).
To import/export vector-valued variables from/to images, you can use the assignment operator, like this:
V = I[#1,0]; # Get first vector value of image [1].
V = sort(V); # Sort vector values.
I[#1,0] = V; # Put vector result back in image [1].
Well, that’s it for now. If you have ideas and suggestions about new functions I could add to the math evaluator, that would be great! Also I want to say that doing all this required a major rewrite of some of the math evaluator code. I took the opportunity to clean and optimize the code a lot. The added complexity makes the compiler a bit slower to compile the bytecode for the expression, but it generates a shorter bytecode, with more optimized strategies and the evaluation of the expression itself is finally faster than in the previous version.
Any comments ?
EDIT: I’ve been able to upload new pre-releases in the G’MIC web site, so you can even install the development version with the improved math parser, and play with it. Check this page.