about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/PIL/ImageFilter.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/PIL/ImageFilter.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/PIL/ImageFilter.py')
-rw-r--r--.venv/lib/python3.12/site-packages/PIL/ImageFilter.py605
1 files changed, 605 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/PIL/ImageFilter.py b/.venv/lib/python3.12/site-packages/PIL/ImageFilter.py
new file mode 100644
index 00000000..b350e56f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/PIL/ImageFilter.py
@@ -0,0 +1,605 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# standard filters
+#
+# History:
+# 1995-11-27 fl   Created
+# 2002-06-08 fl   Added rank and mode filters
+# 2003-09-15 fl   Fixed rank calculation in rank filter; added expand call
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2002 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+from __future__ import annotations
+
+import abc
+import functools
+from collections.abc import Sequence
+from types import ModuleType
+from typing import TYPE_CHECKING, Any, Callable, cast
+
+if TYPE_CHECKING:
+    from . import _imaging
+    from ._typing import NumpyArray
+
+
+class Filter:
+    @abc.abstractmethod
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        pass
+
+
+class MultibandFilter(Filter):
+    pass
+
+
+class BuiltinFilter(MultibandFilter):
+    filterargs: tuple[Any, ...]
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        if image.mode == "P":
+            msg = "cannot filter palette images"
+            raise ValueError(msg)
+        return image.filter(*self.filterargs)
+
+
+class Kernel(BuiltinFilter):
+    """
+    Create a convolution kernel. This only supports 3x3 and 5x5 integer and floating
+    point kernels.
+
+    Kernels can only be applied to "L" and "RGB" images.
+
+    :param size: Kernel size, given as (width, height). This must be (3,3) or (5,5).
+    :param kernel: A sequence containing kernel weights. The kernel will be flipped
+                   vertically before being applied to the image.
+    :param scale: Scale factor. If given, the result for each pixel is divided by this
+                  value. The default is the sum of the kernel weights.
+    :param offset: Offset. If given, this value is added to the result, after it has
+                   been divided by the scale factor.
+    """
+
+    name = "Kernel"
+
+    def __init__(
+        self,
+        size: tuple[int, int],
+        kernel: Sequence[float],
+        scale: float | None = None,
+        offset: float = 0,
+    ) -> None:
+        if scale is None:
+            # default scale is sum of kernel
+            scale = functools.reduce(lambda a, b: a + b, kernel)
+        if size[0] * size[1] != len(kernel):
+            msg = "not enough coefficients in kernel"
+            raise ValueError(msg)
+        self.filterargs = size, scale, offset, kernel
+
+
+class RankFilter(Filter):
+    """
+    Create a rank filter.  The rank filter sorts all pixels in
+    a window of the given size, and returns the ``rank``'th value.
+
+    :param size: The kernel size, in pixels.
+    :param rank: What pixel value to pick.  Use 0 for a min filter,
+                 ``size * size / 2`` for a median filter, ``size * size - 1``
+                 for a max filter, etc.
+    """
+
+    name = "Rank"
+
+    def __init__(self, size: int, rank: int) -> None:
+        self.size = size
+        self.rank = rank
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        if image.mode == "P":
+            msg = "cannot filter palette images"
+            raise ValueError(msg)
+        image = image.expand(self.size // 2, self.size // 2)
+        return image.rankfilter(self.size, self.rank)
+
+
+class MedianFilter(RankFilter):
+    """
+    Create a median filter. Picks the median pixel value in a window with the
+    given size.
+
+    :param size: The kernel size, in pixels.
+    """
+
+    name = "Median"
+
+    def __init__(self, size: int = 3) -> None:
+        self.size = size
+        self.rank = size * size // 2
+
+
+class MinFilter(RankFilter):
+    """
+    Create a min filter.  Picks the lowest pixel value in a window with the
+    given size.
+
+    :param size: The kernel size, in pixels.
+    """
+
+    name = "Min"
+
+    def __init__(self, size: int = 3) -> None:
+        self.size = size
+        self.rank = 0
+
+
+class MaxFilter(RankFilter):
+    """
+    Create a max filter.  Picks the largest pixel value in a window with the
+    given size.
+
+    :param size: The kernel size, in pixels.
+    """
+
+    name = "Max"
+
+    def __init__(self, size: int = 3) -> None:
+        self.size = size
+        self.rank = size * size - 1
+
+
+class ModeFilter(Filter):
+    """
+    Create a mode filter. Picks the most frequent pixel value in a box with the
+    given size.  Pixel values that occur only once or twice are ignored; if no
+    pixel value occurs more than twice, the original pixel value is preserved.
+
+    :param size: The kernel size, in pixels.
+    """
+
+    name = "Mode"
+
+    def __init__(self, size: int = 3) -> None:
+        self.size = size
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        return image.modefilter(self.size)
+
+
+class GaussianBlur(MultibandFilter):
+    """Blurs the image with a sequence of extended box filters, which
+    approximates a Gaussian kernel. For details on accuracy see
+    <https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
+
+    :param radius: Standard deviation of the Gaussian kernel. Either a sequence of two
+                   numbers for x and y, or a single number for both.
+    """
+
+    name = "GaussianBlur"
+
+    def __init__(self, radius: float | Sequence[float] = 2) -> None:
+        self.radius = radius
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        xy = self.radius
+        if isinstance(xy, (int, float)):
+            xy = (xy, xy)
+        if xy == (0, 0):
+            return image.copy()
+        return image.gaussian_blur(xy)
+
+
+class BoxBlur(MultibandFilter):
+    """Blurs the image by setting each pixel to the average value of the pixels
+    in a square box extending radius pixels in each direction.
+    Supports float radius of arbitrary size. Uses an optimized implementation
+    which runs in linear time relative to the size of the image
+    for any radius value.
+
+    :param radius: Size of the box in a direction. Either a sequence of two numbers for
+                   x and y, or a single number for both.
+
+                   Radius 0 does not blur, returns an identical image.
+                   Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total.
+    """
+
+    name = "BoxBlur"
+
+    def __init__(self, radius: float | Sequence[float]) -> None:
+        xy = radius if isinstance(radius, (tuple, list)) else (radius, radius)
+        if xy[0] < 0 or xy[1] < 0:
+            msg = "radius must be >= 0"
+            raise ValueError(msg)
+        self.radius = radius
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        xy = self.radius
+        if isinstance(xy, (int, float)):
+            xy = (xy, xy)
+        if xy == (0, 0):
+            return image.copy()
+        return image.box_blur(xy)
+
+
+class UnsharpMask(MultibandFilter):
+    """Unsharp mask filter.
+
+    See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
+    the parameters.
+
+    :param radius: Blur Radius
+    :param percent: Unsharp strength, in percent
+    :param threshold: Threshold controls the minimum brightness change that
+      will be sharpened
+
+    .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
+
+    """
+
+    name = "UnsharpMask"
+
+    def __init__(
+        self, radius: float = 2, percent: int = 150, threshold: int = 3
+    ) -> None:
+        self.radius = radius
+        self.percent = percent
+        self.threshold = threshold
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        return image.unsharp_mask(self.radius, self.percent, self.threshold)
+
+
+class BLUR(BuiltinFilter):
+    name = "Blur"
+    # fmt: off
+    filterargs = (5, 5), 16, 0, (
+        1, 1, 1, 1, 1,
+        1, 0, 0, 0, 1,
+        1, 0, 0, 0, 1,
+        1, 0, 0, 0, 1,
+        1, 1, 1, 1, 1,
+    )
+    # fmt: on
+
+
+class CONTOUR(BuiltinFilter):
+    name = "Contour"
+    # fmt: off
+    filterargs = (3, 3), 1, 255, (
+        -1, -1, -1,
+        -1,  8, -1,
+        -1, -1, -1,
+    )
+    # fmt: on
+
+
+class DETAIL(BuiltinFilter):
+    name = "Detail"
+    # fmt: off
+    filterargs = (3, 3), 6, 0, (
+        0,  -1,  0,
+        -1, 10, -1,
+        0,  -1,  0,
+    )
+    # fmt: on
+
+
+class EDGE_ENHANCE(BuiltinFilter):
+    name = "Edge-enhance"
+    # fmt: off
+    filterargs = (3, 3), 2, 0, (
+        -1, -1, -1,
+        -1, 10, -1,
+        -1, -1, -1,
+    )
+    # fmt: on
+
+
+class EDGE_ENHANCE_MORE(BuiltinFilter):
+    name = "Edge-enhance More"
+    # fmt: off
+    filterargs = (3, 3), 1, 0, (
+        -1, -1, -1,
+        -1,  9, -1,
+        -1, -1, -1,
+    )
+    # fmt: on
+
+
+class EMBOSS(BuiltinFilter):
+    name = "Emboss"
+    # fmt: off
+    filterargs = (3, 3), 1, 128, (
+        -1, 0, 0,
+        0,  1, 0,
+        0,  0, 0,
+    )
+    # fmt: on
+
+
+class FIND_EDGES(BuiltinFilter):
+    name = "Find Edges"
+    # fmt: off
+    filterargs = (3, 3), 1, 0, (
+        -1, -1, -1,
+        -1,  8, -1,
+        -1, -1, -1,
+    )
+    # fmt: on
+
+
+class SHARPEN(BuiltinFilter):
+    name = "Sharpen"
+    # fmt: off
+    filterargs = (3, 3), 16, 0, (
+        -2, -2, -2,
+        -2, 32, -2,
+        -2, -2, -2,
+    )
+    # fmt: on
+
+
+class SMOOTH(BuiltinFilter):
+    name = "Smooth"
+    # fmt: off
+    filterargs = (3, 3), 13, 0, (
+        1, 1, 1,
+        1, 5, 1,
+        1, 1, 1,
+    )
+    # fmt: on
+
+
+class SMOOTH_MORE(BuiltinFilter):
+    name = "Smooth More"
+    # fmt: off
+    filterargs = (5, 5), 100, 0, (
+        1, 1,  1, 1, 1,
+        1, 5,  5, 5, 1,
+        1, 5, 44, 5, 1,
+        1, 5,  5, 5, 1,
+        1, 1,  1, 1, 1,
+    )
+    # fmt: on
+
+
+class Color3DLUT(MultibandFilter):
+    """Three-dimensional color lookup table.
+
+    Transforms 3-channel pixels using the values of the channels as coordinates
+    in the 3D lookup table and interpolating the nearest elements.
+
+    This method allows you to apply almost any color transformation
+    in constant time by using pre-calculated decimated tables.
+
+    .. versionadded:: 5.2.0
+
+    :param size: Size of the table. One int or tuple of (int, int, int).
+                 Minimal size in any dimension is 2, maximum is 65.
+    :param table: Flat lookup table. A list of ``channels * size**3``
+                  float elements or a list of ``size**3`` channels-sized
+                  tuples with floats. Channels are changed first,
+                  then first dimension, then second, then third.
+                  Value 0.0 corresponds lowest value of output, 1.0 highest.
+    :param channels: Number of channels in the table. Could be 3 or 4.
+                     Default is 3.
+    :param target_mode: A mode for the result image. Should have not less
+                        than ``channels`` channels. Default is ``None``,
+                        which means that mode wouldn't be changed.
+    """
+
+    name = "Color 3D LUT"
+
+    def __init__(
+        self,
+        size: int | tuple[int, int, int],
+        table: Sequence[float] | Sequence[Sequence[int]] | NumpyArray,
+        channels: int = 3,
+        target_mode: str | None = None,
+        **kwargs: bool,
+    ) -> None:
+        if channels not in (3, 4):
+            msg = "Only 3 or 4 output channels are supported"
+            raise ValueError(msg)
+        self.size = size = self._check_size(size)
+        self.channels = channels
+        self.mode = target_mode
+
+        # Hidden flag `_copy_table=False` could be used to avoid extra copying
+        # of the table if the table is specially made for the constructor.
+        copy_table = kwargs.get("_copy_table", True)
+        items = size[0] * size[1] * size[2]
+        wrong_size = False
+
+        numpy: ModuleType | None = None
+        if hasattr(table, "shape"):
+            try:
+                import numpy
+            except ImportError:
+                pass
+
+        if numpy and isinstance(table, numpy.ndarray):
+            numpy_table: NumpyArray = table
+            if copy_table:
+                numpy_table = numpy_table.copy()
+
+            if numpy_table.shape in [
+                (items * channels,),
+                (items, channels),
+                (size[2], size[1], size[0], channels),
+            ]:
+                table = numpy_table.reshape(items * channels)
+            else:
+                wrong_size = True
+
+        else:
+            if copy_table:
+                table = list(table)
+
+            # Convert to a flat list
+            if table and isinstance(table[0], (list, tuple)):
+                raw_table = cast(Sequence[Sequence[int]], table)
+                flat_table: list[int] = []
+                for pixel in raw_table:
+                    if len(pixel) != channels:
+                        msg = (
+                            "The elements of the table should "
+                            f"have a length of {channels}."
+                        )
+                        raise ValueError(msg)
+                    flat_table.extend(pixel)
+                table = flat_table
+
+        if wrong_size or len(table) != items * channels:
+            msg = (
+                "The table should have either channels * size**3 float items "
+                "or size**3 items of channels-sized tuples with floats. "
+                f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. "
+                f"Actual length: {len(table)}"
+            )
+            raise ValueError(msg)
+        self.table = table
+
+    @staticmethod
+    def _check_size(size: Any) -> tuple[int, int, int]:
+        try:
+            _, _, _ = size
+        except ValueError as e:
+            msg = "Size should be either an integer or a tuple of three integers."
+            raise ValueError(msg) from e
+        except TypeError:
+            size = (size, size, size)
+        size = tuple(int(x) for x in size)
+        for size_1d in size:
+            if not 2 <= size_1d <= 65:
+                msg = "Size should be in [2, 65] range."
+                raise ValueError(msg)
+        return size
+
+    @classmethod
+    def generate(
+        cls,
+        size: int | tuple[int, int, int],
+        callback: Callable[[float, float, float], tuple[float, ...]],
+        channels: int = 3,
+        target_mode: str | None = None,
+    ) -> Color3DLUT:
+        """Generates new LUT using provided callback.
+
+        :param size: Size of the table. Passed to the constructor.
+        :param callback: Function with three parameters which correspond
+                         three color channels. Will be called ``size**3``
+                         times with values from 0.0 to 1.0 and should return
+                         a tuple with ``channels`` elements.
+        :param channels: The number of channels which should return callback.
+        :param target_mode: Passed to the constructor of the resulting
+                            lookup table.
+        """
+        size_1d, size_2d, size_3d = cls._check_size(size)
+        if channels not in (3, 4):
+            msg = "Only 3 or 4 output channels are supported"
+            raise ValueError(msg)
+
+        table: list[float] = [0] * (size_1d * size_2d * size_3d * channels)
+        idx_out = 0
+        for b in range(size_3d):
+            for g in range(size_2d):
+                for r in range(size_1d):
+                    table[idx_out : idx_out + channels] = callback(
+                        r / (size_1d - 1), g / (size_2d - 1), b / (size_3d - 1)
+                    )
+                    idx_out += channels
+
+        return cls(
+            (size_1d, size_2d, size_3d),
+            table,
+            channels=channels,
+            target_mode=target_mode,
+            _copy_table=False,
+        )
+
+    def transform(
+        self,
+        callback: Callable[..., tuple[float, ...]],
+        with_normals: bool = False,
+        channels: int | None = None,
+        target_mode: str | None = None,
+    ) -> Color3DLUT:
+        """Transforms the table values using provided callback and returns
+        a new LUT with altered values.
+
+        :param callback: A function which takes old lookup table values
+                         and returns a new set of values. The number
+                         of arguments which function should take is
+                         ``self.channels`` or ``3 + self.channels``
+                         if ``with_normals`` flag is set.
+                         Should return a tuple of ``self.channels`` or
+                         ``channels`` elements if it is set.
+        :param with_normals: If true, ``callback`` will be called with
+                             coordinates in the color cube as the first
+                             three arguments. Otherwise, ``callback``
+                             will be called only with actual color values.
+        :param channels: The number of channels in the resulting lookup table.
+        :param target_mode: Passed to the constructor of the resulting
+                            lookup table.
+        """
+        if channels not in (None, 3, 4):
+            msg = "Only 3 or 4 output channels are supported"
+            raise ValueError(msg)
+        ch_in = self.channels
+        ch_out = channels or ch_in
+        size_1d, size_2d, size_3d = self.size
+
+        table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out)
+        idx_in = 0
+        idx_out = 0
+        for b in range(size_3d):
+            for g in range(size_2d):
+                for r in range(size_1d):
+                    values = self.table[idx_in : idx_in + ch_in]
+                    if with_normals:
+                        values = callback(
+                            r / (size_1d - 1),
+                            g / (size_2d - 1),
+                            b / (size_3d - 1),
+                            *values,
+                        )
+                    else:
+                        values = callback(*values)
+                    table[idx_out : idx_out + ch_out] = values
+                    idx_in += ch_in
+                    idx_out += ch_out
+
+        return type(self)(
+            self.size,
+            table,
+            channels=ch_out,
+            target_mode=target_mode or self.mode,
+            _copy_table=False,
+        )
+
+    def __repr__(self) -> str:
+        r = [
+            f"{self.__class__.__name__} from {self.table.__class__.__name__}",
+            "size={:d}x{:d}x{:d}".format(*self.size),
+            f"channels={self.channels:d}",
+        ]
+        if self.mode:
+            r.append(f"target_mode={self.mode}")
+        return "<{}>".format(" ".join(r))
+
+    def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+        from . import Image
+
+        return image.color_lut_3d(
+            self.mode or image.mode,
+            Image.Resampling.BILINEAR,
+            self.channels,
+            self.size[0],
+            self.size[1],
+            self.size[2],
+            self.table,
+        )