├── meson.build ├── LICENSE ├── include ├── Helper.hpp ├── Lut.hpp ├── JincFunc.hpp └── EWAResizer.hpp ├── msvc ├── JincResize.sln ├── JincResize.vcxproj.filters └── JincResize.vcxproj ├── .github └── workflows │ └── CI.yml ├── README.md ├── .gitignore └── src └── JincResize.cpp /meson.build: -------------------------------------------------------------------------------- 1 | project('JincResize', 'cpp', 2 | default_options : ['buildtype=release', 'b_ndebug=if-release', 'cpp_std=c++17'], 3 | meson_version : '>=0.48.0', 4 | version : '7' 5 | ) 6 | 7 | sources = ['src/JincResize.cpp'] 8 | 9 | vapoursynth_dep = dependency('vapoursynth').partial_dependency(compile_args : true, includes : true) 10 | 11 | add_project_arguments(language : 'cpp') 12 | 13 | shared_module('jincresize', sources, 14 | dependencies : vapoursynth_dep, 15 | include_directories : include_directories('include'), 16 | cpp_args: ['-march=native'], 17 | install : true, 18 | install_dir : join_paths(vapoursynth_dep.get_pkgconfig_variable('libdir'), 'vapoursynth'), 19 | gnu_symbol_visibility : 'hidden' 20 | ) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lypheo 4 | Copyright (c) 2019-2020 Kiyamou 5 | Copyright (c) 2020 luglio 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /include/Helper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Help function: 3 | * 1.clamp(): 4 | * Return the middle value, which is similar with np.clip() in Python 5 | * Called in EWAResizer.hpp 6 | * 2.sample_sqr(): 7 | * Data preprocessing for jinc_sqr() in JincFunc.hpp 8 | * Called in Lut.cpp 9 | * 3.reduce(): 10 | * Horizontal sum of 8 packed 32bit floats 11 | * By Z boson 12 | * Form https://stackoverflow.com/questions/13879609/horizontal-sum-of-8-packed-32bit-floats/18616679#18616679 13 | * Called in EWAResizer.hpp 14 | */ 15 | 16 | 17 | #ifndef HELPER_HPP_ 18 | #define HELPER_HPP_ 19 | 20 | //#define USE_AVX2 21 | 22 | #include 23 | 24 | #if defined(USE_AVX2) 25 | #include "immintrin.h" 26 | #endif 27 | 28 | template 29 | inline T clamp(T input, T range_min, T range_max) 30 | { 31 | return std::min(std::max(input, range_min), range_max); 32 | } 33 | 34 | double sample_sqr(double (*filter)(double), double x2, double blur2, double radius2) 35 | { 36 | if (blur2 > 0.0) 37 | x2 /= blur2; 38 | 39 | if (x2 < radius2) 40 | return filter(x2); 41 | 42 | return 0.0; 43 | } 44 | 45 | #if defined(USE_AVX2) 46 | inline float reduce(__m256 a) 47 | { 48 | __m256 t1 = _mm256_hadd_ps(a, a); 49 | __m256 t2 = _mm256_hadd_ps(t1, t1); 50 | __m128 t3 = _mm256_extractf128_ps(t2, 1); 51 | __m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2), t3); 52 | return _mm_cvtss_f32(t4); 53 | } 54 | #endif 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /msvc/JincResize.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29025.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JincResize", "JincResize.vcxproj", "{E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}" 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 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Debug|x64.ActiveCfg = Debug|x64 17 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Debug|x64.Build.0 = Debug|x64 18 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Debug|x86.ActiveCfg = Debug|Win32 19 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Debug|x86.Build.0 = Debug|Win32 20 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Release|x64.ActiveCfg = Release|x64 21 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Release|x64.Build.0 = Release|x64 22 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Release|x86.ActiveCfg = Release|Win32 23 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {968A8DFF-B6DC-422C-8313-445C170260B6} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /msvc/JincResize.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;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 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | 32 | 33 | Source Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /include/Lut.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LUT_HPP_ 2 | #define LUT_HPP_ 3 | 4 | #include "Helper.hpp" 5 | #include "JincFunc.hpp" 6 | 7 | constexpr double JINC_ZERO_SQR = 1.48759464366204680005356; 8 | 9 | class Lut 10 | { 11 | public: 12 | Lut(); 13 | ~Lut() {}; 14 | 15 | void InitLut(int lut_size, double radius, double blur); 16 | void DestroyLutTable(); 17 | 18 | float GetFactor(int index); 19 | 20 | double* lut; 21 | 22 | private: 23 | int lut_size = 1024; 24 | double radius; 25 | double blur; 26 | }; 27 | 28 | Lut::Lut() 29 | { 30 | #if defined(USE_AVX2) 31 | lut = (double*)_mm_malloc(sizeof(double) * lut_size, 64); 32 | #else 33 | lut = new double[lut_size]; 34 | #endif 35 | } 36 | 37 | void Lut::InitLut(int lut_size, double radius, double blur) 38 | { 39 | auto radius2 = radius * radius; 40 | auto blur2 = blur * blur; 41 | 42 | #if defined(USE_AVX2) 43 | double* filters = (double *)_mm_malloc(sizeof(double) * lut_size, 64); 44 | double* windows = (double *)_mm_malloc(sizeof(double) * lut_size, 64); 45 | for (auto i = 0; i < lut_size; i++) 46 | { 47 | auto t2 = i / (lut_size - 1.0); 48 | filters[i] = sample_sqr(jinc_sqr, radius2 * t2, blur2, radius2); 49 | windows[i] = sample_sqr(jinc_sqr, JINC_ZERO_SQR * t2, 1.0, radius2); 50 | } 51 | for (auto j = 0; j < lut_size / 4; ++j) 52 | { 53 | auto rf = _mm256_load_pd(filters + j * 4); 54 | auto rw = _mm256_load_pd(windows + j * 4); 55 | auto rl = _mm256_mul_pd(rf, rw); 56 | _mm256_store_pd(lut + j * 4, rl); 57 | } 58 | _mm_free(filters); 59 | _mm_free(windows); 60 | #else 61 | for (auto i = 0; i < lut_size; ++i) 62 | { 63 | auto t2 = i / (lut_size - 1.0); 64 | double filter = sample_sqr(jinc_sqr, radius2 * t2, blur2, radius2); 65 | double window = sample_sqr(jinc_sqr, JINC_ZERO_SQR * t2, 1.0, radius2); 66 | lut[i] = filter * window; 67 | } 68 | #endif 69 | } 70 | 71 | void Lut::DestroyLutTable() 72 | { 73 | #if defined(USE_AVX2) 74 | _mm_free(lut); 75 | #else 76 | delete[] lut; 77 | #endif 78 | } 79 | 80 | float Lut::GetFactor(int index) 81 | { 82 | if (index >= lut_size) 83 | return 0.f; 84 | return (float)lut[index]; 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '.gitignore' 8 | - '.gitattributes' 9 | - '.gitmodules' 10 | - '**/LICENSE' 11 | - '**.md' 12 | 13 | pull_request: 14 | branches: [ master ] 15 | paths-ignore: 16 | - '.gitignore' 17 | - '.gitattributes' 18 | - '.gitmodules' 19 | - '**/LICENSE' 20 | - '**.md' 21 | 22 | # Manual trigger 23 | workflow_dispatch: 24 | 25 | env: 26 | VAPOURSYNTH_VERSION: R61 27 | 28 | jobs: 29 | 30 | build-linux: 31 | 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | 37 | - name: configure 38 | run: | 39 | wget https://github.com/vapoursynth/vapoursynth/archive/refs/tags/${{env.VAPOURSYNTH_VERSION}}.tar.gz 40 | tar -xzvf ${{env.VAPOURSYNTH_VERSION}}.tar.gz vapoursynth-${{env.VAPOURSYNTH_VERSION}}/include 41 | mkdir include/vapoursynth 42 | mv vapoursynth-${{env.VAPOURSYNTH_VERSION}}/include/VapourSynth.h include/vapoursynth/VapourSynth.h 43 | mv vapoursynth-${{env.VAPOURSYNTH_VERSION}}/include/VSHelper.h include/vapoursynth/VSHelper.h 44 | 45 | - name: build 46 | run: g++ -shared -fPIC -std=c++17 -O3 -march=native src/JincResize.cpp -o JincResize.so 47 | 48 | - name: strip 49 | run: strip JincResize.so 50 | 51 | - name: upload artifact 52 | uses: actions/upload-artifact@v3 53 | with: 54 | name: linux-vapoursynth-jincresize 55 | path: JincResize.so 56 | 57 | build-windows: 58 | 59 | runs-on: windows-latest 60 | 61 | steps: 62 | - uses: actions/checkout@v3 63 | 64 | - name: configure 65 | run: | 66 | curl -s -L https://github.com/vapoursynth/vapoursynth/archive/refs/tags/${{env.VAPOURSYNTH_VERSION}}.tar.gz -o ${{env.VAPOURSYNTH_VERSION}}.tar.gz 67 | tar -xzvf ${{env.VAPOURSYNTH_VERSION}}.tar.gz vapoursynth-${{env.VAPOURSYNTH_VERSION}}/include 68 | mkdir include/vapoursynth 69 | mv vapoursynth-${{env.VAPOURSYNTH_VERSION}}/include/VapourSynth.h include/vapoursynth/VapourSynth.h 70 | mv vapoursynth-${{env.VAPOURSYNTH_VERSION}}/include/VSHelper.h include/vapoursynth/VSHelper.h 71 | 72 | - name: build 73 | run: x86_64-w64-mingw32-g++ -shared -static -std=c++17 -O3 -march=native src/JincResize.cpp -o JincResize.dll 74 | 75 | - name: strip 76 | run: strip JincResize.dll 77 | 78 | - name: upload artifact 79 | uses: actions/upload-artifact@v3 80 | with: 81 | name: windows-vapoursynth-jincresize 82 | path: JincResize.dll 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VapourSynth-JincResize 2 | 3 | [![Build Status](https://github.com/Kiyamou/VapourSynth-JincResize/workflows/CI/badge.svg)](https://github.com/Kiyamou/VapourSynth-JincResize/actions) 4 | 5 | ## Description 6 | 7 | JincResize is a resizer plugin for VapourSynth, works by Jinc function and elliptical weighted averaging (EWA). Support 8-16 bit and 32 bit sample type. Support YUV color family. 8 | 9 | The repo is ported from [AviSynth plugin](https://github.com/AviSynth/jinc-resize) and based on [EWA-Resampling-VS](https://github.com/Lypheo/EWA-Resampling-VS). 10 | 11 | If want to learn more about Jinc, you can read the [post](https://zhuanlan.zhihu.com/p/103910606) (Simplified Chinese / 简体中文). 12 | 13 | ## Usage 14 | 15 | ```python 16 | core.jinc.JincResize(clip clip, int width, int height[, int tap, float src_left, float src_top, 17 | float src_width, float src_height, int quant_x, int quant_y, float blur]) 18 | ``` 19 | 20 | * ***clip*** 21 | * Required parameter. 22 | * Clip to process. 23 | * Integer sample type of 8-16 bit depth and float sample type of 32 bit depth is supported. 24 | * ***width*** 25 | * Required parameter. 26 | * The width of output. 27 | * ***height*** 28 | * Required parameter. 29 | * The height of output. 30 | * ***tap*** 31 | * Optional parameter. Range: 1–16. *Default: 3*. 32 | * Corresponding to different zero points of Jinc function. 33 | * The recommended value is 3, 4, 6, 8, which is similar to the [AviSynth plugin](https://github.com/AviSynth/jinc-resize), ` Jinc36Resize `, ` Jinc64Resize `, ` Jinc128Resize `, ` Jinc256Resize `. 34 | * ***src_left*** 35 | * Optional parameter. *Default: 0.0*. 36 | * Cropping of the left edge respectively, in pixels, before resizing. 37 | * ***src_top*** 38 | * Optional parameter. *Default: 0.0*. 39 | * Cropping of the top edge respectively, in pixels, before resizing. 40 | * ***src_width*** 41 | * Optional parameter. *Default: the width of input*. 42 | * If > 0, setting the width of the clip before resizing. 43 | * If <= 0, setting the cropping of the right edge respectively, before resizing. 44 | * ***src_height*** 45 | * Optional parameter. *Default: the height of input*. 46 | * If > 0, setting the height of the clip before resizing. 47 | * If <= 0, setting the cropping of the bottom edge respectively, before resizing. 48 | * ***quant_x*** 49 | * ***quant_y*** 50 | * Optional parameter. *Default: 256*. 51 | * Controls sub-pixel quantization. 52 | * ***blur*** 53 | * Optional parameter. *Default: 0.9812505644269356*. 54 | * Blur processing, it can reduce side effects. 55 | * To achieve blur, the value should less than 1. 56 | * If don't have relevant knowledge or experience, had better not modify the parameter. 57 | 58 | ## Tips 59 | 60 | JincResize will lead to ringing. A solution is to use de-ringing as post-processing, such as `HQDeringmod()` in [havsfunc](https://github.com/HomeOfVapourSynthEvolution/havsfunc). A simple example as follows. 61 | 62 | ```python 63 | from vapoursynth import core 64 | import havfunc as haf 65 | 66 | # src is a 720p clip 67 | dst = core.jinc.JincResize(src, 1920, 1080) 68 | dst = haf.HQDeringmod(dst) 69 | ``` 70 | 71 | ## Compilation 72 | 73 | ### Windows 74 | 75 | ```bash 76 | x86_64-w64-mingw32-g++ -shared -static -std=c++17 -O3 -march=native JincResize.cpp -o JincResize.dll 77 | ``` 78 | 79 | `VapourSynth.h` and `VSHelper.h` is need. You can get them from [here](https://github.com/vapoursynth/vapoursynth/tree/master/include) or your VapourSynth installation directory (`VapourSynth/sdk/include/vapoursynth`). 80 | 81 | ### Linux 82 | 83 | ```bash 84 | meson build 85 | ninja -C build 86 | ``` 87 | or directly 88 | 89 | ```bash 90 | g++ -shared -fPIC -std=c++17 -O3 -march=native JincResize.cpp -o JincResize.so 91 | ``` 92 | ### Windows and Linux using Github Actions 93 | 94 | 1.[Fork this repository](https://github.com/Kiyamou/VapourSynth-JincResize/fork). 95 | 96 | 2.Enable Github Actions on your fork: **Settings** tab -> **Actions** -> **General** -> **Allow all actions and reusable workflows** -> **Save** button. 97 | 98 | 3.Edit (if necessary) the file `.github/workflows/CI.yml` on your fork modifying the environment variable VapourSynth version: 99 | 100 | ``` 101 | env: 102 | VAPOURSYNTH_VERSION: 103 | ``` 104 | 105 | 4.Go to the GitHub **Actions** tab on your fork, select **CI** workflow and press the **Run workflow** button (if you modified the `.github/workflows/CI.yml` file, a workflow will be already running and no need to run a new one). 106 | 107 | When the workflow is completed you will be able to download the artifacts generated (Windows and Linux versions) from the run. 108 | 109 | ## Download Nightly Builds 110 | 111 | **GitHub Actions Artifacts ONLY can be downloaded by GitHub logged users.** 112 | 113 | Nightly builds are built automatically by GitHub Actions (GitHub's integrated CI/CD tool) every time a new commit is pushed to the _master_ branch or a pull request is created. 114 | 115 | To download the latest nightly build, go to the GitHub [Actions](https://github.com/Kiyamou/VapourSynth-JincResize/actions/workflows/CI.yml) tab, enter the last run of workflow **CI**, and download the artifacts generated (Windows and Linux versions) from the run. 116 | 117 | ## Acknowledgement 118 | 119 | EWA-Resampling-VS: https://github.com/Lypheo/EWA-Resampling-VS 120 | 121 | AviSynth plugin: https://github.com/AviSynth/jinc-resize 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | # Generic header file 333 | VapourSynth.h 334 | VSHelper.h 335 | 336 | # Dynamic link library after compiling 337 | *.dll 338 | 339 | # meson build folder 340 | build/ 341 | 342 | # Linux build 343 | *.so 344 | 345 | # Other 346 | *.7z 347 | -------------------------------------------------------------------------------- /include/JincFunc.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Define Jinc function. 3 | * 4 | * See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions. 5 | * http://mathworld.wolfram.com/JincFunction.html and page 11 of 6 | * http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf. 7 | * 8 | * Calculate Jinc function by Taylor series, asymptotic expansion 9 | * or C++ special function, according to the different radius. 10 | * 11 | * In fact, function is jinc(sqrt(x^2)), instead of jinc(x), which can 12 | * avoid extra square root calculations in JincResize.cpp 13 | */ 14 | 15 | 16 | #ifndef JINCFUNC_HPP_ 17 | #define JINCFUNC_HPP_ 18 | 19 | #include 20 | 21 | #ifndef M_PI // GCC seems to have it 22 | constexpr double M_PI = 3.14159265358979323846; 23 | #endif 24 | 25 | // Taylor series coefficients of 2*BesselJ1(pi*x)/(pi*x) as (x^2) -> 0 26 | double jinc_taylor_series[31] = 27 | { 28 | 1.0, 29 | -1.23370055013616982735431137, 30 | 0.507339015802096027273126733, 31 | -0.104317403816764804365258186, 32 | 0.0128696438477519721233840271, 33 | -0.00105848577966854543020422691, 34 | 6.21835470803998638484476598e-05, 35 | -2.73985272294670461142756204e-06, 36 | 9.38932725442064547796003405e-08, 37 | -2.57413737759717407304931036e-09, 38 | 5.77402672521402031756429343e-11, 39 | -1.07930605263598241754572977e-12, 40 | 1.70710316782347356046974552e-14, 41 | -2.31434518382749184406648762e-16, 42 | 2.71924659665997312120515390e-18, 43 | -2.79561335187943028518083529e-20, 44 | 2.53599244866299622352138464e-22, 45 | -2.04487273140961494085786452e-24, 46 | 1.47529860450204338866792475e-26, 47 | -9.57935105257523453155043307e-29, 48 | 5.62764317309979254140393917e-31, 49 | -3.00555258814860366342363867e-33, 50 | 1.46559362903641161989338221e-35, 51 | -6.55110024064596600335624426e-38, 52 | 2.69403199029404093412381643e-40, 53 | -1.02265499954159964097119923e-42, 54 | 3.59444454568084324694180635e-45, 55 | -1.17313973900539982313119019e-47, 56 | 3.56478606255557746426034301e-50, 57 | -1.01100655781438313239513538e-52, 58 | 2.68232117541264485328658605e-55 59 | }; 60 | 61 | double jinc_zeros[16] = 62 | { 63 | 1.2196698912665045, 64 | 2.2331305943815286, 65 | 3.2383154841662362, 66 | 4.2410628637960699, 67 | 5.2427643768701817, 68 | 6.2439216898644877, 69 | 7.2447598687199570, 70 | 8.2453949139520427, 71 | 9.2458926849494673, 72 | 10.246293348754916, 73 | 11.246622794877883, 74 | 12.246898461138105, 75 | 13.247132522181061, 76 | 14.247333735806849, 77 | 15.247508563037300, 78 | 16.247661874700962 79 | }; 80 | 81 | // Modified from boost package math/tools/`rational.hpp` 82 | // 83 | // (C) Copyright John Maddock 2006. 84 | // Use, modification and distribution are subject to the 85 | // Boost Software License, Version 1.0. (See accompanying file 86 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 87 | double evaluate_rational(const double* num, const double* denom, double z, int count) 88 | { 89 | double s1, s2; 90 | if (z <= 1.0) 91 | { 92 | s1 = num[count-1]; 93 | s2 = denom[count-1]; 94 | for (auto i = count - 2; i >= 0; --i) 95 | { 96 | s1 *= z; 97 | s2 *= z; 98 | s1 += num[i]; 99 | s2 += denom[i]; 100 | } 101 | } 102 | else 103 | { 104 | z = 1.0f / z; 105 | s1 = num[0]; 106 | s2 = denom[0]; 107 | for (auto i = 1; i < count; ++i) 108 | { 109 | s1 *= z; 110 | s2 *= z; 111 | s1 += num[i]; 112 | s2 += denom[i]; 113 | } 114 | } 115 | 116 | return s1 / s2; 117 | } 118 | 119 | // Modified from boost package `BesselJ1.hpp` 120 | // 121 | // Copyright (c) 2006 Xiaogang Zhang 122 | // Use, modification and distribution are subject to the 123 | // Boost Software License, Version 1.0. (See accompanying file 124 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 125 | double jinc_sqr_boost_l(double x2) 126 | { 127 | static const double bPC[7] = 128 | { 129 | -4.4357578167941278571e+06, 130 | -9.9422465050776411957e+06, 131 | -6.6033732483649391093e+06, 132 | -1.5235293511811373833e+06, 133 | -1.0982405543459346727e+05, 134 | -1.6116166443246101165e+03, 135 | 0.0 136 | }; 137 | static const double bQC[7] = 138 | { 139 | -4.4357578167941278568e+06, 140 | -9.9341243899345856590e+06, 141 | -6.5853394797230870728e+06, 142 | -1.5118095066341608816e+06, 143 | -1.0726385991103820119e+05, 144 | -1.4550094401904961825e+03, 145 | 1.0 146 | }; 147 | static const double bPS[7] = 148 | { 149 | 3.3220913409857223519e+04, 150 | 8.5145160675335701966e+04, 151 | 6.6178836581270835179e+04, 152 | 1.8494262873223866797e+04, 153 | 1.7063754290207680021e+03, 154 | 3.5265133846636032186e+01, 155 | 0.0 156 | }; 157 | static const double bQS[7] = 158 | { 159 | 7.0871281941028743574e+05, 160 | 1.8194580422439972989e+06, 161 | 1.4194606696037208929e+06, 162 | 4.0029443582266975117e+05, 163 | 3.7890229745772202641e+04, 164 | 8.6383677696049909675e+02, 165 | 1.0 166 | }; 167 | 168 | auto y2 = M_PI * M_PI * x2; 169 | auto xp = std::sqrt(y2); 170 | auto y2p = 64.0 / y2; 171 | auto yp = 8.0 / xp; 172 | auto factor = std::sqrt(xp / M_PI) * 2.0 / y2; 173 | auto rc = evaluate_rational(bPC, bQC, y2p, 7); 174 | auto rs = evaluate_rational(bPS, bQS, y2p, 7); 175 | auto sx = std::sin(xp); 176 | auto cx = std::cos(xp); 177 | 178 | return factor * (rc * (sx - cx) + yp * rs * (sx + cx)); 179 | } 180 | 181 | // jinc(sqrt(x2)) 182 | double jinc_sqr(double x2) 183 | { 184 | if (x2 < 1.49) // the 1-tap radius 185 | { 186 | double res = 0.0; 187 | for (auto j = 16; j > 0; --j) 188 | res = res * x2 + jinc_taylor_series[j - 1]; 189 | return res; 190 | } 191 | else if (x2 < 4.97) // the 2-tap radius 192 | { 193 | double res = 0.0; 194 | for (auto j = 21; j > 0; --j) 195 | res = res * x2 + jinc_taylor_series[j - 1]; 196 | return res; 197 | } 198 | else if (x2 < 10.49) // the 3-tap radius 199 | { 200 | double res = 0.0; 201 | for (auto j = 26; j > 0; --j) 202 | res = res * x2 + jinc_taylor_series[j - 1]; 203 | return res; 204 | } 205 | else if (x2 < 17.99) // the 4-tap radius 206 | { 207 | double res = 0.0; 208 | for (auto j = 31; j > 0; --j) 209 | res = res * x2 + jinc_taylor_series[j - 1]; 210 | return res; 211 | } 212 | else if (x2 < 52.57) // the 5~7-tap radius 213 | { 214 | auto x = M_PI * std::sqrt(x2); 215 | #if (defined(_MSC_VER) && _MSC_VER < 1914) || (defined(__GNUC__) && __GNUC__ < 7) || defined(__clang__) 216 | return 2.0 * j1(x) / x; 217 | #else 218 | return 2.0 * std::cyl_bessel_j(1, x) / x; 219 | #endif 220 | } 221 | else if (x2 < 68.07) // the 8-tap radius // Modify from pull request #4 222 | { 223 | return jinc_sqr_boost_l(x2); 224 | } 225 | else // the 9~16-tap radius 226 | { 227 | auto x = M_PI * std::sqrt(x2); 228 | #if (defined(_MSC_VER) && _MSC_VER < 1914) || (defined(__GNUC__) && __GNUC__ < 7) || defined(__clang__) 229 | return 2.0 * j1(x) / x; 230 | #else 231 | return 2.0 * std::cyl_bessel_j(1, x) / x; 232 | #endif 233 | } 234 | } 235 | 236 | #endif 237 | -------------------------------------------------------------------------------- /msvc/JincResize.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 | 26 | 27 | 28 | 29 | 30 | 31 | 16.0 32 | {E8E2620C-42EC-40A5-BAC1-B351DF8AD94F} 33 | Win32Proj 34 | JincResize 35 | 10.0 36 | 37 | 38 | 39 | DynamicLibrary 40 | true 41 | v142 42 | Unicode 43 | 44 | 45 | DynamicLibrary 46 | false 47 | v142 48 | true 49 | Unicode 50 | 51 | 52 | DynamicLibrary 53 | true 54 | v142 55 | Unicode 56 | 57 | 58 | DynamicLibrary 59 | false 60 | v142 61 | true 62 | Unicode 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | true 84 | 85 | 86 | true 87 | 88 | 89 | false 90 | 91 | 92 | false 93 | 94 | 95 | 96 | Level3 97 | Disabled 98 | true 99 | WIN32;_DEBUG;JINCRESIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 100 | true 101 | stdcpp17 102 | 103 | 104 | Windows 105 | true 106 | false 107 | 108 | 109 | 110 | 111 | Level3 112 | Disabled 113 | true 114 | _DEBUG;JINCRESIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 115 | true 116 | stdcpp17 117 | 118 | 119 | Windows 120 | true 121 | false 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | WIN32;NDEBUG;JINCRESIZE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 132 | true 133 | stdcpp17 134 | 135 | 136 | Windows 137 | true 138 | true 139 | true 140 | false 141 | 142 | 143 | 144 | 145 | Level3 146 | MaxSpeed 147 | true 148 | true 149 | true 150 | NDEBUG;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) 151 | true 152 | C:\Program Files\VapourSynth\sdk\include;C:\Program Files (x86)\VapourSynth\sdk\include;%(AdditionalIncludeDirectories) 153 | Speed 154 | stdcpp17 155 | AdvancedVectorExtensions2 156 | 157 | 158 | Windows 159 | true 160 | true 161 | true 162 | false 163 | vapoursynth.lib;vsscript.lib;%(AdditionalDependencies) 164 | %(AdditionalManifestDependencies) 165 | C:\Program Files\VapourSynth\sdk\lib64;C:\Program Files (x86)\VapourSynth\sdk\lib64;%(AdditionalLibraryDirectories) 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /src/JincResize.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../include/EWAResizer.hpp" 5 | 6 | struct FilterData 7 | { 8 | VSNodeRef* node; 9 | const VSVideoInfo* vi; 10 | int w, h; 11 | int peak; 12 | Lut* init_lut; 13 | EWAPixelCoeff* out_y; 14 | EWAPixelCoeff* out_u; 15 | EWAPixelCoeff* out_v; 16 | }; 17 | 18 | // Doesn't double precision overkill? 19 | 20 | static void VS_CC filterInit(VSMap* in, VSMap* out, void** instanceData, VSNode* node, VSCore* core, const VSAPI* vsapi) 21 | { 22 | FilterData* d = static_cast(*instanceData); 23 | VSVideoInfo new_vi = (VSVideoInfo) * (d->vi); 24 | new_vi.width = d->w; 25 | new_vi.height = d->h; 26 | vsapi->setVideoInfo(&new_vi, 1, node); 27 | } 28 | 29 | template 30 | static void process_uint(const VSFrameRef* src, VSFrameRef* dst, const FilterData* const VS_RESTRICT d, const VSAPI* vsapi) noexcept 31 | { 32 | for (int plane = 0; plane < d->vi->format->numPlanes; plane++) 33 | { 34 | const T* srcp = reinterpret_cast(vsapi->getReadPtr(src, plane)); 35 | T* VS_RESTRICT dstp = reinterpret_cast(vsapi->getWritePtr(dst, plane)); 36 | int src_stride = vsapi->getStride(src, plane) / sizeof(T); 37 | int dst_stride = vsapi->getStride(dst, plane) / sizeof(T); 38 | 39 | int dst_width = vsapi->getFrameWidth(dst, plane); 40 | int dst_height = vsapi->getFrameHeight(dst, plane); 41 | 42 | if (plane == 0) 43 | resize_plane_c(d->out_y, srcp, dstp, dst_width, dst_height, src_stride, dst_stride, d->peak); 44 | else if (plane == 1) 45 | resize_plane_c(d->out_u, srcp, dstp, dst_width, dst_height, src_stride, dst_stride, d->peak); 46 | else if (plane == 2) 47 | resize_plane_c(d->out_v, srcp, dstp, dst_width, dst_height, src_stride, dst_stride, d->peak); 48 | } 49 | } 50 | 51 | static void process_float(const VSFrameRef* src, VSFrameRef* dst, const FilterData* const VS_RESTRICT d, const VSAPI* vsapi) noexcept 52 | { 53 | for (int plane = 0; plane < d->vi->format->numPlanes; plane++) 54 | { 55 | const float* srcp = reinterpret_cast(vsapi->getReadPtr(src, plane)); 56 | float* VS_RESTRICT dstp = reinterpret_cast(vsapi->getWritePtr(dst, plane)); 57 | int src_stride = vsapi->getStride(src, plane) / sizeof(float); 58 | int dst_stride = vsapi->getStride(dst, plane) / sizeof(float); 59 | 60 | int dst_width = vsapi->getFrameWidth(dst, plane); 61 | int dst_height = vsapi->getFrameHeight(dst, plane); 62 | 63 | #if defined(USE_AVX2) 64 | if (plane == 0) 65 | resize_plane_avx2(d->out_y, srcp, dstp, dst_width, dst_height, src_stride, dst_stride); 66 | else if (plane == 1) 67 | resize_plane_avx2(d->out_u, srcp, dstp, dst_width, dst_height, src_stride, dst_stride); 68 | else if (plane == 2) 69 | resize_plane_avx2(d->out_v, srcp, dstp, dst_width, dst_height, src_stride, dst_stride); 70 | #else 71 | if (plane == 0) 72 | resize_plane_c(d->out_y, srcp, dstp, dst_width, dst_height, src_stride, dst_stride); 73 | else if (plane == 1) 74 | resize_plane_c(d->out_u, srcp, dstp, dst_width, dst_height, src_stride, dst_stride); 75 | else if (plane == 2) 76 | resize_plane_c(d->out_v, srcp, dstp, dst_width, dst_height, src_stride, dst_stride); 77 | #endif 78 | } 79 | } 80 | 81 | static const VSFrameRef* VS_CC filterGetFrame(int n, int activationReason, void** instanceData, 82 | void** frameData, VSFrameContext* frameCtx, VSCore* core, const VSAPI* vsapi) 83 | { 84 | const FilterData* d = static_cast(*instanceData); 85 | 86 | if (activationReason == arInitial) 87 | { 88 | vsapi->requestFrameFilter(n, d->node, frameCtx); 89 | } 90 | else if (activationReason == arAllFramesReady) 91 | { 92 | const VSFrameRef* src = vsapi->getFrameFilter(n, d->node, frameCtx); 93 | VSFrameRef* dst = vsapi->newVideoFrame(d->vi->format, d->w, d->h, src, core); 94 | 95 | if (d->vi->format->bytesPerSample == 1) 96 | process_uint(src, dst, d, vsapi); 97 | else if (d->vi->format->bytesPerSample == 2) 98 | process_uint(src, dst, d, vsapi); 99 | else 100 | process_float(src, dst, d, vsapi); 101 | 102 | vsapi->freeFrame(src); 103 | return dst; 104 | } 105 | 106 | return 0; 107 | } 108 | 109 | static void VS_CC filterFree(void* instanceData, VSCore* core, const VSAPI* vsapi) 110 | { 111 | FilterData* d = static_cast(instanceData); 112 | vsapi->freeNode(d->node); 113 | 114 | delete_coeff_table(d->out_y); 115 | if (d->vi->format->numPlanes > 1) 116 | { 117 | delete_coeff_table(d->out_u); 118 | delete_coeff_table(d->out_v); 119 | } 120 | 121 | d->init_lut->DestroyLutTable(); 122 | delete d; 123 | } 124 | 125 | static void VS_CC filterCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi) 126 | { 127 | std::unique_ptr d = std::make_unique(); 128 | int err; 129 | 130 | d->node = vsapi->propGetNode(in, "clip", 0, 0); 131 | d->vi = vsapi->getVideoInfo(d->node); 132 | d->w = int64ToIntS(vsapi->propGetInt(in, "width", 0, &err)); 133 | d->h = int64ToIntS(vsapi->propGetInt(in, "height", 0, &err)); 134 | 135 | if (d->vi->format->bytesPerSample <= 2) 136 | d->peak = (1 << d->vi->format->bitsPerSample) - 1; 137 | 138 | //probably add an RGB check because subpixel shifting is :effort: 139 | try 140 | { 141 | if (!isConstantFormat(d->vi) || 142 | (d->vi->format->sampleType == stInteger && d->vi->format->bitsPerSample > 16) || 143 | (d->vi->format->sampleType == stFloat && d->vi->format->bitsPerSample != 32)) 144 | throw std::string{ "only constant format 8-16 bit integer and 32 bits float input supported" }; 145 | 146 | int tap = int64ToIntS(vsapi->propGetInt(in, "tap", 0, &err)); 147 | if (err) 148 | tap = 3; 149 | 150 | if (tap < 1 || tap > 16) 151 | throw std::string{ "tap must be in the range of 1-16" }; 152 | 153 | double radius = jinc_zeros[tap - 1]; 154 | 155 | double blur = vsapi->propGetFloat(in, "blur", 0, &err); 156 | if (err) 157 | blur = 0.9812505644269356; 158 | 159 | double crop_left = vsapi->propGetFloat(in, "src_left", 0, &err); 160 | if (err) 161 | crop_left = 0.0; 162 | 163 | double crop_top = vsapi->propGetFloat(in, "src_top", 0, &err); 164 | if (err) 165 | crop_top = 0.0; 166 | 167 | double crop_width = vsapi->propGetFloat(in, "src_width", 0, &err); 168 | if (err) 169 | crop_width = static_cast(d->vi->width); 170 | 171 | double crop_height = vsapi->propGetFloat(in, "src_height", 0, &err); 172 | if (err) 173 | crop_height = static_cast(d->vi->height); 174 | 175 | int samples = 1024; // should be a multiple of 4 176 | 177 | int quantize_x = int64ToIntS(vsapi->propGetInt(in, "quant_x", 0, &err)); 178 | if (err) 179 | quantize_x = 256; 180 | int quantize_y = int64ToIntS(vsapi->propGetInt(in, "quant_y", 0, &err)); 181 | if (err) 182 | quantize_y = 256; 183 | 184 | d->init_lut = new Lut(); 185 | d->init_lut->InitLut(samples, radius, blur); 186 | d->out_y = new EWAPixelCoeff(); 187 | 188 | generate_coeff_table_c(d->init_lut, d->out_y, quantize_x, quantize_y, samples, d->vi->width, d->vi->height, 189 | d->w, d->h, radius, crop_left, crop_top, crop_width, crop_height); 190 | 191 | if (d->vi->format->numPlanes > 1) 192 | { 193 | d->out_u = new EWAPixelCoeff(); 194 | d->out_v = new EWAPixelCoeff(); 195 | 196 | int sub_w = d->vi->format->subSamplingW; 197 | int sub_h = d->vi->format->subSamplingH; 198 | double div_w = static_cast(1 << sub_w); 199 | double div_h = static_cast(1 << sub_h); 200 | 201 | generate_coeff_table_c(d->init_lut, d->out_u, quantize_x, quantize_y, samples, d->vi->width >> sub_w, d->vi->height >> sub_h, 202 | d->w >> sub_w, d->h >> sub_h, radius, crop_left / div_w, crop_top / div_h, crop_width / div_w, crop_height / div_h); 203 | generate_coeff_table_c(d->init_lut, d->out_v, quantize_x, quantize_y, samples, d->vi->width >> sub_w, d->vi->height >> sub_h, 204 | d->w >> sub_w, d->h >> sub_h, radius, crop_left / div_w, crop_top / div_h, crop_width / div_w, crop_height / div_h); 205 | } 206 | } 207 | catch (const std::string & error) 208 | { 209 | vsapi->setError(out, ("JincResize: " + error).c_str()); 210 | vsapi->freeNode(d->node); 211 | return; 212 | } 213 | 214 | vsapi->createFilter(in, out, "JincResize", filterInit, filterGetFrame, filterFree, fmParallel, 0, d.release(), core); 215 | } 216 | 217 | VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin* plugin) 218 | { 219 | configFunc("com.vapoursynth.jincresize", "jinc", "VapourSynth EWA resampling", VAPOURSYNTH_API_VERSION, 1, plugin); 220 | 221 | registerFunc("JincResize", 222 | "clip:clip;" 223 | "width:int;" 224 | "height:int;" 225 | "tap:int:opt;" 226 | "src_left:float:opt;" 227 | "src_top:float:opt;" 228 | "src_width:float:opt;" 229 | "src_height:float:opt;" 230 | "quant_x:int:opt;" 231 | "quant_y:int:opt;" 232 | "blur:float:opt", 233 | filterCreate, 0, plugin); 234 | } 235 | -------------------------------------------------------------------------------- /include/EWAResizer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * EWA: Elliptical Weighted Averaging 3 | * 4 | * port from AviSynth jinc-resize, origin repo: 5 | * https://github.com/AviSynth/jinc-resize/blob/master/JincResize/EWAResizer.h 6 | * https://github.com/AviSynth/jinc-resize/blob/master/JincResize/EWAResizerStruct.h 7 | */ 8 | 9 | 10 | #ifndef EWARESIZER_HPP_ 11 | #define EWARESIZER_HPP_ 12 | 13 | #include 14 | 15 | #include "vapoursynth/VapourSynth.h" 16 | #include "vapoursynth/VSHelper.h" 17 | 18 | #include "Lut.hpp" 19 | 20 | constexpr double DOUBLE_ROUND_MAGIC_NUMBER = 6755399441055744.0; 21 | 22 | struct EWAPixelCoeffMeta 23 | { 24 | int start_x, start_y; 25 | int coeff_meta; 26 | }; 27 | 28 | struct EWAPixelCoeff 29 | { 30 | float* factor; 31 | EWAPixelCoeffMeta* meta; 32 | int* factor_map; 33 | int filter_size, quantize_x, quantize_y, coeff_stride; 34 | }; 35 | 36 | static void init_coeff_table(EWAPixelCoeff* out, int quantize_x, int quantize_y, 37 | int filter_size, int dst_width, int dst_height) 38 | { 39 | out->filter_size = filter_size; 40 | out->quantize_x = quantize_x; 41 | out->quantize_y = quantize_y; 42 | out->coeff_stride = ((filter_size + 7) / 8) * 8; 43 | 44 | // Allocate metadata 45 | out->meta = new EWAPixelCoeffMeta[dst_width * dst_height]; 46 | 47 | // Alocate factor map 48 | if (quantize_x > 0 && quantize_y > 0) 49 | out->factor_map = new int[quantize_x * quantize_y]; 50 | else 51 | out->factor_map = nullptr; 52 | 53 | // This will be reserved to exact size in coff generating procedure 54 | out->factor = nullptr; 55 | 56 | // Zeroed memory 57 | if (out->factor_map != nullptr) 58 | memset(out->factor_map, 0, quantize_x * quantize_y * sizeof(int)); 59 | 60 | memset(out->meta, 0, dst_width * dst_height * sizeof(EWAPixelCoeffMeta)); 61 | } 62 | 63 | void delete_coeff_table(EWAPixelCoeff* out) 64 | { 65 | if(out == nullptr) 66 | return; 67 | 68 | vs_aligned_free(out->factor); 69 | delete[] out->meta; 70 | delete[] out->factor_map; 71 | } 72 | 73 | /* Coefficient table generation */ 74 | void generate_coeff_table_c(Lut* func, EWAPixelCoeff* out, int quantize_x, int quantize_y, 75 | int samples, int src_width, int src_height, int dst_width, int dst_height, double radius, 76 | double crop_left, double crop_top, double crop_width, double crop_height) 77 | { 78 | const double filter_scale_x = (double)dst_width / crop_width; 79 | const double filter_scale_y = (double)dst_height / crop_height; 80 | 81 | const double filter_step_x = std::min(filter_scale_x, 1.0); 82 | const double filter_step_y = std::min(filter_scale_y, 1.0); 83 | 84 | const float filter_support_x = (float)radius / filter_step_x; 85 | const float filter_support_y = (float)radius / filter_step_y; 86 | 87 | const int filter_size_x = (int)ceil(filter_support_x * 2.0); 88 | const int filter_size_y = (int)ceil(filter_support_y * 2.0); 89 | 90 | const float filter_support = std::max(filter_support_x, filter_support_y); 91 | const int filter_size = std::max(filter_size_x, filter_size_y); 92 | 93 | const float start_x = (float)(crop_left + (crop_width - dst_width) / (dst_width * 2)); 94 | const float start_y = (float)(crop_top + (crop_height - dst_height) / (dst_height * 2)); 95 | 96 | const float x_step = (float)(crop_width / dst_width); 97 | const float y_step = (float)(crop_height / dst_height); 98 | 99 | float xpos = start_x; 100 | float ypos = start_y; 101 | 102 | // Initialize EWAPixelCoeff data structure 103 | init_coeff_table(out, quantize_x, quantize_y, filter_size, dst_width, dst_height); 104 | 105 | std::vector tmp_array; 106 | int tmp_array_top = 0; 107 | 108 | // Use to advance the coeff pointer 109 | const int coeff_per_pixel = out->coeff_stride * filter_size; 110 | 111 | for (int y = 0; y < dst_height; y++) 112 | { 113 | for (int x = 0; x < dst_width; x++) 114 | { 115 | bool is_border = false; 116 | 117 | EWAPixelCoeffMeta* meta = &out->meta[y * dst_width + x]; 118 | 119 | // Here, the window_*** variable specified a begin/size/end 120 | // of EWA window to process. 121 | int window_end_x = (int)(xpos + filter_support); 122 | int window_end_y = (int)(ypos + filter_support); 123 | 124 | if (window_end_x >= src_width) 125 | { 126 | window_end_x = src_width - 1; 127 | is_border = true; 128 | } 129 | if (window_end_y >= src_height) 130 | { 131 | window_end_y = src_height - 1; 132 | is_border = true; 133 | } 134 | 135 | int window_begin_x = window_end_x - filter_size + 1; 136 | int window_begin_y = window_end_y - filter_size + 1; 137 | 138 | if (window_begin_x < 0) 139 | { 140 | window_begin_x = 0; 141 | is_border = true; 142 | } 143 | if (window_begin_y < 0) 144 | { 145 | window_begin_y = 0; 146 | is_border = true; 147 | } 148 | 149 | meta->start_x = window_begin_x; 150 | meta->start_y = window_begin_y; 151 | 152 | // Quantize xpos and ypos 153 | const int quantized_x_int = (int)((double)xpos * quantize_x + 0.5); 154 | const int quantized_y_int = (int)((double)ypos * quantize_y + 0.5); 155 | const int quantized_x_value = quantized_x_int % quantize_x; 156 | const int quantized_y_value = quantized_y_int % quantize_y; 157 | const float quantized_xpos = (float)quantized_x_int / quantize_x; 158 | const float quantized_ypos = (float)quantized_y_int / quantize_y; 159 | 160 | if (!is_border && out->factor_map[quantized_y_value * quantize_x + quantized_x_value] != 0) 161 | { 162 | // Not border pixel and already have coefficient calculated at this quantized position 163 | meta->coeff_meta = out->factor_map[quantized_y_value * quantize_x + quantized_x_value] - 1; 164 | } 165 | else 166 | { 167 | // then need computation 168 | float divider = 0.f; 169 | 170 | // This is the location of current target pixel in source pixel 171 | // Quantized 172 | const float current_x = clamp(is_border ? xpos : quantized_xpos, 0.f, src_width - 1.f); 173 | const float current_y = clamp(is_border ? ypos : quantized_ypos, 0.f, src_height - 1.f); 174 | 175 | if (!is_border) 176 | { 177 | // Change window position to quantized position 178 | window_begin_x = (int)(quantized_xpos + filter_support) - filter_size + 1; 179 | window_begin_y = (int)(quantized_ypos + filter_support) - filter_size + 1; 180 | } 181 | 182 | // Windowing positon 183 | int window_x = window_begin_x; 184 | int window_y = window_begin_y; 185 | 186 | // First loop calcuate coeff 187 | tmp_array.resize(tmp_array.size() + coeff_per_pixel, 0.f); 188 | int curr_factor_ptr = tmp_array_top; 189 | 190 | const double radius2 = radius * radius; 191 | for (int ly = 0; ly < filter_size; ly++) 192 | { 193 | for (int lx = 0; lx < filter_size; lx++) 194 | { 195 | // Euclidean distance to sampling pixel 196 | const float dx = (current_x - window_x) * filter_step_x; 197 | const float dy = (current_y - window_y) * filter_step_y; 198 | const float dist = dx * dx + dy * dy; 199 | double index_d = round((samples - 1) * dist / radius2) + DOUBLE_ROUND_MAGIC_NUMBER; 200 | int index = *reinterpret_cast(&index_d); 201 | 202 | const float factor = func->GetFactor(index); 203 | 204 | tmp_array[curr_factor_ptr + lx] = factor; 205 | divider += factor; 206 | 207 | window_x++; 208 | } 209 | 210 | curr_factor_ptr += out->coeff_stride; 211 | 212 | window_x = window_begin_x; 213 | window_y++; 214 | } 215 | 216 | // Second loop to divide the coeff 217 | curr_factor_ptr = tmp_array_top; 218 | for (int ly = 0; ly < filter_size; ly++) 219 | { 220 | for (int lx = 0; lx < filter_size; lx++) 221 | { 222 | tmp_array[curr_factor_ptr + lx] /= divider; 223 | } 224 | 225 | curr_factor_ptr += out->coeff_stride; 226 | } 227 | 228 | // Save factor to table 229 | if (!is_border) 230 | out->factor_map[quantized_y_value * quantize_x + quantized_x_value] = tmp_array_top + 1; 231 | 232 | meta->coeff_meta = tmp_array_top; 233 | tmp_array_top += coeff_per_pixel; 234 | } 235 | 236 | xpos += x_step; 237 | } 238 | 239 | ypos += y_step; 240 | xpos = start_x; 241 | } 242 | 243 | // Copy from tmp_array to real array 244 | const int tmp_array_size = tmp_array.size(); 245 | out->factor = (float *)vs_aligned_malloc(tmp_array_size * sizeof(float), 64); // aligned to cache line 246 | memcpy(out->factor, &tmp_array[0], tmp_array_size * sizeof(float)); 247 | } 248 | 249 | /* Planar resampling with coeff table */ 250 | /* 8-16 bit */ 251 | //#pragma intel optimization_parameter target_arch=sse 252 | template 253 | void resize_plane_c(EWAPixelCoeff* coeff, const T* srcp, T* VS_RESTRICT dstp, 254 | int dst_width, int dst_height, int src_stride, int dst_stride, int peak) 255 | { 256 | EWAPixelCoeffMeta* meta = coeff->meta; 257 | 258 | for (int y = 0; y < dst_height; y++) 259 | { 260 | for (int x = 0; x < dst_width; x++) 261 | { 262 | const T* src_ptr = srcp + meta->start_y * src_stride + meta->start_x; 263 | const float* coeff_ptr = coeff->factor + meta->coeff_meta; 264 | 265 | float result = 0.f; 266 | for (int ly = 0; ly < coeff->filter_size; ly++) 267 | { 268 | for (int lx = 0; lx < coeff->filter_size; lx++) 269 | { 270 | result += src_ptr[lx] * coeff_ptr[lx]; 271 | } 272 | coeff_ptr += coeff->coeff_stride; 273 | src_ptr += src_stride; 274 | } 275 | 276 | dstp[x] = static_cast(clamp(result, 0.f, (float)peak)); 277 | 278 | meta++; 279 | } 280 | 281 | dstp += dst_stride; 282 | } 283 | } 284 | 285 | /* Planar resampling with coeff table */ 286 | /* 32 bit */ 287 | void resize_plane_c(EWAPixelCoeff* coeff, const float* srcp, float* VS_RESTRICT dstp, 288 | int dst_width, int dst_height, int src_stride, int dst_stride) 289 | { 290 | EWAPixelCoeffMeta* meta = coeff->meta; 291 | 292 | for (int y = 0; y < dst_height; y++) 293 | { 294 | for (int x = 0; x < dst_width; x++) 295 | { 296 | const float* src_ptr = srcp + meta->start_y * src_stride + meta->start_x; 297 | const float* coeff_ptr = coeff->factor + meta->coeff_meta; 298 | 299 | float result = 0.f; 300 | for (int ly = 0; ly < coeff->filter_size; ly++) 301 | { 302 | for (int lx = 0; lx < coeff->filter_size; lx++) 303 | { 304 | result += src_ptr[lx] * coeff_ptr[lx]; 305 | } 306 | coeff_ptr += coeff->coeff_stride; 307 | src_ptr += src_stride; 308 | } 309 | 310 | dstp[x] = clamp(result, -1.f, 1.f); 311 | 312 | meta++; 313 | } 314 | 315 | dstp += dst_stride; 316 | } 317 | } 318 | 319 | #if defined(USE_AVX2) 320 | /* Planar resampling with coeff table */ 321 | /* 8-16 bit */ 322 | /*template 323 | static void resize_plane_avx2(EWAPixelCoeff* coeff, const T* srcp, T* VS_RESTRICT dstp, 324 | int dst_width, int dst_height, int src_stride, int dst_stride, int peak) 325 | { 326 | EWAPixelCoeffMeta* meta = coeff->meta; 327 | 328 | for (int y = 0; y < dst_height; y++) 329 | { 330 | for (int x = 0; x < dst_width; x++) 331 | { 332 | const T* src_ptr = srcp + meta->start_y * src_stride + meta->start_x; 333 | const float* coeff_ptr = coeff->factor + meta->coeff_meta; 334 | 335 | float result = 0.f; 336 | auto rres = _mm256_setzero_ps(); 337 | for (int ly = 0; ly < coeff->filter_size; ly++) 338 | { 339 | for (int lx = 0; lx < coeff->filter_size / 8; lx++) 340 | { 341 | auto rsrc = _mm256_loadu_ps(reinterpret_cast(src_ptr + lx * 8)); 342 | auto rcof = _mm256_load_ps(coeff_ptr + lx * 8); 343 | rres = _mm256_fmadd_ps(rsrc, rcof, rres); 344 | } 345 | for (int lx = coeff->filter_size - coeff->filter_size % 8; lx < coeff->filter_size; ++lx) 346 | { 347 | result += src_ptr[lx] * coeff_ptr[lx]; 348 | } 349 | coeff_ptr += coeff->coeff_stride; 350 | src_ptr += src_stride; 351 | } 352 | result += reduce(rres); 353 | dstp[x] = static_cast(clamp(result, 0.f, (float)peak)); 354 | 355 | ++meta; 356 | } 357 | dstp += dst_stride; 358 | } 359 | }*/ 360 | 361 | /* Planar resampling with coeff table */ 362 | /* 32 bit */ 363 | static void resize_plane_avx2(EWAPixelCoeff* coeff, const float* srcp, float* VS_RESTRICT dstp, 364 | int dst_width, int dst_height, int src_stride, int dst_stride) 365 | { 366 | EWAPixelCoeffMeta* meta = coeff->meta; 367 | 368 | for (int y = 0; y < dst_height; y++) 369 | { 370 | for (int x = 0; x < dst_width; x++) 371 | { 372 | const float* src_ptr = srcp + meta->start_y * src_stride + meta->start_x; 373 | const float* coeff_ptr = coeff->factor + meta->coeff_meta; 374 | 375 | float result = 0.f; 376 | auto rres = _mm256_setzero_ps(); 377 | for (int ly = 0; ly < coeff->filter_size; ly++) 378 | { 379 | for (int lx = 0; lx < coeff->filter_size / 8; lx++) 380 | { 381 | auto rsrc = _mm256_loadu_ps(src_ptr + lx * 8); 382 | auto rcof = _mm256_load_ps(coeff_ptr + lx * 8); 383 | rres = _mm256_fmadd_ps(rsrc, rcof, rres); 384 | } 385 | for (int lx = coeff->filter_size - coeff->filter_size % 8; lx < coeff->filter_size; ++lx) 386 | { 387 | result += src_ptr[lx] * coeff_ptr[lx]; 388 | } 389 | coeff_ptr += coeff->coeff_stride; 390 | src_ptr += src_stride; 391 | } 392 | result += reduce(rres); 393 | dstp[x] = clamp(result, -1.f, 1.f); 394 | 395 | ++meta; 396 | } 397 | dstp += dst_stride; 398 | } 399 | } 400 | #endif 401 | 402 | #endif 403 | --------------------------------------------------------------------------------