-
Notifications
You must be signed in to change notification settings - Fork 7
Expr
akarin.Expr
is an enhanced version of std.Expr
.
- New math operators
-
sin
,cos
: also present instd.Expr
version R54 (merged PR #693) -
%
: fmod operator -
clip
andclamp
:x 16 235 clip
andx 16 235 clamp
are both equivalent tox 16 max 235 min
.clip
is used by AVS+ Expr filter. -
trunc
,round
,floor
-
- New constant operators
-
N
to access the current 0-based frame number -
X
andY
to access the current pixel's absolute coordinate (note: in chroma planes, the number includes subsampling, i.e. for YUV420,X
in chroma planes only ranges from 0 to width/2-1. -
width
andheight
: access the plane width and height. - Frame property access: use
x.PropertyName
to access clip x's frame propertyPropertyName
.
-
- Temporary variables: these variable does not preserve across pixels though, as limited by the SIMD nature of the implementation.
-
var@
to load a local variable onto stack. -
var!
to store the top of the stack to variablevar
and pop it from the stack. (Equivalent to AVS+'sA^
, orA@ pop
)
-
- Stack operations
-
dropN
that removes the top N items.drop
is an alias ofdrop1
.
-
-
sortN
sorts the top N elements of the stack. - Relative Pixel Access:
x[relX,relY]
.- -width < relX < width and -height < relY < height
- by default, out of bound pixel accesses are clamped to the edge. i.e.,
x[relX,relY]
access the pixel at location[clamp(X+relX,0,width-1),clamp(Y+relY,0,height-1)]
. - mirror boundary condition enabled with
boundary=1
orx[relX,relY]:m
.
There are two implementations in the repository, which is selected at the build time.
- legacy version: using the same JIT framework as
std.Expr
- Feature limits to:
sin
,cos
,N
,X
,Y
,pi
,trunc
,round
, frame property access features. - No longer actively developed. As the LLVM version is better in every aspect.
- Feature limits to:
- LLVM version (lexpr): using LLVM framework.
- Actively developed
- Works on more than x86 platforms
- Supports all enhancements
- Can handle much larger/complicated expressions than the legacy version.
AVS+ Expr | Meaning | lexpr |
---|---|---|
W |
read local variable W
|
W@ |
W@ |
write local variable W , without pop |
dup W! |
W^ |
write local variable W
|
W! |
sx or sy
|
absolute X / Y |
X or Y
|
sxr or syr
|
relative X / Y |
X width / or Y height /
|
frameno |
frame number | N |
^ |
power | pow |
One example for this is to implement Adaptive Graining (aka adg
) with fully customized formula.
stat = core.std.PlaneStats(src)
grain = core.grain.Add(core.std.BlankClip(src), var=1)
# simple example adapting grain strength to average brightness of the frame
adged = core.akarin.Expr([stat,grain], ['x.PlaneStatsAverage y * x +', ''])
It could be used to optimize some uses of std.FrameEval
that needs to access the frame statistics, for example:
https://github.com/AmusementClub/mvsfunc/blob/0b2c6a84de789d9483762fa88a813680f151980c/mvsfunc.py#L1293-L1301
The syntax of this feature is modeled after the Forth programming language. And it's not compatible with the Internal Variable feature of AviSynth+'s Expr filter for a reason: I have already settled on N
, X
and Y
and the AVS+ limits variables to A-Z only, which is also too limiting (I regard the use of variable names as a form documentation, so longer names definitely help.) This implementation does not limit the length of the name, as long as it does not contain space, any identifiers can be used (in fact, you can use a+b
as identifier.)
This feature is not essential in the sense that you can always use swap
to access temporary variables stored at the bottom of the stack. But that approach does not scale well: it is hard to add a new variable (potentially all swap
indices need to be changed) and too hard to read.
A complete example plotting the sin
function:
import vapoursynth as vs
core = vs.core
core.std.BlankClip(format=vs.GRAY8).akarin.Expr(
'height 2 / halfh! ' # save half height for later reuse
'X width / N 20 / + pi 2 / * ' # compute input to sin: (X/width + N/20) * pi/2
'sin -1 * ' # flip sign of sin (in math, we want lower left corner to be the origin, not the upper left corner)
'halfh@ * halfh@ + ' # adjust result to height of the clip
'round Y = 255 0 ?' # round to integer and see if it's equal to current Y
).set_output()
We can easily use the relative pixel access feature to implement custom convolution kernels.
In fact, benchmark showed that 3x3 convolution implemented this way has performance on par with std.Convolution
.
boxfilter3x3 = lambda c: c.akarin.Expr(
'x[-1,-1] x[-1,0] x[-1,1] + + ' # left column
'x[0,-1] x[0,0] x[0,1] + + ' # middle column
'x[1,-1] x[1,0] x[1,1] + + ' # right column
'+ + 9 /'
)
If you want the edge to wrap around instead of clamping, add boundary=1
to the akarin.Expr
call.
Reimplementation of std.FlipHorizontal
and std.FlipVertical
:
flipH = lambda c: c.akarin.Expr(f'x[-{c.width},0]:m')
flipV = lambda c: c.akarin.Expr(f'x[0,-{c.height}]', boundary=1) # either use :m or set boundary=1 for mirrored boundary
flipHV = lambda c: c.akarin.Expr(f'x[-{c.width},-{c.height}]:m') # faster than c.std.FlipHorizontal.std.FlipVertical
Please note that the relative coordinates [relX,rely]
must be static constants and they have to satisfy:
-width <= relX <= width
and -height <= relY <= height
(the filter will not enforce this constraint, but if it's violated, funny thing might happen.)
For example, core.rgvs.RemoveGrain(1)
can be implemented as follows:
#core.rgvs.RemoveGrain(c, 1).set_output(0)
core.akarin.Expr(c,
'x[-1,-1] x[0,-1] x[1,-1] ' # previous row
'x[-1,0] x[1,0] ' # current row
'x[-1,1] x[0,1] x[1,1] ' # next row
'sort8 ' # sort the top 8 items, now the stack becomes (bot) 7 6 5 4 3 2 1 0 (top)
'dup7 ' # make a copy of the max value to the top, 7 6 5 4 3 2 1 0 7
'max! ' # store it to the `max` variable and pop it, 7 6 5 4 3 2 1 0
'dup0 min! ' # similarly store the minimum value to the `min` variable
'drop8 ' # remove the remaining 8 items
'x min@ max@ clamp' # clamp the current pixel to be within the range [min, max]
).set_output(1)
(To implement RemoveGrain(i)
0<i<5, just replace the dup7
with dup(8-i)
and dup0
with dup(i-1)
.)
Besides allowing arbitrary stencil, this implementation also supports int24/int32 and float32 pixel types.
See my weird scripts collection https://github.com/AkarinVS/weird-scripts/tree/master/lexpr.
- Mandelbrot zoomer: translated from AVS+ Expr Example, original code and idea on doom9.