Splendid party. Here’s my ticket:
calispline.gmic
===
ribbons :
ribboncnt=48
-input 1024,1024,1,3,lerp([50,60,165],[65,140,225],y/(h-1))
-name. canvas
(\
40,70,{cexp([log(1000),-50°])};\
512,450,{cexp([log(2000),35°])};\
870,13,{cexp([log(1000),35°])}^\
130,970,{cexp([log(1000),-35°])};\
490,600,{cexp([log(2000),-135°])};\
1015,950,{cexp([log(1000),10°])}\
)
-name. boundrypts
-resize[boundrypts] 100%,$ribboncnt,100%,100%,5
-repeat $ribboncnt
-calispline[canvas] {crop(#$boundrypts,0,$>,0,0,4,1,1,1)},\
{crop(#$boundrypts,0,$>,0,1,4,1,1,1)},\
8,1,45,1,\
{235+15*$>/($ribboncnt-1)},\
{100-35*$>/($ribboncnt-1)},\
{25+225*$>/($ribboncnt-1)}
-done
-keep[canvas]
#@cli calispline : x0[%],y0[%],u0[%],v0[%],x1[%],y1[%],u1[%],v1[%],_rad0,rad1,_ang,_opacity,_color1,...
#@cli : Draw specified colored calligraphic line, tracing an underlying cubic
#@cli : hermite spline, on selected images. Default values: 'rad0=rad1=3',
#@cli : 'ang=0', 'opacity=1' and 'color1=0'.
#@cli : $ image.jpg repeat 30 { calispline {u(100)}%,{u(100)}%,{u(-600,600)},{u(-600,600)},{u(100)}%,{u(100)}%,\
# {u(-600,600)},{u(-600,600)},0.6,255 }
calispline : skip ${9=3},${10=3},${11=0},${12=1},${13=0}
e[^-1] "Draw a calligraphic spline from ($1,$2) tangent: [$3,$4] to ($5,$6) tangent: [$7,$8] on image$?, "\
"with pen shape: $9-$10, angle $11, opacity: $12 and color (${13--1})."
foreach {
x0={if(${"is_percent $1"},$1*(w-1),$1)}
y0={if(${"is_percent $2"},$2*(h-1),$2)}
u0={if(${"is_percent $3"},$3*(w-1),$3)}
v0={if(${"is_percent $4"},$4*(h-1),$4)}
x1={if(${"is_percent $5"},$5*(w-1),$5)}
y1={if(${"is_percent $6"},$6*(h-1),$6)}
u1={if(${"is_percent $7"},$7*(w-1),$7)}
v1={if(${"is_percent $8"},$8*(h-1),$8)}
r0,r1,ang,opac=${9-12}
eval ${-mysplines}"calispline(#0,["$x0","$y0"],["$u0","$v0"],["$x1","$y1"],["$u1","$v1"],$r0,$r1,$ang,$opac,[${13--1}])"
}
mysplines :
status "
calispline(ind,P0,T0,P1,T1,rad0,rad1,ang,opacity,color) = ( # Draw calligraphic line around spline P0-P1 with tangents T0,T1 on image #ind
unref(_ds_color);
unref(_ds_mask);
_P0 = P0;
_P1 = P1;
_rad0 = rad0;
_rad1 = rad1;
_ang = ang;
_opacity = opacity;
_ds_color = resize(color,s#ind)*=abs(_opacity);
_omopacity = 1 - max(_opacity,0);
_C = hermite_coef(_P0,T0,P1,T1);
_dt = _dtmin = 1/max(abs(_P1 - _P0));
_P0 = inf;
for (_t = 0, _t<=1, _t+=_dt,
_P = round(mul([_t^3,_t^2,_t,1],_C,2));
_dP = abs(mul([3*_t^2,2*_t,1,0],_C,2));
_dt = min(_dtmin,0.75/max(_dP));
if (_P0!=_P,
ellipse(#ind,_P[0],_P[1],_rad0,_rad1,ang,_opacity,_ds_color);
);
_P0 = _P;
);
nan;
);
hermite_coef(P0,T0,P1,T1) = ( # Given location/tangent pairs, return coefficients of cubic interpolating polynomial in Hermite form
C=mul([ 2,-2,1,1,-3,3,-2,-1,0,0,1,0,1,0,0,0 ],[ P0,P1,T0,T1 ],2);
);
"
===
===
$ gmic -command calispline.gmic -ribbons -output. ribbons.jpg,80
===
Remarks:
- spline ⇒ thickspline ⇒ calispline: The latter two are near identical variants of the first. ‘spline’ plots pixels as it goes along. With ‘thickspline’, @David_Tschumperle plots an entire ellipse instead, with
radius_1=radius_2=thickness/2
and angle equals zero; this garners the constant thickness spline stroke.calispline
goes just a tiny step further, allowing the major and minor ellipse radii to be set, along with the angle. That gives us @prawnsushi 's calligraphic effect, practically for no cost at all.
- math_lib syntax:
math_lib
syntax is just that of Mathematical expressions, no more, no less. So, “squeezing a repeat” inside ofeval
is just taking one of the math expression looping constructs — take your pick. In fact, (pro tip!!!) there is nothing special at all aboutmath_lib.
It is just a G’MIC custom command that returns a string as its status. So:
eval ${-foo}"do_foo(3+7)"
is a kind of, sort of, an inclusion mechanism, operating somewhat like the ‘C/C++’ preprocessor ‘#include’ directive. foo
is a GMIC custom command that returns a string in its status, i. e.:
foo:
u "My name is foo! How do you do?!?"
which is fine, but it would probably be more useful for our purposes if the string that foo
returns is a syntactically proper G’MIC math expression that defines a do_foo
macro. When the G’MIC command line interpreter encounters this construct: ${-foo}
, it evaluates the pipeline expression within the curly braces. -foo
executes and returns a (hopefully syntactically correct math expression) string. G’MIC concatenates this returned string with whatever follows, if anything. The concatenated string then becomes the argument for eval
, whose lot in life is to evaluate mathematical expressions, either once (when it has no selection decorator), or in iteration over the pixmaps of selected images — like fill
but without the implicit assignment of expression results to pixels.
The takeaway is that you can write your own math_lib
s. They may be either modified sections of math_lib
or whole cloth inventions. In many cases, you would rather not include math_lib
. It embodies a Big Math Expression String. Do you really want to be JIT compiiling that big Math Expression string within a long-running inner loop? Didn’t think so. So you take as small a piece as possible and just include that. And then you can modify that little piece to do fiendish tricks and not worry about breaking math_lib
. Take a look at the implementation of the custom command mysplines
in calispline.gmic
for a more practical example.
- What does
{cexp([log(1000),-50°])}
do?: It is a vector in radial form; with it, I can write the length of the vector and the angle it has with respect to a reference direction, andcexp()
resolves it into rectangular components; that way I can write tangent vectors for hermite cubics in an intuitive way:
$ gmic run 'rect={cexp([log(1000),-50°])}'
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./run/__run/ Set local variable 'rect=642.78760968653921,-766.04444311897782'.
[gmic]-0./ End G'MIC interpreter.
- Semi-opaque ellipses that overlap invariably create more opaque patches on the overlaps. I cannot see a way around that so long as we are plotting ellipses, one-by-one, directly. I suppose one could consider rendering at full opacity to a transparent intermediary image, then alpha-blend the intermediary, at the desired opacity, to a base plate. Further along, I’ve been contemplating capturing the hull of the pen’s traversal as it proceeds along the spline. Rather than paint/plot at every Δt, capture the offset points in a dynamic array. Then pass the hull in just one call to
polygon()
, in fill mode, which renders the entire stroke inCImg.
That would be pretty darn quick, me thinks.