github pinterf/masktools 2.3
Masktools v2.3

51 minutes ago

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, avx2 are 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 _ChromaLocation frame property of clip1 (frame 0), else uses the
    format's default, as AviSynth+ does: top_left for 4:4:0 and 4:1:0, mpeg2 for 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, xor as names of ^, &, |, @.
  • New context values, as in AviSynth+ Expr: width, height of the plane, sx, sy, sxr, syr (position in it),
    frameno, time. With offX, offY, w, h they 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, Layer and Overlay do. 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 _ChromaLocation other than left can 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, scalef are level conversions with the arithmetic of AviSynth+ Expr and ConvertBits, for every base from i8 to
    f32. scalef on chroma scales around the center: 128 scalef at 10 bit is 512 on chroma (was 514). Between a 10-16 bit
    base and float scaleb uses 255 steps: i10 940 scaleb is 0.92157 (was 0.91887). Unchanged: scaleb from 8 bit between
    integer depths, scalef on luma and RGB. They are for levels, not for differences, see the reference.
  • dup1..dup9: a b dup1 gives a b a as documented and as in Expr. It gave a a a (since 2.2.25).
  • A constant power of 0.5 is sqrt, as in Expr: x 0.5 ^ on a negative x gives 0, was NaN. sqrt of a negative or NaN is 0.

Other filters

  • mt_logic "andn": ~x & y everywhere, as the SIMD code always computed it. The C code computed x & ~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, like thY1, thY2. On float YUV and float RGB an explicit value
    with the default paramscale went 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_expr on 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_expr is 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 bit use_expr ran 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_polish and mt_infix are 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" with thY/thC below 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=false at 10-32 bit: wrong output.
  • mt_luts at 16 bit with weights (wexpr) and realtime=false on 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.0000001 gave 0) and to 19 characters. Integers above
    2147483647 are accepted. Numbers are read correctly rounded, independent of the locale.
  • mt_infix: dup and swap expand correctly (x dup * gives (x*x)).

Same result on every CPU

  • mt_edge prewitt, hprewitt, cartoon and 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=65 at 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. YV12 offX=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 Prefetch the chroma of a frame could take the scene change decision of another frame. The decision
    belongs to each frame now; the filter stays MT_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_inputs crashed (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 mode as 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 "). 1e300 crashed.
  • mt_infix: an invalid expression ("", "x +", "x y") crashed or gave garbage.
  • use_expr with offX, offY, w or h: one clear message, only the full frame can be passed to Expr. With use_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, the cplace and
    luma mask rules, and how scaleb/scalef convert levels. It replaces the former list of differences to MaskTools 2.0b1;
    the original 2.0 manual is kept as mt_masktools.html.
  • README.md: an overview, the change log, and build instructions for Visual Studio 2026, MinGW, llvm-mingw and Linux.

Don't miss a new masktools release

NewReleases is sending notifications on new releases.