├── .gitignore ├── meson.build ├── msvc_project ├── AdaptiveBinarize.vcxproj.filters ├── AdaptiveBinarize.sln └── AdaptiveBinarize.vcxproj ├── LICENSE ├── README.md ├── .github └── workflows │ └── build.yaml └── src └── adaptivebinarize.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | msvc_project/.vs 2 | msvc_project/x64 3 | *.user -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('adaptivebinarize', 'cpp', 2 | default_options: ['buildtype=release', 'warning_level=2', 'b_lto=true', 'b_ndebug=if-release', 'cpp_std=c++17'], 3 | license: 'MIT', 4 | meson_version: '>=0.51.0', 5 | version: '14' 6 | ) 7 | 8 | cxx = meson.get_compiler('cpp') 9 | 10 | gcc_syntax = cxx.get_argument_syntax() == 'gcc' 11 | 12 | if gcc_syntax 13 | vapoursynth_dep = dependency('vapoursynth', version: '>=55').partial_dependency(compile_args: true, includes: true) 14 | install_dir = vapoursynth_dep.get_variable(pkgconfig: 'libdir') / 'vapoursynth' 15 | else 16 | vapoursynth_dep = [] 17 | install_dir = get_option('libdir') / 'vapoursynth' 18 | endif 19 | 20 | sources = [ 21 | 'src/adaptivebinarize.cpp' 22 | ] 23 | 24 | libs = [] 25 | 26 | shared_module('adaptivebinarize', sources, 27 | dependencies: vapoursynth_dep, 28 | link_with: libs, 29 | install: true, 30 | install_dir: install_dir, 31 | gnu_symbol_visibility: 'hidden' 32 | ) 33 | -------------------------------------------------------------------------------- /msvc_project/AdaptiveBinarize.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Julek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AdaptiveBinarize 2 | 3 | Adaptive Binarize for Vapoursynth, based on [OpenCV's Adaptive Thresholding](https://docs.opencv.org/5.x/d7/d4d/tutorial_py_thresholding.html). 4 | 5 | ### Usage 6 | ```python 7 | abrz.AdaptiveBinarize(vnode clip, vnode clip2[, int c=3]) 8 | ``` 9 | ### Parameters: 10 | 11 | - clip\ 12 | A clip to process. It must be in YUV/GRAY 8-bit. 13 | 14 | - clip2\ 15 | Blurred clip to calculate the thresholding.\ 16 | Gauss has a cleaner result and Mean/BoxBlur retains more detail. 17 | 18 | - c\ 19 | Controls the threshold, read the OpenCV doc for more details.\ 20 | Default: 3. 21 | 22 | ### Example 23 | This code in Vapoursynth: 24 | ```python 25 | import vsutil 26 | import vapoursynth as vs 27 | core = vs.core 28 | 29 | src8 = ... 30 | luma = vsutil.get_y(src8) 31 | luma16 = vsutil.depth(luma, 16) 32 | blur16 = luma16.std.Convolution([1]*11, mode='hv') 33 | blur8 = vsutil.depth(blur16, 8) 34 | binarize = core.abrz.AdaptiveBinarize(luma, blur8, c=3) 35 | ``` 36 | 37 | Has the same result as this in OpenCV: 38 | ```python 39 | binarize = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY_INV, 11, 3) 40 | ``` 41 | 42 | More example: [flat_mask from jvsfunc](https://github.com/dnjulek/jvsfunc/blob/ee14f8e908781ff19c891a0fc2bd4b43ba94852a/jvsfunc/mask.py#L15-L41) -------------------------------------------------------------------------------- /msvc_project/AdaptiveBinarize.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32526.322 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AdaptiveBinarize", "AdaptiveBinarize.vcxproj", "{68926A12-6394-4B75-80E4-F3D138161C7E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Debug|x64.ActiveCfg = Debug|x64 17 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Debug|x64.Build.0 = Debug|x64 18 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Debug|x86.ActiveCfg = Debug|Win32 19 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Debug|x86.Build.0 = Debug|Win32 20 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Release|x64.ActiveCfg = Release|x64 21 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Release|x64.Build.0 = Release|x64 22 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Release|x86.ActiveCfg = Release|Win32 23 | {68926A12-6394-4B75-80E4-F3D138161C7E}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {019222CE-6D80-45CD-8063-36619199A5C3} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | - push 4 | - release 5 | - pull_request 6 | - workflow_dispatch 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | strategy: 12 | matrix: 13 | arch: 14 | - amd64 15 | - x86 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: setup MS dev commands 19 | uses: ilammy/msvc-dev-cmd@v1 20 | with: 21 | arch: ${{ matrix.arch }} 22 | - name: Setup Python 23 | uses: actions/setup-python@v1 24 | with: 25 | python-version: '3.x' 26 | - name: install meson and ninja 27 | run: pip install meson ninja 28 | 29 | - name: Plug in vs-api3 30 | shell: bash 31 | run: | 32 | git clone https://github.com/AmusementClub/vs-api3 --depth=1 --branch master api3 33 | cp api3/api3.cc src/ 34 | sed -i -e "/adaptivebinarize.cpp'/s//&, 'src\\/api3.cc'/" meson.build 35 | cat meson.build 36 | 37 | - name: download VS headers and patch header location 38 | shell: bash 39 | run: | 40 | git clone https://github.com/AmusementClub/vapoursynth-classic --depth=1 --branch doodle2 vapoursynth 41 | cp vapoursynth/include/*.h src/ 42 | #sed -i -e '/#include |""|' src/adaptivebinarize.cpp 43 | - name: Meson setup 44 | run: meson setup builddir/ -Db_vscrt=mt 45 | - name: Meson compile 46 | run: meson compile -C builddir/ -v 47 | - name: Upload artifact 48 | uses: actions/upload-artifact@v2 49 | with: 50 | name: release-${{matrix.arch}} 51 | path: | 52 | builddir/*.dll 53 | -------------------------------------------------------------------------------- /src/adaptivebinarize.cpp: -------------------------------------------------------------------------------- 1 | #include "VapourSynth4.h" 2 | #include "VSHelper4.h" 3 | 4 | typedef struct AdaptiveBinarizeData { 5 | VSNode* node; 6 | VSNode* node2; 7 | const VSVideoInfo* vi; 8 | int c_param; 9 | int tab[768]{}; 10 | } AdaptiveBinarizeData; 11 | 12 | static const VSFrame* VS_CC adaptiveBinarizeGetFrame(int n, int activationReason, void* instanceData, void** frameData, VSFrameContext* frameCtx, VSCore* core, const VSAPI* vsapi) { 13 | auto* d = reinterpret_cast(instanceData); 14 | 15 | if (activationReason == arInitial) { 16 | vsapi->requestFrameFilter(n, d->node, frameCtx); 17 | vsapi->requestFrameFilter(n, d->node2, frameCtx); 18 | } 19 | else if (activationReason == arAllFramesReady) { 20 | const VSFrame* src = vsapi->getFrameFilter(n, d->node, frameCtx); 21 | const VSFrame* src2 = vsapi->getFrameFilter(n, d->node2, frameCtx); 22 | 23 | const VSVideoFormat* fi = vsapi->getVideoFrameFormat(src); 24 | int height = vsapi->getFrameHeight(src, 0); 25 | int width = vsapi->getFrameWidth(src, 0); 26 | 27 | VSFrame* dst = vsapi->newVideoFrame(fi, width, height, src, core); 28 | 29 | for (int plane = 0; plane < fi->numPlanes; plane++) { 30 | 31 | const uint8_t* srcp = vsapi->getReadPtr(src, plane); 32 | const uint8_t* src2p = vsapi->getReadPtr(src2, plane); 33 | uint8_t* dstp = vsapi->getWritePtr(dst, plane); 34 | 35 | ptrdiff_t stride = vsapi->getStride(src, plane); 36 | 37 | int h = vsapi->getFrameHeight(src, plane); 38 | int w = vsapi->getFrameWidth(src, plane); 39 | 40 | for (int y = 0; y < h; y++) { 41 | for (int x = 0; x < w; x++) { 42 | int z = (srcp[x] - src2p[x] + 255); 43 | dstp[x] = d->tab[z]; 44 | } 45 | 46 | dstp += stride; 47 | srcp += stride; 48 | src2p += stride; 49 | } 50 | } 51 | 52 | vsapi->freeFrame(src); 53 | vsapi->freeFrame(src2); 54 | 55 | return dst; 56 | } 57 | 58 | return NULL; 59 | } 60 | 61 | static void VS_CC adaptiveBinarizeFree(void* instanceData, VSCore* core, const VSAPI* vsapi) { 62 | AdaptiveBinarizeData* d = (AdaptiveBinarizeData*)instanceData; 63 | vsapi->freeNode(d->node); 64 | vsapi->freeNode(d->node2); 65 | free(d); 66 | } 67 | 68 | static void VS_CC adaptiveBinarizeCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi) { 69 | AdaptiveBinarizeData d; 70 | AdaptiveBinarizeData* data; 71 | int err; 72 | 73 | d.node = vsapi->mapGetNode(in, "clip", 0, 0); 74 | d.node2 = vsapi->mapGetNode(in, "clip2", 0, 0); 75 | d.vi = vsapi->getVideoInfo(d.node); 76 | 77 | if (!vsh::isConstantVideoFormat(d.vi) || d.vi->format.sampleType != stInteger || d.vi->format.bitsPerSample != 8) { 78 | vsapi->mapSetError(out, "AdaptiveBinarize: only constant format 8bit integer input supported"); 79 | vsapi->freeNode(d.node); 80 | return; 81 | } 82 | 83 | if (!vsh::isSameVideoInfo(vsapi->getVideoInfo(d.node2), d.vi)) { 84 | vsapi->mapSetError(out, "AdaptiveBinarize: both clips must have the same format and dimensions"); 85 | vsapi->freeNode(d.node); 86 | vsapi->freeNode(d.node2); 87 | return; 88 | } 89 | 90 | if (vsapi->getVideoInfo(d.node2)->numFrames != d.vi->numFrames) { 91 | vsapi->mapSetError(out, "AdaptiveBinarize: both clips' number of frames do not match"); 92 | vsapi->freeNode(d.node); 93 | vsapi->freeNode(d.node2); 94 | return; 95 | } 96 | 97 | d.c_param = vsapi->mapGetIntSaturated(in, "c", 0, &err); 98 | if (err) 99 | d.c_param = 3; 100 | 101 | for (int i = 0; i < 768; i++) { 102 | d.tab[i] = i - 255 <= -d.c_param ? 255 : 0; 103 | } 104 | 105 | data = (AdaptiveBinarizeData*)malloc(sizeof(d)); 106 | *data = d; 107 | 108 | VSFilterDependency deps[]{ {d.node, rpGeneral}, {d.node2, rpGeneral} }; 109 | vsapi->createVideoFilter(out, "AdaptiveBinarize", data->vi, adaptiveBinarizeGetFrame, adaptiveBinarizeFree, fmParallel, deps, 2, data, core); 110 | } 111 | 112 | VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin* plugin, const VSPLUGINAPI* vspapi) { 113 | vspapi->configPlugin("com.julek.abrz", "abrz", "Adaptive Binarize", VS_MAKE_VERSION(1, 0), VAPOURSYNTH_API_VERSION, 0, plugin); 114 | vspapi->registerFunction("AdaptiveBinarize", 115 | "clip:vnode;" 116 | "clip2:vnode;" 117 | "c:int:opt;", 118 | "clip:vnode;", 119 | adaptiveBinarizeCreate, NULL, plugin); 120 | } -------------------------------------------------------------------------------- /msvc_project/AdaptiveBinarize.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 16.0 26 | Win32Proj 27 | {68926a12-6394-4b75-80e4-f3d138161c7e} 28 | AdaptiveBinarize 29 | 10.0 30 | 31 | 32 | 33 | DynamicLibrary 34 | true 35 | v143 36 | Unicode 37 | 38 | 39 | DynamicLibrary 40 | false 41 | v143 42 | true 43 | Unicode 44 | 45 | 46 | DynamicLibrary 47 | true 48 | v143 49 | Unicode 50 | 51 | 52 | DynamicLibrary 53 | false 54 | v143 55 | true 56 | Unicode 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | C:\Program Files\VapourSynth\sdk\include\vapoursynth;$(IncludePath) 78 | 79 | 80 | C:\Program Files\VapourSynth\sdk\include\vapoursynth;$(IncludePath) 81 | 82 | 83 | C:\Program Files\VapourSynth\sdk\include\vapoursynth;$(IncludePath) 84 | 85 | 86 | C:\Program Files\VapourSynth\sdk\include\vapoursynth;$(IncludePath) 87 | 88 | 89 | 90 | Level3 91 | true 92 | WIN32;_DEBUG;ADAPTIVEBINARIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 93 | true 94 | NotUsing 95 | stdafx.h 96 | stdcpp20 97 | 98 | 99 | Windows 100 | true 101 | false 102 | 103 | 104 | 105 | 106 | Level3 107 | true 108 | true 109 | true 110 | WIN32;NDEBUG;ADAPTIVEBINARIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 111 | true 112 | NotUsing 113 | stdafx.h 114 | stdcpp20 115 | 116 | 117 | Windows 118 | true 119 | true 120 | true 121 | false 122 | 123 | 124 | 125 | 126 | Level3 127 | true 128 | _DEBUG;ADAPTIVEBINARIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 129 | true 130 | NotUsing 131 | stdafx.h 132 | stdcpp20 133 | 134 | 135 | Windows 136 | true 137 | false 138 | 139 | 140 | 141 | 142 | Level3 143 | true 144 | true 145 | true 146 | NDEBUG;ADAPTIVEBINARIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 147 | true 148 | NotUsing 149 | stdafx.h 150 | stdcpp20 151 | 152 | 153 | Windows 154 | true 155 | true 156 | true 157 | false 158 | 159 | 160 | 161 | 162 | 163 | --------------------------------------------------------------------------------