MaskTools2 2.3
DLLs for x64 and Win32 (ClangCL, MSVC_XP) and Windows ARM64 (llvm-mingw); Note.txt tells which one to use.
For AviSynth+ and classic AviSynth 2.6.
New: a complete reference of every filter and parameter, masktools2.md.
The full history since 2.0 is in README.md.
Scripts that need a change
sse2,sse3,ssse3,sse4,avx,avx2are replaced by one debug parameter,opt:
0 auto, 1 C, 2 SSE2, 3 SSE4.1, 4 AVX, 5 AVX2; on aarch64 1 C, everything else NEON. Naming an old one is an error
("does not have a named argument"). The parameters after them (A,alpha,paramscale,stacked,realtime,
use_expr...) move 5 positions forward, so a call giving that many arguments by position needs a change too.
Additions
Formats
- YA (greyscale + alpha) at 8-16 bit and float, with the alpha controls and lut expressions.
- YUV(A) 4:1:1, 4:4:0 and 4:1:0 at all bit depths, including mt_merge's luma masks. 4:1:1 above 8 bit and with alpha needs a
recent AviSynth+ (interface V13).
Filters and modes
- mt_edge
"sobel_real": the real Sobel,sqrt(Gx² + Gy²)of the 1-2-1 derivatives, at every bit depth. The historical
"sobel"and the default mode are unchanged. - mt_merge
cplace: new default"auto". It reads the_ChromaLocationframe property ofclip1(frame 0), else uses the
format's default, as AviSynth+ does:top_leftfor 4:4:0 and 4:1:0,mpeg2for the others. An explicit name overrides both. - mt_merge: low-pass luma masks for 4:1:1 and 4:1:0, as in AviSynth+ Overlay: a triangle (1-3-5-7-7-5-3-1) where chroma is
centred, a tent (1-2-3-4-3-2-1) where it is co-sited.
Expressions (mt_lut family, mt_polish, mt_infix)
- New operators:
sqrt,neg,sgn,not;pow,and,or,xoras names of^,&,|,@. - New context values, as in AviSynth+ Expr:
width,heightof the plane,sx,sy,sxr,syr(position in it),
frameno,time. WithoffX,offY,w,hthey refer to the box.
Platforms
- aarch64 with NEON, on Linux and on Windows (llvm-mingw). The NEON code gives the same integer results as C.
Changes that can alter the output of existing scripts
mt_merge
- Integer clips: the blend divides exactly by the maximum, as AviSynth+
Merge,LayerandOverlaydo. It divided by 256
(2^bits), with only the end points corrected. - 4:1:1 with
luma=true: the low-pass masks above. It was the average of the 4 luma pixels. - 4:2:2 with
cplace="top_left": the 1-2-1 mask, as"mpeg2", the horizontal siting being the same. It was the box of"mpeg1". - A clip tagged with a
_ChromaLocationother thanleftcan get another mask than with the old fixed"mpeg2"default.
Expressions
%is the floating point remainder, as in Expr:5.7 2.2 %= 1.3,-5.7 2.2 %= -1.3. It was the integer remainder of the
rounded operands (0 here). Non-negative integer operands give the same result as before.x 0 %is NaN (0 at integer depths).scaleb,scalefare level conversions with the arithmetic of AviSynth+ Expr and ConvertBits, for every base fromi8to
f32.scalefon chroma scales around the center:128 scalefat 10 bit is 512 on chroma (was 514). Between a 10-16 bit
base and floatscalebuses 255 steps:i10 940 scalebis 0.92157 (was 0.91887). Unchanged:scalebfrom 8 bit between
integer depths,scalefon luma and RGB. They are for levels, not for differences, see the reference.dup1..dup9:a b dup1givesa b aas documented and as in Expr. It gavea a a(since 2.2.25).- A constant power of 0.5 is
sqrt, as in Expr:x 0.5 ^on a negativexgives 0, was NaN.sqrtof a negative or NaN is 0.
Other filters
- mt_logic
"andn":~x & yeverywhere, as the SIMD code always computed it. The C code computedx & ~y: on narrow planes,
at the row ends of some widths, and when C was forced. - mt_edge, mt_motion
thC1,thC2: scaled as a difference, likethY1,thY2. On float YUV and float RGB an explicit value
with the defaultparamscalewent negative and made the chroma mask all 1;paramscale="f32"on integer YUV was wrong too. - mt_luts
"avg", mt_lutf"average"on Linux and other LP64 builds, 8 and 16 bit: rounded again, it truncated. - mt_convolution, even vertical kernels: the last rows use the clamped bottom row (was the row above it).
- mt_motion with luma not processed: the luma plane still decides on a scene change for the whole frame. It read an
uninitialized value. - mt_lut with
use_expron stacked clips: runs the internal code. Expr computed the 8 bit double height clip. use_expr: frame properties come from Expr, from the first clip the expression uses; a constant expression gives none.
Performance
use_expris as fast as Expr itself. Expr is set up once, when the filter is created, and takes the place of the filter.
It was set up again for every frame: at 1080p 10 bituse_exprran at 17% of plain Expr's speed, at 320x240 at 3%.- More AVX2: mt_binarize, mt_motion, mt_average and the morphological filters at all depths. mt_logic at 10-16 bit now
actually uses its AVX2 code (it was never selected). - mt_merge: SSE2, AVX2 and NEON for every luma mask, the new 4:1:1, 4:4:0 and 4:1:0 ones included.
- Stacked 16 bit is processed as native 16 bit internally, so the SIMD code serves it too.
Platforms, builds, interface
- Windows ARM64 build with llvm-mingw. It needs an AviSynth+ built with llvm-mingw too.
- Builds with Visual Studio 2026 (MSVC, ClangCL), the v141_xp toolset, MinGW GCC/Clang and llvm-mingw; Linux x86-64 and aarch64
with GCC or Clang. macOS is supported by the CMake project but not tested. - The minimum host is classic AviSynth 2.6; AviSynth 2.5 is no longer supported. Tested on classic 2.6.0 final.
- Boost is no longer needed:
mt_polishandmt_infixare in every build, XP and Linux included. - One function signature per filter, float parameters as float. The integer duplicates ("dual signatures", against an old
integer-only MaskTools loaded at the same time) are gone; integer arguments still work. - The host's interface version is checked once, at plugin load.
Bug fixes
Wrong results in normal use
- YV411 (4:1:1): chroma was processed as 4x wide and 1/4 high (swapped ratios): wrong output in every filter, rows left
unwritten (since 2.2.1). - mt_expand, mt_inpand
"square"withthY/thCbelow the maximum: the limit applies once. In SIMD code the result could
go up to twice the limit away from the source pixel. - mt_mappedblur at 10-16 bit and float, MSVC builds (the release DLLs): the mask was read from wrong rows, partly from the row
padding. - mt_hysteresis
corners=falseat 10-32 bit: wrong output. - mt_luts at 16 bit with weights (
wexpr) andrealtime=falseon aarch64: the weights were ignored. - mt_luts float with weights: an uninitialized clamp setting could change the result depending on memory contents (in 2.2.30).
- mt_polish:
"!&"gives the&!(and-not) the filters know (since 2.2.4). Numbers are written in their shortest exact form
with.whatever the locale; they were cut to 6 decimals (0.0000001gave 0) and to 19 characters. Integers above
2147483647 are accepted. Numbers are read correctly rounded, independent of the locale. - mt_infix:
dupandswapexpand correctly (x dup *gives(x*x)).
Same result on every CPU
- mt_edge
prewitt,hprewitt,cartoonand custom kernels at 10-16 bit: values above the maximum gave too large a value
or 0 in the SIMD code; now the maximum, as in C. - 8 bit mt_edge and the morphological filters: a plane exactly one vector wide (16, or 32 with AVX2 pixels) got wrong edge
pixels; such planes use the C code. - mt_motion at 8 bit on very large frames: the scene change limit overflowed (from
thT=65at 7680x4320), no scene change was
detected. On very wide planes the scene change sum overflowed (from width 32776 at 16 bit).
Crashes and reads outside the frame
- Boxes (
offX,offY,w,h): a box empty after snapping to the chroma grid (e.g. YV12offX=0, w=1) crashed every
filter; now nothing is processed. mt_hysteresis wrote past the frame end with a box. mt_lutspa read outside its table with
boxes not aligned to the chroma grid, the alpha plane of YUVA 4:2:0, 4:2:2 and 4:1:1 included. - mt_edge, mt_mappedblur, mt_expand, mt_inpand, mt_inflate, mt_deflate: planes or boxes 1 pixel wide or high were read outside
the plane, and 1 pixel high ones got a row written below them. - mt_expand, mt_inpand SIMD (
square,horizontal,both): read up to a vector past the end of the row when the width was
not a multiple of the vector. - mt_edge custom kernels in SSE2/SSE4.1: planes 8 or 16 pixels wide were read outside the plane; they use the C code now.
- mt_convolution: works on planes and boxes smaller than the kernel. A 1 pixel wide box corrupted the heap, smaller clips
were an error.
Multithreading
- mt_motion: under
Prefetchthe chroma of a frame could take the scene change decision of another frame. The decision
belongs to each frame now; the filter staysMT_NICE_FILTER. - mt_hysteresis: unstable with multithreading (one shared buffer).
Instructions the CPU does not have
- mt_binarize at 10-16 bit
"t x"and"x t": needed SSE4.1 in the SSE2 code, now SSE2. - mt_merge float with
luma=true: needed SSE3 in the SSE2 code.
Memory
- mt_lutsx did not free its pixel list.
- mt_lutxyza, mt_lutxy, mt_lutf, mt_luts with
realtime=false: an error message when the table cannot be allocated
(4 GB for mt_lutxyza, 8 GB at 16 bit), instead of an unhandled exception.
Parameter and expression checks
These were accepted before and gave a wrong result, a crash or a read past a list; now they are errors with the reason.
- Expressions are checked when the filter is created, as in Expr: not enough operands (
1 +) or more than one value left
at the end (1 2). Before, only the 8-16 bit tables caught missing operands; realtime and float gave 0, leftover values
were ignored. - mt_luts, mt_lutsx, mt_lutspa: an invalid expression or
scale_inputscrashed (heap corruption) instead of giving the error. - mt_luts, mt_lutsx
pixels: an empty list or an odd number of values. Empty crashed in"avg"mode (the default), odd read
past the list. - mt_expand, mt_inpand
modeas a coordinate list: an odd number of values (it read past the list). A misspelled mode name,
read as such a list, is an error now too. - mt_polish: an expression that cannot be converted to its end ("syntax error at position n"); it returned the part converted
so far ("x y"gave"x ").1e300crashed. - mt_infix: an invalid expression (
"","x +","x y") crashed or gave garbage. use_exprwithoffX,offY,worh: one clear message, only the full frame can be passed to Expr. Withuse_expr,
a bad expression is reported when the script loads, not on the first frame.
Documentation
- masktools2.md: the complete
reference of every filter and parameter, with the expression language, a comparison with AviSynth+ Expr, thecplaceand
luma mask rules, and howscaleb/scalefconvert levels. It replaces the former list of differences to MaskTools 2.0b1;
the original 2.0 manual is kept asmt_masktools.html. - README.md: an overview, the change log, and build instructions for Visual Studio 2026, MinGW, llvm-mingw and Linux.