Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom kernel support and resizeable filter curves #28

Merged
merged 19 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vskernels/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import exceptions, kernels, util # noqa: F401, F403
from .exceptions import * # noqa: F401, F403
from .kernels import * # noqa: F401, F403
from .types import * # noqa: F401, F403
from .util import * # noqa: F401, F403
5 changes: 1 addition & 4 deletions vskernels/kernels/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from .abstract import * # noqa: F401, F403
from .bicubic import * # noqa: F401, F403
from .complex import * # noqa: F401, F403
from .fmtconv import * # noqa: F401, F403
from .impulse import * # noqa: F401, F403
from .custom import * # noqa: F401, F403
from .placebo import * # noqa: F401, F403
from .resize import * # noqa: F401, F403
from .spline import * # noqa: F401, F403
from .various import * # noqa: F401, F403
from .zimg import * # noqa: F401, F403
29 changes: 21 additions & 8 deletions vskernels/kernels/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
from vstools.enums.color import _norm_props_enums

from ..exceptions import UnknownDescalerError, UnknownKernelError, UnknownResamplerError, UnknownScalerError
from ..types import BotFieldLeftShift, BotFieldTopShift, LeftShift, TopFieldLeftShift, TopFieldTopShift, TopShift
from ..types import (
BorderHandling, BotFieldLeftShift, BotFieldTopShift, LeftShift, SampleGridModel, TopFieldLeftShift,
TopFieldTopShift, TopShift
)

__all__ = [
'Scaler', 'ScalerT',
Expand Down Expand Up @@ -135,7 +138,7 @@ def __init_subclass__(cls) -> None:
return

from ..util import abstract_kernels
from .zimg import ZimgComplexKernel
from .complex import CustomComplexKernel

if cls in abstract_kernels:
return
Expand All @@ -152,7 +155,7 @@ def __init_subclass__(cls) -> None:
if 'kernel_radius' in cls.__dict__.keys():
return

mro = [cls, *({*cls.mro()} - {*ZimgComplexKernel.mro()})]
mro = [cls, *({*cls.mro()} - {*CustomComplexKernel.mro()})]

for sub_cls in mro:
if hasattr(sub_cls, '_static_kernel_radius'):
Expand Down Expand Up @@ -189,7 +192,7 @@ def ensure_obj(
cls, (mro := cls.mro())[mro.index(BaseScaler) - 1], scaler, cls._err_class, [], func_except
)

@inject_self.property
@inject_self.cached.property
def kernel_radius(self) -> int:
return _default_kernel_radius(__class__, self) # type: ignore

Expand Down Expand Up @@ -272,25 +275,33 @@ def descale( # type: ignore[override]
shift: tuple[TopShift, LeftShift] | tuple[
TopShift | tuple[TopFieldTopShift, BotFieldTopShift],
LeftShift | tuple[TopFieldLeftShift, BotFieldLeftShift]
] = (0, 0), **kwargs: Any
] = (0, 0), *,
border_handling: BorderHandling = BorderHandling.MIRROR,
sample_grid_model: SampleGridModel = SampleGridModel.MATCH_EDGES,
field_based: FieldBased | None = None,
**kwargs: Any
) -> vs.VideoNode:
width, height = self._wh_norm(clip, width, height)

check_correct_subsampling(clip, width, height)

field_based = FieldBased.from_param_or_video(kwargs.pop('field_based', None), clip)
field_based = FieldBased.from_param_or_video(field_based, clip)

clip, bits = expect_bits(clip, 32)

