├── README.md ├── nnedi3_resample.md └── nnedi3_resample.py /README.md: -------------------------------------------------------------------------------- 1 | # VapourSynth-script 2 | 3 | **NOTE: this repository is deprecated** 4 | 5 | nnedi3_resample has been moved to: https://github.com/HomeOfVapourSynthEvolution/nnedi3_resample 6 | -------------------------------------------------------------------------------- /nnedi3_resample.md: -------------------------------------------------------------------------------- 1 | # nnedi3_resample 2 | 3 | ## Requirements 4 | 5 | - [nnedi3](https://github.com/dubhater/vapoursynth-nnedi3) / [znedi3](https://github.com/sekrit-twc/znedi3) / [nnedi3cl](https://github.com/HomeOfVapourSynthEvolution/VapourSynth-NNEDI3CL) 6 | - [fmtconv](https://github.com/EleonoreMizo/fmtconv) 7 | - [mvsfunc](https://github.com/HomeOfVapourSynthEvolution/mvsfunc) 8 | 9 | ## Usage 10 | 11 | Put nnedi3_resample.py into `\Lib\site-packages` 12 | 13 | ## Function 14 | 15 | It can do scaling, color space conversion, etc. 16 | 17 | **Note**: Internally always processing in 16-bit integer, and the output format can be specified by "csp" with Format id (default is the same as input). 18 | 19 | ## Example 20 | 21 | Double the width and height of a clip. 22 | 23 | ```python 24 | import vapoursynth as vs 25 | from vapoursynth import core 26 | import nnedi3_resample as nnrs 27 | 28 | clip = XXXSource() 29 | clip = nnrs.nnedi3_resample(clip, clip.width * 2, clip.height * 2, mode='znedi3') 30 | 31 | clip.set_output() 32 | ``` 33 | 34 | ## ChangeLog 35 | 36 | 1. Add new option `mode`, default value is `nnedi3`, optional value: 37 | - `znedi3`, it may be faster than nnedi3 38 | - `nnedi3cl`, it can be used with new options `device` (same as the parameter of the plugin with the same name) 39 | 2. Chage how to import core because `get_core` is deprecated. 40 | 3. Remove `YCOCG` and `COMPAT`, these will be deprecated in API4. 41 | -------------------------------------------------------------------------------- /nnedi3_resample.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | import mvsfunc as mvf 4 | import math 5 | 6 | 7 | def nnedi3_resample(input, target_width=None, target_height=None, src_left=None, src_top=None, src_width=None, src_height=None, csp=None, mats=None, matd=None, cplaces=None, cplaced=None, fulls=None, fulld=None, curves=None, curved=None, sigmoid=None, scale_thr=None, nsize=None, nns=None, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, kernel=None, invks=False, taps=None, invkstaps=3, a1=None, a2=None, chromak_up=None, chromak_up_taps=None, chromak_up_a1=None, chromak_up_a2=None, chromak_down=None, chromak_down_invks=False, chromak_down_invkstaps=3, chromak_down_taps=None, chromak_down_a1=None, chromak_down_a2=None, mode='nnedi3', device=None): 8 | funcName = 'nnedi3_resample' 9 | 10 | # Get property about input clip 11 | if not isinstance(input, vs.VideoNode): 12 | raise TypeError(funcName + ': This is not a clip!') 13 | 14 | sFormat = input.format 15 | 16 | sColorFamily = sFormat.color_family 17 | sIsGRAY = sColorFamily == vs.GRAY 18 | sIsYUV = sColorFamily == vs.YUV 19 | sIsRGB = sColorFamily == vs.RGB 20 | 21 | sbitPS = sFormat.bits_per_sample 22 | 23 | sHSubS = 1 << sFormat.subsampling_w 24 | sVSubS = 1 << sFormat.subsampling_h 25 | sIsSubS = sHSubS > 1 or sVSubS > 1 26 | 27 | sPlaneNum = sFormat.num_planes 28 | 29 | # Get property about output clip 30 | dFormat = sFormat if csp is None else core.get_format(csp) 31 | 32 | dColorFamily = dFormat.color_family 33 | dIsGRAY = dColorFamily == vs.GRAY 34 | dIsYUV = dColorFamily == vs.YUV 35 | dIsRGB = dColorFamily == vs.RGB 36 | 37 | dbitPS = dFormat.bits_per_sample 38 | 39 | dHSubS = 1 << dFormat.subsampling_w 40 | dVSubS = 1 << dFormat.subsampling_h 41 | dIsSubS = dHSubS > 1 or dVSubS > 1 42 | 43 | dPlaneNum = dFormat.num_planes 44 | 45 | # Parameters of format 46 | SD = input.width <= 1024 and input.height <= 576 47 | HD = input.width <= 2048 and input.height <= 1536 48 | 49 | if mats is None: 50 | mats = "601" if SD else "709" if HD else "2020" 51 | else: 52 | mats = mats.lower() 53 | if matd is None: 54 | matd = mats 55 | else: 56 | matd = matd.lower() 57 | # Matrix of output clip makes sense only if dst is not of RGB 58 | if dIsRGB: 59 | matd = mats 60 | # Matrix of input clip makes sense only src is not of GRAY or RGB 61 | if sIsGRAY or sIsRGB: 62 | mats = matd 63 | if cplaces is None: 64 | if sHSubS == 4: 65 | cplaces = 'dv' 66 | else: 67 | cplaces = 'mpeg2' 68 | else: 69 | cplaces = cplaces.lower() 70 | if cplaced is None: 71 | if dHSubS == 4: 72 | cplaced = 'dv' 73 | else: 74 | cplaced = cplaces 75 | else: 76 | cplaced = cplaced.lower() 77 | if fulls is None: 78 | fulls = sColorFamily == vs.RGB 79 | if fulld is None: 80 | if dColorFamily == sColorFamily: 81 | fulld = fulls 82 | else: 83 | fulld = dColorFamily == vs.RGB 84 | if curves is None: 85 | curves = 'linear' 86 | else: 87 | curves = curves.lower() 88 | if curved is None: 89 | curved = curves 90 | else: 91 | curved = curved.lower() 92 | if sigmoid is None: 93 | sigmoid = False 94 | 95 | # Parameters of scaling 96 | if target_width is None: 97 | target_width = input.width 98 | if target_height is None: 99 | target_height = input.height 100 | if src_left is None: 101 | src_left = 0 102 | if src_top is None: 103 | src_top = 0 104 | if src_width is None: 105 | src_width = input.width 106 | elif src_width <= 0: 107 | src_width = input.width - src_left + src_width 108 | if src_height is None: 109 | src_height = input.height 110 | elif src_height <= 0: 111 | src_height = input.height - src_top + src_height 112 | if scale_thr is None: 113 | scale_thr = 1.125 114 | 115 | src_right = src_width - input.width + src_left 116 | src_bottom = src_height - input.height + src_top 117 | 118 | hScale = target_width / src_width 119 | vScale = target_height / src_height 120 | 121 | # Parameters of nnedi3 122 | if nsize is None: 123 | nsize = 0 124 | if nns is None: 125 | nns = 3 126 | if qual is None: 127 | qual = 2 128 | 129 | # Parameters of fmtc.resample 130 | if kernel is None: 131 | if not invks: 132 | kernel = 'spline36' 133 | else: 134 | kernel = 'bilinear' 135 | else: 136 | kernel = kernel.lower() 137 | if chromak_up is None: 138 | chromak_up = 'nnedi3' 139 | else: 140 | chromak_up = chromak_up.lower() 141 | if chromak_up == 'softcubic': 142 | chromak_up = 'bicubic' 143 | if chromak_up_a1 is None: 144 | chromak_up_a1 = 75 145 | chromak_up_a1 = chromak_up_a1 / 100 146 | chromak_up_a2 = 1 - chromak_up_a1 147 | if chromak_down is None: 148 | chromak_down = 'bicubic' 149 | else: 150 | chromak_down = chromak_down.lower() 151 | if chromak_down == 'softcubic': 152 | chromak_down = 'bicubic' 153 | if chromak_down_a1 is None: 154 | chromak_down_a1 = 75 155 | chromak_down_a1 = chromak_down_a1 / 100 156 | chromak_down_a2 = 1 - chromak_down_a1 157 | 158 | # Procedure decision 159 | hIsScale = hScale != 1 160 | vIsScale = vScale != 1 161 | isScale = hIsScale or vIsScale 162 | hResample = hIsScale or int(src_left) != src_left or int(src_right) != src_right 163 | vResample = vIsScale or int(src_top) != src_top or int(src_bottom) != src_bottom 164 | resample = hResample or vResample 165 | hReSubS = dHSubS != sHSubS 166 | vReSubS = dVSubS != sVSubS 167 | reSubS = hReSubS or vReSubS 168 | sigmoid = sigmoid and resample 169 | sGammaConv = curves != 'linear' 170 | dGammaConv = curved != 'linear' 171 | gammaConv = (sGammaConv or dGammaConv or sigmoid) and (resample or curved != curves) 172 | scaleInGRAY = sIsGRAY or dIsGRAY 173 | scaleInYUV = not scaleInGRAY and mats == matd and not gammaConv and (reSubS or (sIsYUV and dIsYUV)) 174 | scaleInRGB = not scaleInGRAY and not scaleInYUV 175 | # If matrix conversion or gamma correction is applied, scaling will be done in RGB. Otherwise, if at least one of input&output clip is RGB and no chroma subsampling is involved, scaling will be done in RGB. 176 | 177 | # Chroma placement relative to the frame center in luma scale 178 | sCLeftAlign = cplaces == 'mpeg2' or cplaces == 'dv' 179 | sHCPlace = 0 if not sCLeftAlign else 0.5 - sHSubS / 2 180 | sVCPlace = 0 181 | dCLeftAlign = cplaced == 'mpeg2' or cplaced == 'dv' 182 | dHCPlace = 0 if not dCLeftAlign else 0.5 - dHSubS / 2 183 | dVCPlace = 0 184 | 185 | # Convert depth to 16-bit 186 | last = mvf.Depth(input, depth=16, fulls=fulls) 187 | 188 | # Color space conversion before scaling 189 | if scaleInGRAY and sIsYUV: 190 | if mats != matd: 191 | last = core.fmtc.matrix(last, mats=mats, matd=matd, fulls=fulls, fulld=fulld, col_fam=vs.GRAY, singleout=0) 192 | last = core.std.ShufflePlanes(last, [0], vs.GRAY) 193 | elif scaleInGRAY and sIsRGB: 194 | # Matrix conversion for output clip of GRAY 195 | last = core.fmtc.matrix(last, mat=matd, fulls=fulls, fulld=fulld, col_fam=vs.GRAY, singleout=0) 196 | fulls = fulld 197 | elif scaleInRGB and sIsYUV: 198 | # Chroma upsampling 199 | if sIsSubS: 200 | if chromak_up == 'nnedi3': 201 | # Separate planes 202 | Y = core.std.ShufflePlanes(last, [0], vs.GRAY) 203 | U = core.std.ShufflePlanes(last, [1], vs.GRAY) 204 | V = core.std.ShufflePlanes(last, [2], vs.GRAY) 205 | # Chroma up-scaling 206 | U = nnedi3_resample_kernel(U, Y.width, Y.height, -sHCPlace / sHSubS, -sVCPlace / sVSubS, None, None, 1, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, mode=mode, device=device) 207 | V = nnedi3_resample_kernel(V, Y.width, Y.height, -sHCPlace / sHSubS, -sVCPlace / sVSubS, None, None, 1, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, mode=mode, device=device) 208 | # Merge planes 209 | last = core.std.ShufflePlanes([Y, U, V], [0, 0, 0], last.format.color_family) 210 | else: 211 | last = core.fmtc.resample(last, kernel=chromak_up, taps=chromak_up_taps, a1=chromak_up_a1, a2=chromak_up_a2, css="444", fulls=fulls, cplaces=cplaces) 212 | # Matrix conversion 213 | if mats == '2020cl': 214 | last = core.fmtc.matrix2020cl(last, fulls) 215 | else: 216 | last = core.fmtc.matrix(last, mat=mats, fulls=fulls, fulld=True, col_fam=vs.RGB, singleout=-1) 217 | fulls = True 218 | elif scaleInYUV and sIsRGB: 219 | # Matrix conversion 220 | if matd == '2020cl': 221 | last = core.fmtc.matrix2020cl(last, fulld) 222 | else: 223 | last = core.fmtc.matrix(last, mat=matd, fulls=fulls, fulld=fulld, col_fam=vs.YUV, singleout=-1) 224 | fulls = fulld 225 | 226 | # Scaling 227 | if scaleInGRAY or scaleInRGB: 228 | if gammaConv and sGammaConv: 229 | last = GammaToLinear(last, fulls, fulls, curves, sigmoid=sigmoid) 230 | elif sigmoid: 231 | last = SigmoidInverse(last) 232 | last = nnedi3_resample_kernel(last, target_width, target_height, src_left, src_top, src_width, src_height, scale_thr, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, invks, invkstaps, mode, device) 233 | if gammaConv and dGammaConv: 234 | last = LinearToGamma(last, fulls, fulls, curved, sigmoid=sigmoid) 235 | elif sigmoid: 236 | last = SigmoidDirect(last) 237 | elif scaleInYUV: 238 | # Separate planes 239 | Y = core.std.ShufflePlanes(last, [0], vs.GRAY) 240 | U = core.std.ShufflePlanes(last, [1], vs.GRAY) 241 | V = core.std.ShufflePlanes(last, [2], vs.GRAY) 242 | # Scale Y 243 | Y = nnedi3_resample_kernel(Y, target_width, target_height, src_left, src_top, src_width, src_height, scale_thr, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, mode=mode, device=device) 244 | # Scale UV 245 | dCw = target_width // dHSubS 246 | dCh = target_height // dVSubS 247 | dCsx = ((src_left - sHCPlace) * hScale + dHCPlace) / hScale / sHSubS 248 | dCsy = ((src_top - sVCPlace) * vScale + dVCPlace) / vScale / sVSubS 249 | dCsw = src_width / sHSubS 250 | dCsh = src_height / sVSubS 251 | U = nnedi3_resample_kernel(U, dCw, dCh, dCsx, dCsy, dCsw, dCsh, scale_thr, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, mode=mode, device=device) 252 | V = nnedi3_resample_kernel(V, dCw, dCh, dCsx, dCsy, dCsw, dCsh, scale_thr, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, mode=mode, device=device) 253 | # Merge planes 254 | last = core.std.ShufflePlanes([Y, U, V], [0, 0, 0], last.format.color_family) 255 | 256 | # Color space conversion after scaling 257 | if scaleInGRAY and dIsYUV: 258 | dCw = target_width // dHSubS 259 | dCh = target_height // dVSubS 260 | last = mvf.Depth(last, depth=dbitPS, fulls=fulls, fulld=fulld) 261 | blkUV = core.std.BlankClip(last, dCw, dCh, color=[1 << (dbitPS - 1)]) 262 | last = core.std.ShufflePlanes([last, blkUV, blkUV], [0, 0, 0], dColorFamily) 263 | elif scaleInGRAY and dIsRGB: 264 | last = mvf.Depth(last, depth=dbitPS, fulls=fulls, fulld=fulld) 265 | last = core.std.ShufflePlanes([last, last, last], [0, 0, 0], dColorFamily) 266 | elif scaleInRGB and dIsYUV: 267 | # Matrix conversion 268 | if matd == '2020cl': 269 | last = core.fmtc.matrix2020cl(last, fulld) 270 | else: 271 | last = core.fmtc.matrix(last, mat=matd, fulls=fulls, fulld=fulld, col_fam=dColorFamily, singleout=-1) 272 | # Chroma subsampling 273 | if dIsSubS: 274 | dCSS = '411' if dHSubS == 4 else '420' if dVSubS == 2 else '422' 275 | last = core.fmtc.resample(last, kernel=chromak_down, taps=chromak_down_taps, a1=chromak_down_a1, a2=chromak_down_a2, css=dCSS, fulls=fulld, cplaced=cplaced, invks=chromak_down_invks, invkstaps=chromak_down_invkstaps, planes=[2,3,3]) 276 | last = mvf.Depth(last, depth=dbitPS, fulls=fulld) 277 | elif scaleInYUV and dIsRGB: 278 | # Matrix conversion 279 | if mats == '2020cl': 280 | last = core.fmtc.matrix2020cl(last, fulls) 281 | else: 282 | last = core.fmtc.matrix(last, mat=mats, fulls=fulls, fulld=True, col_fam=vs.RGB, singleout=-1) 283 | last = mvf.Depth(last, depth=dbitPS, fulls=True, fulld=fulld) 284 | else: 285 | last = mvf.Depth(last, depth=dbitPS, fulls=fulls, fulld=fulld) 286 | 287 | # Output 288 | return last 289 | 290 | 291 | def nnedi3_resample_kernel(input, target_width=None, target_height=None, src_left=None, src_top=None, src_width=None, src_height=None, scale_thr=None, nsize=None, nns=None, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, kernel=None, taps=None, a1=None, a2=None, invks=False, invkstaps=3, mode=None, device=None): 292 | 293 | # Parameters of scaling 294 | if target_width is None: 295 | target_width = input.width 296 | if target_height is None: 297 | target_height = input.height 298 | if src_left is None: 299 | src_left = 0 300 | if src_top is None: 301 | src_top = 0 302 | if src_width is None: 303 | src_width = input.width 304 | elif src_width <= 0: 305 | src_width = input.width - src_left + src_width 306 | if src_height is None: 307 | src_height = input.height 308 | elif src_height <= 0: 309 | src_height = input.height - src_top + src_height 310 | if scale_thr is None: 311 | scale_thr = 1.125 312 | 313 | src_right = src_width - input.width + src_left 314 | src_bottom = src_height - input.height + src_top 315 | 316 | hScale = target_width / src_width 317 | vScale = target_height / src_height 318 | 319 | # Parameters of nnedi3 320 | if nsize is None: 321 | nsize = 0 322 | if nns is None: 323 | nns = 3 324 | if qual is None: 325 | qual = 2 326 | 327 | # Parameters of fmtc.resample 328 | if kernel is None: 329 | kernel = 'spline36' 330 | else: 331 | kernel = kernel.lower() 332 | 333 | # Procedure decision 334 | hIsScale = hScale != 1 335 | vIsScale = vScale != 1 336 | isScale = hIsScale or vIsScale 337 | hResample = hIsScale or int(src_left) != src_left or int(src_right) != src_right 338 | vResample = vIsScale or int(src_top) != src_top or int(src_bottom) != src_bottom 339 | resample = hResample or vResample 340 | 341 | # Scaling 342 | last = input 343 | 344 | if hResample: 345 | last = core.std.Transpose(last) 346 | last = nnedi3_resample_kernel_vertical(last, target_width, src_left, src_width, scale_thr, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, invks, invkstaps, mode, device) 347 | last = core.std.Transpose(last) 348 | if vResample: 349 | last = nnedi3_resample_kernel_vertical(last, target_height, src_top, src_height, scale_thr, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, kernel, taps, a1, a2, invks, invkstaps, mode, device) 350 | 351 | # Output 352 | return last 353 | 354 | 355 | def nnedi3_resample_kernel_vertical(input, target_height=None, src_top=None, src_height=None, scale_thr=None, nsize=None, nns=None, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, kernel=None, taps=None, a1=None, a2=None, invks=False, invkstaps=3, mode=None, device=None): 356 | 357 | # Parameters of scaling 358 | if target_height is None: 359 | target_height = input.height 360 | if src_top is None: 361 | src_top = 0 362 | if src_height is None: 363 | src_height = input.height 364 | elif src_height <= 0: 365 | src_height = input.height - src_top + src_height 366 | if scale_thr is None: 367 | scale_thr = 1.125 368 | 369 | scale = target_height / src_height # Total scaling ratio 370 | eTimes = math.ceil(math.log(scale / scale_thr, 2)) if scale > scale_thr else 0 # Iterative times of nnedi3 371 | eScale = 1 << eTimes # Scaling ratio of nnedi3 372 | pScale = scale / eScale # Scaling ratio of fmtc.resample 373 | 374 | # Parameters of nnedi3 375 | if nsize is None: 376 | nsize = 0 377 | if nns is None: 378 | nns = 3 379 | if qual is None: 380 | qual = 2 381 | 382 | # Parameters of fmtc.resample 383 | if kernel is None: 384 | kernel = 'spline36' 385 | else: 386 | kernel = kernel.lower() 387 | 388 | # Skip scaling if not needed 389 | if scale == 1 and src_top == 0 and src_height == input.height: 390 | return input 391 | 392 | # Scaling with nnedi3 393 | last = nnedi3_rpow2_vertical(input, eTimes, 1, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, mode, device) 394 | 395 | # Center shift calculation 396 | vShift = 0.5 if eTimes >= 1 else 0 397 | 398 | # Scaling with fmtc.resample as well as correct center shift 399 | w = last.width 400 | h = target_height 401 | sx = 0 402 | sy = src_top * eScale - vShift 403 | sw = last.width 404 | sh = src_height * eScale 405 | 406 | if h != last.height or sy != 0 or sh != last.height: 407 | if h < last.height and invks is True: 408 | last = core.fmtc.resample(last, w, h, sx, sy, sw, sh, kernel=kernel, taps=taps, a1=a1, a2=a2, invks=True, invkstaps=invkstaps) 409 | else: 410 | last = core.fmtc.resample(last, w, h, sx, sy, sw, sh, kernel=kernel, taps=taps, a1=a1, a2=a2) 411 | 412 | # Output 413 | return last 414 | 415 | 416 | def nnedi3_rpow2_vertical(input, eTimes=1, field=1, nsize=None, nns=None, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, mode=None, device=None): 417 | 418 | if eTimes >= 1: 419 | last = nnedi3_dh(input, field, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, mode, device) 420 | eTimes = eTimes - 1 421 | field = 0 422 | else: 423 | last = input 424 | 425 | if eTimes >= 1: 426 | return nnedi3_rpow2_vertical(last, eTimes, field, nsize, nns, qual, etype, pscrn, opt, int16_prescreener, int16_predictor, exp, mode, device) 427 | else: 428 | return last 429 | 430 | 431 | def nnedi3_dh(input, field=1, nsize=None, nns=None, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, mode=None, device=None): 432 | nnedi3_args1 = dict(nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn) 433 | nnedi3_args2 = dict(opt=opt, int16_prescreener=int16_prescreener, int16_predictor=int16_predictor, exp=exp) 434 | 435 | if mode == 'nnedi3': 436 | res = core.nnedi3.nnedi3(input, field=field, dh=True, **nnedi3_args1, **nnedi3_args2) 437 | elif mode == 'znedi3': 438 | res = core.znedi3.nnedi3(input, field=field, dh=True, **nnedi3_args1, **nnedi3_args2) 439 | elif mode == 'nnedi3cl': 440 | res = core.nnedi3cl.NNEDI3CL(input, field=field, dh=True, **nnedi3_args1, device=device) 441 | else: raise ValueError('nnedi3_dh: Unsupported mode, should be nnedi3 (default), znedi3 or nnedi3cl.') 442 | 443 | return res 444 | 445 | 446 | ## Gamma conversion functions from HAvsFunc-r18 447 | # Convert the luma channel to linear light 448 | def GammaToLinear(src, fulls=True, fulld=True, curve='709', planes=[0, 1, 2], gcor=1., sigmoid=False, thr=0.5, cont=6.5): 449 | if not isinstance(src, vs.VideoNode) or src.format.bits_per_sample != 16: 450 | raise ValueError('GammaToLinear: This is not a 16-bit clip') 451 | 452 | return LinearAndGamma(src, False, fulls, fulld, curve.lower(), planes, gcor, sigmoid, thr, cont) 453 | 454 | # Convert back a clip to gamma-corrected luma 455 | def LinearToGamma(src, fulls=True, fulld=True, curve='709', planes=[0, 1, 2], gcor=1., sigmoid=False, thr=0.5, cont=6.5): 456 | if not isinstance(src, vs.VideoNode) or src.format.bits_per_sample != 16: 457 | raise ValueError('LinearToGamma: This is not a 16-bit clip') 458 | 459 | return LinearAndGamma(src, True, fulls, fulld, curve.lower(), planes, gcor, sigmoid, thr, cont) 460 | 461 | def LinearAndGamma(src, l2g_flag, fulls, fulld, curve, planes, gcor, sigmoid, thr, cont): 462 | 463 | if curve == 'srgb': 464 | c_num = 0 465 | elif curve in ['709', '601', '170']: 466 | c_num = 1 467 | elif curve == '240': 468 | c_num = 2 469 | elif curve == '2020': 470 | c_num = 3 471 | else: 472 | raise ValueError('LinearAndGamma: wrong curve value') 473 | 474 | if src.format.color_family == vs.GRAY: 475 | planes = [0] 476 | 477 | # BT-709/601 478 | # sRGB SMPTE 170M SMPTE 240M BT-2020 479 | k0 = [0.04045, 0.081, 0.0912, 0.08145][c_num] 480 | phi = [12.92, 4.5, 4.0, 4.5][c_num] 481 | alpha = [0.055, 0.099, 0.1115, 0.0993][c_num] 482 | gamma = [2.4, 2.22222, 2.22222, 2.22222][c_num] 483 | 484 | def g2l(x): 485 | expr = x / 65536 if fulls else (x - 4096) / 56064 486 | if expr <= k0: 487 | expr /= phi 488 | else: 489 | expr = ((expr + alpha) / (1 + alpha)) ** gamma 490 | if gcor != 1 and expr >= 0: 491 | expr **= gcor 492 | if sigmoid: 493 | x0 = 1 / (1 + math.exp(cont * thr)) 494 | x1 = 1 / (1 + math.exp(cont * (thr - 1))) 495 | expr = thr - math.log(max(1 / max(expr * (x1 - x0) + x0, 0.000001) - 1, 0.000001)) / cont 496 | if fulld: 497 | return min(max(round(expr * 65536), 0), 65535) 498 | else: 499 | return min(max(round(expr * 56064 + 4096), 0), 65535) 500 | 501 | # E' = (E <= k0 / phi) ? E * phi : (E ^ (1 / gamma)) * (alpha + 1) - alpha 502 | def l2g(x): 503 | expr = x / 65536 if fulls else (x - 4096) / 56064 504 | if sigmoid: 505 | x0 = 1 / (1 + math.exp(cont * thr)) 506 | x1 = 1 / (1 + math.exp(cont * (thr - 1))) 507 | expr = (1 / (1 + math.exp(cont * (thr - expr))) - x0) / (x1 - x0) 508 | if gcor != 1 and expr >= 0: 509 | expr **= gcor 510 | if expr <= k0 / phi: 511 | expr *= phi 512 | else: 513 | expr = expr ** (1 / gamma) * (alpha + 1) - alpha 514 | if fulld: 515 | return min(max(round(expr * 65536), 0), 65535) 516 | else: 517 | return min(max(round(expr * 56064 + 4096), 0), 65535) 518 | 519 | return core.std.Lut(src, planes=planes, function=l2g if l2g_flag else g2l) 520 | 521 | # Apply the inverse sigmoid curve to a clip in linear luminance 522 | def SigmoidInverse(src, thr=0.5, cont=6.5, planes=[0, 1, 2]): 523 | 524 | if not isinstance(src, vs.VideoNode) or src.format.bits_per_sample != 16: 525 | raise ValueError('SigmoidInverse: This is not a 16-bit clip') 526 | 527 | if src.format.color_family == vs.GRAY: 528 | planes = [0] 529 | 530 | def get_lut(x): 531 | x0 = 1 / (1 + math.exp(cont * thr)) 532 | x1 = 1 / (1 + math.exp(cont * (thr - 1))) 533 | return min(max(round((thr - math.log(max(1 / max(x / 65536 * (x1 - x0) + x0, 0.000001) - 1, 0.000001)) / cont) * 65536), 0), 65535) 534 | 535 | return core.std.Lut(src, planes=planes, function=get_lut) 536 | 537 | # Convert back a clip to linear luminance 538 | def SigmoidDirect(src, thr=0.5, cont=6.5, planes=[0, 1, 2]): 539 | 540 | if not isinstance(src, vs.VideoNode) or src.format.bits_per_sample != 16: 541 | raise ValueError('SigmoidDirect: This is not a 16-bit clip') 542 | 543 | if src.format.color_family == vs.GRAY: 544 | planes = [0] 545 | 546 | def get_lut(x): 547 | x0 = 1 / (1 + math.exp(cont * thr)) 548 | x1 = 1 / (1 + math.exp(cont * (thr - 1))) 549 | return min(max(round(((1 / (1 + math.exp(cont * (thr - x / 65536))) - x0) / (x1 - x0)) * 65536), 0), 65535) 550 | 551 | return core.std.Lut(src, planes=planes, function=get_lut) 552 | ## Gamma conversion functions from HAvsFunc-r18 553 | --------------------------------------------------------------------------------