|
| 1 | +""" |
| 2 | +Copyright (C) 2025 sgt0 |
| 3 | +Copyright (C) 2025 yuygfgg |
| 4 | +
|
| 5 | +This file is part of Vapoursynth-llvmexpr. |
| 6 | +
|
| 7 | +This file is derived from and has been modified from code |
| 8 | +originally contributed by sgt0 in a pull request to the akarin-vapoursynth-plugin |
| 9 | +project (https://github.com/Jaded-Encoding-Thaumaturgy/akarin-vapoursynth-plugin/pull/12), |
| 10 | +which was licensed under LGPLv3. |
| 11 | +
|
| 12 | +As Vapoursynth-llvmexpr is licensed under the GNU General Public License v3, this |
| 13 | +modified file is also distributed under the same license. |
| 14 | +
|
| 15 | +Vapoursynth-llvmexpr is free software: you can redistribute it and/or modify |
| 16 | +it under the terms of the GNU General Public License as published by |
| 17 | +the Free Software Foundation, either version 3 of the License, or |
| 18 | +(at your option) any later version. |
| 19 | +
|
| 20 | +Vapoursynth-llvmexpr is distributed in the hope that it will be useful, |
| 21 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 23 | +GNU General Public License for more details. |
| 24 | +
|
| 25 | +You should have received a copy of the GNU General Public License |
| 26 | +along with Vapoursynth-llvmexpr. If not, see <https://www.gnu.org/licenses/>. |
| 27 | +""" |
| 28 | + |
| 29 | +import pytest |
| 30 | +import vapoursynth as vs |
| 31 | + |
| 32 | +core = vs.core |
| 33 | + |
| 34 | + |
| 35 | +@pytest.mark.parametrize( |
| 36 | + "input_format, a, b, expr, expected", |
| 37 | + [ |
| 38 | + (vs.GRAYS, 2.0, 3.0, "x y +", 5.0), |
| 39 | + (vs.GRAYS, 7.0, 5.0, "x y -", 2.0), |
| 40 | + (vs.GRAYS, 4.0, 2.5, "x y *", 10.0), |
| 41 | + (vs.GRAYS, 7.5, 2.5, "x y /", 3.0), |
| 42 | + (vs.GRAYS, 5.25, 1.0, "x 1.0 %", 0.25), |
| 43 | + ], |
| 44 | +) |
| 45 | +def test_arithmetic( |
| 46 | + input_format: int, a: float, b: float, expr: str, expected: float |
| 47 | +) -> None: |
| 48 | + c1 = core.std.BlankClip(format=input_format, color=a) |
| 49 | + c2 = core.std.BlankClip(format=input_format, color=b) |
| 50 | + res = core.llvmexpr.Expr([c1, c2], expr, vs.GRAYS) |
| 51 | + assert res.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 52 | + |
| 53 | + |
| 54 | +@pytest.mark.parametrize( |
| 55 | + "a, b, expr, expected", |
| 56 | + [ |
| 57 | + (3.0, 2.0, "x y >", 1.0), |
| 58 | + (2.0, 3.0, "x y <", 1.0), |
| 59 | + (3.0, 3.0, "x y =", 1.0), |
| 60 | + (3.0, 3.0, "x y >=", 1.0), |
| 61 | + (2.0, 3.0, "x y <=", 1.0), |
| 62 | + (1.0, 0.0, "x y and", 0.0), |
| 63 | + (1.0, 0.0, "x y or", 1.0), |
| 64 | + (1.0, 1.0, "x y xor", 0.0), |
| 65 | + (0.0, 0.0, "x not", 1.0), |
| 66 | + (-2, -3, "x y and", 0.0), |
| 67 | + (-2, -1, "x y or", 0.0), |
| 68 | + (-2, -1, "x y xor", 0.0), |
| 69 | + (-2, 3, "x y xor", 1.0), |
| 70 | + ], |
| 71 | +) |
| 72 | +def test_comparison_and_logical(a: float, b: float, expr: str, expected: float) -> None: |
| 73 | + c1 = core.std.BlankClip(format=vs.GRAYS, color=a) |
| 74 | + c2 = core.std.BlankClip(format=vs.GRAYS, color=b) |
| 75 | + res = core.llvmexpr.Expr([c1, c2], expr, vs.GRAYS) |
| 76 | + assert res.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 77 | + |
| 78 | + |
| 79 | +@pytest.mark.parametrize( |
| 80 | + "input_format, input_value, expr, expected", |
| 81 | + [ |
| 82 | + (vs.GRAY8, 0, "0 exp", 1.0), |
| 83 | + (vs.GRAY8, 0, "x exp", 1.0), |
| 84 | + (vs.GRAY8, 1, "x exp", 2.71828), |
| 85 | + (vs.GRAYS, 0.5, "x exp", 1.64872), |
| 86 | + ], |
| 87 | +) |
| 88 | +def test_exp(input_format: int, input_value: int, expr: str, expected: float) -> None: |
| 89 | + clip = core.std.BlankClip(format=input_format, color=input_value) |
| 90 | + result = core.llvmexpr.Expr(clip, expr, vs.GRAYS) |
| 91 | + assert result.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 92 | + |
| 93 | + |
| 94 | +@pytest.mark.parametrize( |
| 95 | + "input_format, input_value, expr, expected", |
| 96 | + [ |
| 97 | + (vs.GRAY8, 0, "0 log", float("-inf")), |
| 98 | + ( |
| 99 | + vs.GRAY8, |
| 100 | + 0, |
| 101 | + "x log", |
| 102 | + float("-inf"), |
| 103 | + ), # differs from std.Expr's -87.3365478515625 |
| 104 | + (vs.GRAY8, 1, "x log", 0), |
| 105 | + (vs.GRAYS, 7.38905, "x log", 2), |
| 106 | + ], |
| 107 | +) |
| 108 | +def test_log(input_format: int, input_value: int, expr: str, expected: float) -> None: |
| 109 | + clip = core.std.BlankClip(format=input_format, color=input_value) |
| 110 | + result = core.llvmexpr.Expr(clip, expr, vs.GRAYS) |
| 111 | + assert result.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 112 | + |
| 113 | + |
| 114 | +@pytest.mark.parametrize( |
| 115 | + "val, expr, expected", |
| 116 | + [ |
| 117 | + (1.9, "x floor", 1.0), |
| 118 | + (1.1, "x ceil", 2.0), |
| 119 | + (2.49, "x round", 2.0), |
| 120 | + (2.5, "x round", 3.0), |
| 121 | + (-2.9, "x trunc", -2.0), |
| 122 | + (2.9, "x abs", 2.9), |
| 123 | + (3.0, "x neg", -3.0), |
| 124 | + (-5.0, "x sgn", -1.0), |
| 125 | + (0.0, "x sgn", 0.0), |
| 126 | + (7.0, "x sgn", 1.0), |
| 127 | + (2.0, "x -3 copysign", -2.0), |
| 128 | + (2.0, "2 3 4 fma", 10.0), |
| 129 | + ], |
| 130 | +) |
| 131 | +def test_rounding_and_misc(val: float, expr: str, expected: float) -> None: |
| 132 | + c = core.std.BlankClip(format=vs.GRAYS, color=val) |
| 133 | + res = core.llvmexpr.Expr(c, expr, vs.GRAYS) |
| 134 | + assert res.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 135 | + |
| 136 | + |
| 137 | +@pytest.mark.parametrize("input_format", [vs.GRAY8, vs.GRAY16, vs.GRAYS]) |
| 138 | +@pytest.mark.parametrize( |
| 139 | + "expr, expected", |
| 140 | + [ |
| 141 | + ("x 1.5 pow", 0.0), |
| 142 | + ], |
| 143 | +) |
| 144 | +def test_pow(input_format: int, expr: str, expected: float) -> None: |
| 145 | + clip = core.std.BlankClip(format=input_format, color=0) |
| 146 | + result = core.llvmexpr.Expr(clip, expr, vs.GRAYS) |
| 147 | + assert result.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 148 | + |
| 149 | + |
| 150 | +@pytest.mark.parametrize( |
| 151 | + "input_format, input_value, expr, expected", |
| 152 | + [ |
| 153 | + (vs.GRAY8, 0, "x sin", 0), |
| 154 | + (vs.GRAY8, 1, "x sin", 0.8414709568023682), |
| 155 | + (vs.GRAY8, 2, "x sin", 0.9092974066734314), |
| 156 | + ], |
| 157 | +) |
| 158 | +def test_sin(input_format: int, input_value: int, expr: str, expected: float) -> None: |
| 159 | + clip = core.std.BlankClip(format=input_format, color=input_value) |
| 160 | + result = core.llvmexpr.Expr(clip, expr, vs.GRAYS) |
| 161 | + assert result.get_frame(0)[0][0, 0] == pytest.approx(expected) |
| 162 | + |
| 163 | + |
| 164 | +def test_gh_11() -> None: |
| 165 | + clip = core.std.BlankClip(format=vs.GRAY8, color=0) |
| 166 | + result = core.llvmexpr.Expr(clip, "x 128 / 0.86 pow 255 *") |
| 167 | + assert result.get_frame(0)[0][0, 0] == pytest.approx(6.122468756907559e-31) |
| 168 | + |
| 169 | + clip = core.std.BlankClip(format=vs.GRAY16, color=0) |
| 170 | + result = core.llvmexpr.Expr(clip, "x 32768 / 0.86 pow 65535 *") |
| 171 | + assert result.get_frame(0)[0][0, 0] == pytest.approx(1.5734745330615421e-28) |
| 172 | + |
| 173 | + |
| 174 | +@pytest.mark.parametrize( |
| 175 | + "val, min_v, max_v, expected_max, expected_min", |
| 176 | + [ |
| 177 | + (5.0, 2.0, 8.0, 8.0, 2.0), |
| 178 | + (1.0, 2.0, 8.0, 2.0, 1.0), |
| 179 | + ], |
| 180 | +) |
| 181 | +def test_min_max_clip( |
| 182 | + val: float, min_v: float, max_v: float, expected_max: float, expected_min: float |
| 183 | +) -> None: |
| 184 | + c = core.std.BlankClip(format=vs.GRAYS, color=val) |
| 185 | + res_max = core.llvmexpr.Expr(c, "x 3 max", vs.GRAYS) |
| 186 | + res_min = core.llvmexpr.Expr(c, "x 3 min", vs.GRAYS) |
| 187 | + res_clip = core.llvmexpr.Expr(c, f"x {min_v} {max_v} clip", vs.GRAYS) |
| 188 | + assert res_max.get_frame(0)[0][0, 0] == pytest.approx(max(val, 3.0)) |
| 189 | + assert res_min.get_frame(0)[0][0, 0] == pytest.approx(min(val, 3.0)) |
| 190 | + assert res_clip.get_frame(0)[0][0, 0] == pytest.approx(min(max(val, min_v), max_v)) |
| 191 | + |
| 192 | + |
| 193 | +@pytest.mark.parametrize( |
| 194 | + "a, b, expr, expected", |
| 195 | + [ |
| 196 | + (5.9, 1.0, "x y bitand", 1.0), |
| 197 | + (5.0, 2.0, "x y bitor", 7.0), |
| 198 | + (5.0, 1.0, "x y bitxor", 4.0), |
| 199 | + ( |
| 200 | + 5.0, |
| 201 | + 0.0, |
| 202 | + "x bitnot", |
| 203 | + ( |
| 204 | + float(2**32 - 1 - 5) |
| 205 | + if vs.core.core_version.release_major |
| 206 | + >= 64 # pyright: ignore[reportOperatorIssue] |
| 207 | + else float(~5 & 0xFFFFFFFF) |
| 208 | + ), |
| 209 | + ), |
| 210 | + ], |
| 211 | +) |
| 212 | +def test_bitwise(a: float, b: float, expr: str, expected: float) -> None: |
| 213 | + c1 = core.std.BlankClip(format=vs.GRAYS, color=a) |
| 214 | + c2 = core.std.BlankClip(format=vs.GRAYS, color=b) |
| 215 | + res = core.llvmexpr.Expr([c1, c2], expr, vs.GRAYS) |
| 216 | + # bitnot result depends on implementation width; for generality, compare masked |
| 217 | + out = res.get_frame(0)[0][0, 0] |
| 218 | + if "bitnot" in expr: |
| 219 | + assert int(out) & 0xFFFFFFFF == (~int(a)) & 0xFFFFFFFF |
| 220 | + else: |
| 221 | + assert out == pytest.approx(expected) |
| 222 | + |
| 223 | + |
| 224 | +def test_stack_manipulation() -> None: |
| 225 | + c = core.std.BlankClip(format=vs.GRAYS, color=3.0) |
| 226 | + res_dup = core.llvmexpr.Expr(c, "x dup *", vs.GRAYS) |
| 227 | + assert res_dup.get_frame(0)[0][0, 0] == pytest.approx(9.0) |
| 228 | + c0 = core.std.BlankClip(format=vs.GRAYS, color=0.0) |
| 229 | + res_swap = core.llvmexpr.Expr(c0, "2 5 swap -", vs.GRAYS) |
| 230 | + assert res_swap.get_frame(0)[0][0, 0] == pytest.approx(3.0) |
| 231 | + res_drop = core.llvmexpr.Expr(c0, "1 2 3 drop2", vs.GRAYS) |
| 232 | + assert res_drop.get_frame(0)[0][0, 0] == pytest.approx(1.0) |
| 233 | + res_sort = core.llvmexpr.Expr(c0, "3 1 2 sort3 drop2", vs.GRAYS) |
| 234 | + assert res_sort.get_frame(0)[0][0, 0] == pytest.approx(1.0) |
| 235 | + |
| 236 | + |
| 237 | +def test_named_variables_and_loop_power() -> None: |
| 238 | + c = core.std.BlankClip(format=vs.GRAYS, color=2.0) |
| 239 | + y = core.std.BlankClip(format=vs.GRAYS, color=4.0) |
| 240 | + expr = "x base! 1 result! y counter! #loop result@ base@ * result! counter@ 1 - counter! counter@ loop# result@" |
| 241 | + res = core.llvmexpr.Expr([c, y], expr, vs.GRAYS) |
| 242 | + assert res.get_frame(0)[0][0, 0] == pytest.approx(16.0) |
| 243 | + |
| 244 | + |
| 245 | +def test_constants_and_coords() -> None: |
| 246 | + c0 = core.std.BlankClip(format=vs.GRAYS, color=0.0, width=3, height=2) |
| 247 | + res_pi = core.llvmexpr.Expr(c0, "pi", vs.GRAYS) |
| 248 | + assert res_pi.get_frame(0)[0][0, 0] == pytest.approx(3.14159265, rel=1e-6) |
| 249 | + res_N = core.llvmexpr.Expr(c0, "N", vs.GRAYS) |
| 250 | + assert res_N.get_frame(3)[0][0, 0] == pytest.approx(3.0) |
| 251 | + res_wh = core.llvmexpr.Expr(c0, "width height +", vs.GRAYS) |
| 252 | + assert res_wh.get_frame(0)[0][0, 0] == pytest.approx(5.0) |
| 253 | + res_X = core.llvmexpr.Expr(c0, "X", vs.GRAYS) |
| 254 | + res_Y = core.llvmexpr.Expr(c0, "Y", vs.GRAYS) |
| 255 | + fX = res_X.get_frame(0) |
| 256 | + fY = res_Y.get_frame(0) |
| 257 | + assert fX[0][1, 2] == pytest.approx(2.0) |
| 258 | + assert fY[0][1, 2] == pytest.approx(1.0) |
| 259 | + |
| 260 | + |
| 261 | +def test_conditional_ternary() -> None: |
| 262 | + c = core.std.BlankClip(format=vs.GRAYS, color=10.0) |
| 263 | + res = core.llvmexpr.Expr(c, "x 5 > 1 0 ?", vs.GRAYS) |
| 264 | + assert res.get_frame(0)[0][0, 0] == pytest.approx(1.0) |
| 265 | + |
| 266 | + |
| 267 | +def test_pixel_access_static_and_dynamic() -> None: |
| 268 | + base = core.std.BlankClip(format=vs.GRAYS, color=0.0, width=4, height=2) |
| 269 | + ramp = core.llvmexpr.Expr(base, "X", vs.GRAYS) |
| 270 | + src = core.std.BlankClip(format=vs.GRAYS, color=99.0, width=4, height=2) |
| 271 | + expr_rel = "y[-1,0]" |
| 272 | + res_rel = core.llvmexpr.Expr([src, ramp], expr_rel, vs.GRAYS) |
| 273 | + f = res_rel.get_frame(0) |
| 274 | + assert f[0][0, 2] == pytest.approx(1.0) |
| 275 | + expr_abs = "1 1 y[]" |
| 276 | + res_abs = core.llvmexpr.Expr([src, ramp], expr_abs, vs.GRAYS) |
| 277 | + assert res_abs.get_frame(0)[0][0, 0] == pytest.approx(1.0) |
| 278 | + |
| 279 | + |
| 280 | +def test_frame_property_access() -> None: |
| 281 | + c = core.std.BlankClip(format=vs.GRAYS, color=0.0) |
| 282 | + c = core.std.SetFrameProps(c, _TestProp=0.25) |
| 283 | + res = core.llvmexpr.Expr(c, "x._TestProp", vs.GRAYS) |
| 284 | + assert res.get_frame(0)[0][0, 0] == pytest.approx(0.25) |
| 285 | + |
| 286 | + |
| 287 | +def test_direct_output_write_and_exit() -> None: |
| 288 | + base = core.std.BlankClip(format=vs.GRAYS, color=0.0, width=4, height=4) |
| 289 | + expr = "X 1 = Y 2 = and 5 1 2 @[] ^exit^ 0 ?" |
| 290 | + res = core.llvmexpr.Expr(base, expr, vs.GRAYS) |
| 291 | + fr = res.get_frame(0) |
| 292 | + assert fr[0][2, 1] == pytest.approx(5.0) |
| 293 | + assert fr[0][0, 0] == pytest.approx(0.0) |
0 commit comments