de_base_args = (width, height // (1 + field_based.is_inter))
kwargs |= dict(border_handling=border_handling)

if field_based.is_inter:
shift_y, shift_x = tuple[tuple[float, float], ...](
sh if isinstance(sh, tuple) else (sh, sh) for sh in shift
)

de_kwargs_tf = self.get_descale_args(clip, (shift_y[0], shift_x[0]), *de_base_args, **kwargs)
de_kwargs_bf = self.get_descale_args(clip, (shift_y[1], shift_x[1]), *de_base_args, **kwargs)
kwargs_tf, shift = sample_grid_model.for_descale(clip, width, height, (shift_y[0], shift_x[0]), **kwargs)
kwargs_bf, shift = sample_grid_model.for_descale(clip, width, height, (shift_y[1], shift_x[1]), **kwargs)

de_kwargs_tf = self.get_descale_args(clip, (shift_y[0], shift_x[0]), *de_base_args, **kwargs_tf)
de_kwargs_bf = self.get_descale_args(clip, (shift_y[1], shift_x[1]), *de_base_args, **kwargs_bf)

if height % 2:
raise CustomIndexError('You can\'t descale to odd resolution when crossconverted!', self.descale)
Expand All @@ -311,6 +322,8 @@ def descale( # type: ignore[override]
if any(isinstance(sh, tuple) for sh in shift):
raise CustomValueError('You can\'t descale per-field when the input is progressive!', self.descale)

kwargs, shift = sample_grid_model.for_descale(clip, width, height, shift, **kwargs) # type: ignore

de_kwargs = self.get_descale_args(clip, shift, *de_base_args, **kwargs) # type: ignore

descaled = self.descale_function(clip, **_norm_props_enums(de_kwargs))
Expand Down
90 changes: 42 additions & 48 deletions vskernels/kernels/bicubic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from math import sqrt
from typing import Any

from vstools import CustomValueError, core, inject_self, vs
from vstools import CustomValueError, inject_self

from .zimg import ZimgComplexKernel
from .complex import CustomComplexKernel
from .helpers import bic_vals, poly3

__all__ = [
'Bicubic',
Expand All @@ -15,6 +16,8 @@
'Catrom',
'FFmpegBicubic',
'AdobeBicubic',
'AdobeBicubicSharper',
'AdobeBicubicSmoother',
'BicubicSharp',
'RobidouxSoft',
'Robidoux',
Expand All @@ -23,37 +26,34 @@
]


class Bicubic(ZimgComplexKernel):
class Bicubic(CustomComplexKernel):
"""
Built-in bicubic resizer.

Default: b=0, c=0.5

Dependencies:

* VapourSynth-descale

:param b: B-param for bicubic kernel
:param c: C-param for bicubic kernel
"""

scale_function = resample_function = core.lazy.resize.Bicubic
descale_function = core.lazy.descale.Debicubic

def __init__(self, b: float = 0, c: float = 1 / 2, **kwargs: Any) -> None:
self.b = b
self.c = c
super().__init__(**kwargs)

def get_params_args(
self, is_descale: bool, clip: vs.VideoNode, width: int | None = None, height: int | None = None, **kwargs: Any
) -> dict[str, Any]:
args = super().get_params_args(is_descale, clip, width, height, **kwargs)
if is_descale:
return args | dict(b=self.b, c=self.c)
return args | dict(filter_param_a=self.b, filter_param_b=self.c)
@inject_self.cached
def kernel(self, *, x: float) -> float: # type: ignore
x, b, c = abs(x), self.b, self.c

if (x < 1.0):
return poly3(x, bic_vals.p0(b, c), 0.0, bic_vals.p2(b, c), bic_vals.p3(b, c))

if (x < 2.0):
return poly3(x, bic_vals.q0(b, c), bic_vals.q1(b, c), bic_vals.q2(b, c), bic_vals.q3(b, c))

return 0.0

@inject_self.property
@inject_self.cached.property
def kernel_radius(self) -> int: # type: ignore
if (self.b, self.c) == (0, 0):
return 1
Expand Down Expand Up @@ -102,6 +102,20 @@ def __init__(self, **kwargs: Any) -> None:
super().__init__(b=0, c=3 / 4, **kwargs)


class AdobeBicubicSharper(Bicubic):
"""Bicubic b=0, c=1, blur=1.05; Adobe's "Bicubic Sharper" interpolation preset."""

def __init__(self, **kwargs: Any) -> None:
super().__init__(b=0, c=1, blur=1.05, **kwargs)


class AdobeBicubicSmoother(Bicubic):
"""Bicubic b=0, c=0.625, blur=1.15; Adobe's "Bicubic Smoother" interpolation preset."""

def __init__(self, **kwargs: Any) -> None:
super().__init__(b=0, c=5 / 8, blur=1.15, **kwargs)


class BicubicSharp(Bicubic):
"""Bicubic b=0, c=1"""

Expand Down Expand Up @@ -136,47 +150,27 @@ def __init__(self, **kwargs: Any) -> None:
super().__init__(b=b, c=c, **kwargs)


class BicubicAuto(ZimgComplexKernel):
class BicubicAuto(Bicubic):
"""
Kernel that follows the rule of:
b + 2c = target
"""

scale_function = resample_function = core.lazy.resize.Bicubic
descale_function = core.lazy.descale.Debicubic

def __init__(self, b: float | None = None, c: float | None = None, target: float = 1.0, **kwargs: Any) -> None:
def __init__(self, b: float | None = None, c: float | None = None, **kwargs: Any) -> None:
if None not in {b, c}:
raise CustomValueError("You can't specify both b and c!", self.__class__)

self.b = b
self.c = c
self.target = target
self.b, self.c = self._get_bc_args(b, c)

super().__init__(**kwargs)

def get_params_args(
self, is_descale: bool, clip: vs.VideoNode, width: int | None = None, height: int | None = None, **kwargs: Any
) -> dict[str, Any]:
args = super().get_params_args(is_descale, clip, width, height, **kwargs)
def _get_bc_args(self, b: float | None, c: float | None) -> tuple[float, float]:
autob = 0.0 if b is None else b
autoc = 0.5 if c is None else c

b, c = self._get_bc_args()

if is_descale:
return args | dict(b=b, c=c)
return args | dict(filter_param_a=b, filter_param_b=c)

def _get_bc_args(self) -> tuple[float, float]:
autob = 0.0 if self.b is None else self.b
autoc = 0.5 if self.c is None else self.c

if self.c is not None and self.b is None:
autob = self.target - 2 * self.c
elif self.c is None and self.b is not None:
autoc = (self.target - self.b) / 2
if c is not None and b is None:
autob = 1.0 - 2 * c
elif c is None and b is not None:
autoc = (1.0 - b) / 2

return autob, autoc

@inject_self.property
def kernel_radius(self) -> int: # type: ignore
return Bicubic(*self._get_bc_args()).kernel_radius
Loading
Loading