diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a425df6..aff7d60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,16 @@ jobs: pip install flake8 flake8 . + - name: Type check with mypy + run: | + # Pin mypy to <1.19 due to librt dependency in 1.19+ + # which is not available on PyPy 3.9 + pip install "mypy<1.19" + mypy pyvips/__init__.pyi --no-error-summary + mypy examples/affine.py examples/convolve.py examples/try5.py examples/watermark.py \ + examples/annotate-animation.py examples/join-animation.py examples/progress.py \ + --no-error-summary + - name: Install tox and any other packages run: pip install tox diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 279ec64..4e15636 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,10 @@ +## Version 3.2.0 (released TBA) + +- add comprehensive type hints for all pyvips operations via generated stubs [JoshCLWren] +- add operator overload type hints with array support (int, float, list[int], list[float]) [JoshCLWren] +- add hand-written binding type hints for common methods [JoshCLWren] +- add test coverage for type stubs [JoshCLWren] + ## Version 3.1.1 (released 9 December 2025) - fix get_gainmap arguments [jcupitt] diff --git a/README.rst b/README.rst index 913eb0f..c89c9fc 100644 --- a/README.rst +++ b/README.rst @@ -228,6 +228,30 @@ Stylecheck: $ flake8 +Stylecheck: + +.. code-block:: shell + + $ flake8 + +Type checking: + +pyvips includes type hints via PEP 561 type stub files (``pyvips/__init__.pyi``). +To enable type checking in your project, install a type checker like mypy: + +.. code-block:: shell + + $ pip install mypy pyvips + +Then run mypy on your code: + +.. code-block:: shell + + $ mypy your_script.py + +Note: ``pyvips`` methods accept arbitrary keyword arguments for libvips options, +which may not be fully covered by type hints. + Generate HTML docs in ``doc/build/html``: .. code-block:: shell @@ -246,6 +270,16 @@ then Then check and move `enums.py` into `pyvips/`. +Regenerate type stubs: + +After adding new libvips operations or updating libvips itself, regenerate type stubs: + +.. code-block:: shell + + $ python examples/generate_type_stubs.py + +This updates ``pyvips/__init__.pyi`` with the latest operations. + Regenerate autodocs: Make sure you have installed a libvips with all optional packages enabled, diff --git a/doc/conf.py b/doc/conf.py index f9ced58..cdc7f13 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,6 +19,7 @@ import os import sys import sphinx_rtd_theme + sys.path.insert(0, os.path.abspath('..')) @@ -55,24 +56,24 @@ master_doc = 'index' # General information about the project. -project = u'pyvips' -copyright = u'2019, John Cupitt' -author = u'John Cupitt' +project = 'pyvips' +copyright = '2019, John Cupitt' +author = 'John Cupitt' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'3.1' +version = u'3.2' # The full version, including alpha/beta/rc tags. -release = u'3.1.1' +release = u'3.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. +# Usually you set 'language' from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and @@ -103,7 +104,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". +# so a file named 'default.css' will overwrite the builtin 'default.css'. html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names @@ -126,7 +127,7 @@ # each page. 'github_user': 'libvips', 'github_repo': 'pyvips', - 'github_version': 'master/doc/' + 'github_version': 'master/doc/', } # -- Options for HTMLHelp output ------------------------------------------ @@ -141,15 +142,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -159,8 +157,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'pyvips.tex', u'pyvips Documentation', - u'john', 'manual'), + (master_doc, 'pyvips.tex', 'pyvips Documentation', 'john', 'manual'), ] @@ -168,10 +165,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pyvips', u'pyvips Documentation', - [author], 1) -] +man_pages = [(master_doc, 'pyvips', 'pyvips Documentation', [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -180,19 +174,26 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'pyvips', u'pyvips Documentation', - author, 'pyvips', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + 'pyvips', + 'pyvips Documentation', + author, + 'pyvips', + 'One line description of project.', + 'Miscellaneous', + ), ] # see https://stackoverflow.com/questions/20569011 # adds autoautosummary directive, see vimage.rst + # try to exclude deprecated def skip_deprecated(app, what, name, obj, skip, options): if hasattr(obj, "func_dict") and "__deprecated__" in obj.func_dict: - print("skipping " + name) + print('skipping ' + name) return True return skip or False @@ -206,10 +207,9 @@ def setup(app): from sphinx.util.inspect import safe_getattr class AutoAutoSummary(Autosummary): - option_spec = { 'methods': directives.unchanged, - 'attributes': directives.unchanged + 'attributes': directives.unchanged, } required_arguments = 1 @@ -227,8 +227,10 @@ def get_members(obj, typ, include_public=None): continue if documenter.objtype == typ: items.append(name) - public = [x for x in items - if x in include_public or not x.startswith('_')] + public = [ + x for x in items + if x in include_public or not x.startswith('_') + ] return public, items def run(self): @@ -242,17 +244,21 @@ def run(self): _, methods = self.get_members(c, 'method', ['__init__']) - self.content = ["~%s.%s" % (clazz, method) - for method in methods - if not method.startswith('_')] + self.content = [ + '~%s.%s' % (clazz, method) + for method in methods + if not method.startswith('_') + ] if 'attributes' in self.options: _, attribs = self.get_members(c, 'attribute') - self.content = ["~%s.%s" % (clazz, attrib) - for attrib in attribs - if not attrib.startswith('_')] + self.content = [ + '~%s.%s' % (clazz, attrib) + for attrib in attribs + if not attrib.startswith('_') + ] finally: return super(AutoAutoSummary, self).run() - app.add_directive('autoautosummary', AutoAutoSummary) + app.add_directive("autoautosummary", AutoAutoSummary) except BaseException as e: raise e diff --git a/examples/generate_type_stubs.py b/examples/generate_type_stubs.py new file mode 100755 index 0000000..cac0d94 --- /dev/null +++ b/examples/generate_type_stubs.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 +""" +Generate pyvips type stubs. + +This script generates .pyi stub files for pyvips using introspection. +Run this to regenerate type stubs after adding new operations. + +Usage: + python examples/generate_type_stubs.py + +RATIONALE: +--------- +Type stubs are generated rather than handwritten because: +1. pyvips is a binding around libvips C library, which evolves independently +2. libvips has 300+ operations with complex signatures +3. New operations are added to libvips frequently +4. Generation uses same introspection mechanism as doc generation +5. Ensures stubs stay synchronized with available operations + +This approach follows the existing pattern used for: +- Enum generation (examples/gen-enums.py) +- Documentation generation (pyvips.Operation.generate_sphinx_all()) +""" + +import typing +from typing import Any, Optional, Union + +import pyvips +from pyvips import ( + GValue, + Introspect, + type_map, + type_from_name, + nickname_find, + at_least_libvips, +) +from pyvips import ffi, Error +from pyvips.voperation import _OPERATION_DEPRECATED + + +def gtype_to_python_type(gtype: int) -> str: + """Map a gtype to Python type annotation string.""" + fundamental = pyvips.gobject_lib.g_type_fundamental(gtype) + + if fundamental == GValue.genum_type: + name = pyvips.type_name(gtype) + if name.startswith("Vips"): + name = name[4:] + return f"Union[str, {name}]" + + type_mapping = { + GValue.gbool_type: "bool", + GValue.gint_type: "int", + GValue.guint64_type: "int", + GValue.gdouble_type: "float", + GValue.gstr_type: "str", + GValue.refstr_type: "str", + GValue.gflags_type: "int", + GValue.gobject_type: "GObject", + GValue.image_type: "Image", + GValue.array_int_type: "list[int]", + GValue.array_double_type: "list[float]", + GValue.array_image_type: "list[Image]", + GValue.blob_type: "str", + GValue.source_type: "Source", + GValue.target_type: "Target", + } + + if gtype in type_mapping: + return type_mapping[gtype] + if fundamental in type_mapping: + return type_mapping[fundamental] + return "Any" + + +PYTHON_KEYWORDS = { + "in", + "min", + "max", + "type", + "class", + "def", + "return", + "import", + "from", + "as", + "if", + "else", + "elif", + "for", + "while", + "break", + "continue", + "pass", + "raise", + "try", + "except", + "finally", + "with", + "lambda", + "and", + "or", + "not", + "is", + "None", + "True", + "False", +} + + +def escape_parameter_name(name: str) -> str: + """Escape Python keywords in parameter names.""" + if name in PYTHON_KEYWORDS: + return f"{name}_" + return name + + +def generate_method_signature(operation_name: str) -> str: + """Generate type signature for an operation method.""" + intro = Introspect.get(operation_name) + + if (intro.flags & _OPERATION_DEPRECATED) != 0: + return None + + args_list = [] + + if intro.member_x is not None: + # Instance method: self comes first + args_list.append("self") + else: + # Static/class method: no self + pass + + # Required args (excluding member_x for instance methods) + for name in intro.method_args: + py_type = gtype_to_python_type(intro.details[name]["type"]) + args_list.append(f"{escape_parameter_name(name)}: {py_type}") + + # Optional args (excluding deprecated) + for name in intro.doc_optional_input: + py_type = gtype_to_python_type(intro.details[name]["type"]) + args_list.append(f"{escape_parameter_name(name)}: {py_type} = ...") + + # Optional output args + for name in intro.doc_optional_output: + args_list.append(f"{escape_parameter_name(name)}: bool = ...") + + args_str = ", ".join(args_list) + # Return type + output_types = [ + gtype_to_python_type(intro.details[name]["type"]) + for name in intro.required_output + ] + + if len(output_types) == 0: + return_type = "None" + elif len(output_types) == 1: + return_type = output_types[0] + else: + return_type = f"tuple[{', '.join(output_types)}]" + + # Optional output dicts can contain any metadata value type + if len(intro.doc_optional_output) > 0: + dict_value_type = ( + "Union[bool, int, float, str, Image, list[int], list[float], list[Image]]" + ) + return_type = f"Union[{return_type}, tuple[{', '.join(output_types + [f'Dict[str, {dict_value_type}]'])}]]" + + if intro.member_x is not None: + return f" def {operation_name}({args_str}) -> {return_type}: ..." + else: + return f" @staticmethod\n def {operation_name}({args_str}) -> {return_type}: ..." + + +def generate_all_image_operations() -> str: + """Generate type stubs for all dynamically generated Image methods.""" + + # these names are aliased + alias = ["crop"] + alias_gtypes = {} + for name in alias: + gtype = pyvips.type_find("VipsOperation", name) + alias_gtypes[gtype] = name + + all_names = [] + + def add_name(gtype, a, b): + if gtype in alias_gtypes: + name = alias_gtypes[gtype] + else: + name = nickname_find(gtype) + + try: + sig = generate_method_signature(name) + if sig: + all_names.append((name, sig)) + except Error: + pass + + type_map(gtype, add_name) + return ffi.NULL + + type_map(type_from_name("VipsOperation"), add_name) + all_names.sort() + + # remove operations we have to wrap by hand + exclude = ["scale", "ifthenelse", "bandjoin", "bandrank", "composite", "copy"] + all_names = [(name, sig) for name, sig in all_names if name not in exclude] + + return "\n".join([sig for _, sig in all_names]) + + +def get_all_enum_names() -> list[str]: + """Get all enum type names from introspection.""" + + enum_names = set() + + def add_enums(gtype, a, b): + fundamental = pyvips.gobject_lib.g_type_fundamental(gtype) + if fundamental == GValue.genum_type: + name = pyvips.type_name(gtype) + if name.startswith("Vips"): + name = name[4:] + enum_names.add(name) + type_map(gtype, add_enums) + return ffi.NULL + + type_map(type_from_name("VipsOperation"), add_enums) + + # Also add base enums + enum_names.update( + [ + "BandFormat", + "Interpretation", + "Kernel", + "Coding", + "Extend", + "Align", + "Direction", + "Angle", + "Angle45", + "Access", + "Shrink", + "Intent", + "PCS", + "OperationBoolean", + "OperationComplex", + "OperationComplex2", + "OperationComplexget", + "OperationMath", + "OperationMath2", + "OperationMorphology", + "OperationRelational", + "OperationRound", + "Interesting", + "SdfShape", + "TextWrap", + "Combine", + "CombineMode", + "CompassDirection", + "Precision", + "FailOn", + "BlendMode", + "ForeignDzLayout", + "ForeignDzDepth", + "ForeignDzContainer", + "RegionShrink", + "ForeignHeifCompression", + "ForeignSubsample", + "ForeignHeifEncoder", + "ForeignPpmFormat", + "ForeignTiffCompression", + "ForeignTiffPredictor", + "ForeignTiffResunit", + "ForeignWebpPreset", + "Size", + ] + ) + + return sorted(enum_names) + + +def generate_enum_class(name: str) -> str: + """Generate type stub for a single enum class.""" + if name == "Direction": + return """class Direction: + HORIZONTAL: str + VERTICAL: str +""" + elif name == "Align": + return """class Align: + LOW: str + CENTRE: str + HIGH: str +""" + else: + return f"class {name}: ..." + + +def generate_stub() -> str: + """Generate complete pyvips type stub.""" + + # Get all enum names + enum_names = get_all_enum_names() + + enum_classes = "\n".join([generate_enum_class(name) for name in enum_names]) + + stub = f'''"""Type stubs for pyvips. + +# flake8: noqa: E501 + +This file is automatically generated by examples/generate_type_stubs.py. + +RATIONALE FOR GENERATION: +- pyvips is a binding around libvips C library +- libvips has 300+ operations that change frequently +- Manual stub maintenance would be unmaintainable +- Generation uses same introspection as docs/ enums +- Ensures stubs stay synchronized with available operations + +To regenerate after libvips updates: + python examples/generate_type_stubs.py +""" + +from __future__ import annotations +from typing import Dict, List, Optional, Tuple, TypeVar, Union, overload + +# Exception classes +class Error(Exception): ... + +# GObject base classes +class GObject: + def signal_connect(self, name: str, callback: object) -> None: ... + +class VipsObject(GObject): ... + +class GValue: + gbool_type: int + gint_type: int + guint64_type: int + gdouble_type: int + gstr_type: int + genum_type: int + gflags_type: int + gobject_type: int + image_type: int + array_int_type: int + array_double_type: int + array_image_type: int + refstr_type: int + blob_type: int + source_type: int + target_type: int + format_type: int + blend_mode_type: int + ... + +# Connection classes +class Source: + @staticmethod + def new_from_descriptor(descriptor: int) -> Source: ... +class SourceCustom(Source): + def on_read(self, handler: object) -> None: ... + def on_seek(self, handler: object) -> None: ... +class Target: + @staticmethod + def new_to_descriptor(descriptor: int) -> Target: ... + +# Interpolator class +class Interpolate: + @staticmethod + def new(name: str) -> Interpolate: ... + +# Enum classes +{enum_classes} + + +class Image(VipsObject): + """Wrap a VipsImage object.""" + + # Properties + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... + @property + def bands(self) -> int: ... + @property + def format(self) -> Union[str, BandFormat]: ... + @property + def interpretation(self) -> Union[str, Interpretation]: ... + @property + def xres(self) -> float: ... + @property + def yres(self) -> float: ... + @property + def xoffset(self) -> int: ... + @property + def yoffset(self) -> int: ... + + # Metadata methods + # GValue can return: bool, int, float, str, Image, list[int], list[float], list[Image] + def get_typeof(self, name: str) -> int: ... + def get(self, name: str) -> Union[bool, int, float, str, Image, list[int], list[float], list[Image]]: ... + def get_fields(self) -> List[str]: ... + def set_type(self, gtype: int, name: str, value: Union[bool, int, float, str, Image, list[int], list[float], list[Image]]) -> None: ... + def set(self, name: str, value: Union[bool, int, float, str, Image, list[int], list[float], list[Image]]) -> None: ... + def remove(self, name: str) -> bool: ... + + # Constructors + @staticmethod + def new_from_file(vips_filename: str, **kwargs: object) -> Image: ... + @staticmethod + def new_from_buffer(data: Union[bytes, bytearray, memoryview], options: str, **kwargs: object) -> Image: ... + @staticmethod + def new_from_list(array: List[List[float]], scale: float =1.0, offset: float = 0.0) -> Image: ... + @classmethod + def new_from_array(cls, obj: Union[List, bytes, bytearray, memoryview], scale: float = 1.0, offset: float = 0.0, interpretation: Optional[Union[str, Interpretation]] = None) -> Image: ... + @staticmethod + def new_from_memory(data: Union[bytes, bytearray, memoryview], width: int, height: int, bands: int, format: Union[str, BandFormat]) -> Image: ... + @staticmethod + def new_from_source(source: Source, options: str, **kwargs: object) -> Image: ... + @staticmethod + def new_temp_file(format: str) -> Image: ... + def new_from_image(self, value: Union[float, int, List[float], List[int]]) -> Image: ... + def copy_memory(self) -> Image: ... + + # Writers + def write_to_file(self, vips_filename: str, **kwargs: object) -> None: ... + def write_to_buffer(self, format_string: str, **kwargs: object) -> bytes: ... + def write_to_target(self, target: Target, format_string: str, **kwargs: object) -> None: ... + def write_to_memory(self) -> bytes: ... + def write(self, other: Image) -> None: ... + + # Utility methods + def invalidate(self) -> None: ... + def set_progress(self, progress: bool) -> None: ... + def set_kill(self, kill: bool) -> None: ... + def copy(self, **kwargs: object) -> Image: ... + def tolist(self) -> List[List[float]]: ... + # numpy is optional dependency - use TYPE_CHECKING guard + def __array__(self, dtype: Optional[str] = None, copy: Optional[bool] = None) -> object: ... + def numpy(self, dtype: Optional[str] = None) -> object: ... + + # Hand-written bindings with type hints + def floor(self) -> Image: ... + def ceil(self) -> Image: ... + def rint(self) -> Image: ... + def bandsplit(self) -> List[Image]: ... + def bandjoin(self, other: Union[Image, float, int, List[Union[Image, float, int]]]) -> Image: ... + def bandrank(self, other: Union[Image, List[Image]], **kwargs: object) -> Image: ... + def composite(self, other: Union[Image, List[Image]], mode: Union[str, BlendMode, List[Union[str, BlendMode]]], **kwargs: object) -> Image: ... + def ifthenelse(self, in1: Union[Image, float, int], in2: Union[Image, float, int], **kwargs: object) -> Image: ... + def hasalpha(self) -> bool: ... + def get_n_pages(self) -> int: ... + def get_page_height(self) -> int: ... + def pagesplit(self) -> List[Image]: ... + def pagejoin(self, other: Union[Image, List[Image]]) -> Image: ... + def scaleimage(self, **kwargs: object) -> Image: ... + def erode(self, mask: Union[Image, List[List[int]]]) -> Image: ... + def dilate(self, mask: Union[Image, List[List[int]]]) -> Image: ... + + # Dynamically generated operations +''' + + stub += generate_all_image_operations() + + stub += """ + + # Operator overloads + def __add__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __radd__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __sub__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rsub__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __mul__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rmul__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __truediv__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rtruediv__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __floordiv__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rfloordiv__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __mod__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __pow__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rpow__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __abs__(self) -> Image: ... + def __neg__(self) -> Image: ... + def __pos__(self) -> Image: ... + def __invert__(self) -> Image: ... + def __lshift__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rshift__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __and__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rand__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __or__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __ror__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __xor__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rxor__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __gt__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __ge__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __lt__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __le__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + + def __getitem__(self, arg: Union[int, slice, List[int], List[bool]]) -> Image: ... + def __call__(self, x: int, y: int) -> List[float]: ... + def __repr__(self) -> str: ... + + +class Operation: ... +class Introspect: ... + +# Global functions +def cache_set_max(mx: int) -> None: ... +def cache_set_max_mem(mx: int) -> None: ... +def cache_set_max_files(mx: int) -> None: ... +def cache_set_trace(trace: bool) -> None: ... +def cache_get_max() -> int: ... +def cache_get_size() -> int: ... +def cache_get_max_mem() -> int: ... +def cache_get_max_files() -> int: ... +def block_untrusted_set(state: bool) -> None: ... +def operation_block_set(name: str, state: bool) -> None: ... +def leak_set(leak: bool) -> None: ... +def shutdown() -> None: ... +def call(operation_name: str, *args: object, **kwargs: object) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + +# Module-level constants +API_mode: bool +""" + + return stub + + +if __name__ == "__main__": + # Generate stub + stub_content = generate_stub() + + # Write to pyvips/__init__.pyi + import os + + # Script is in examples/, need to go up one level to find pyvips/ + stub_file = os.path.join(os.path.dirname(__file__), "..", "pyvips", "__init__.pyi") + with open(stub_file, "w") as f: + f.write(stub_content) + + print(f"Generated type stub at {stub_file}") diff --git a/examples/read_profile.py b/examples/read_profile.py index c31a2fb..d1fb4b3 100755 --- a/examples/read_profile.py +++ b/examples/read_profile.py @@ -11,5 +11,5 @@ profile = a.get("icc-profile-data") -with open('x.icm', 'w') as f: - f.write(profile) +with open("x.icm", "w") as f: + f.write(profile) # type: ignore[arg-type] diff --git a/examples/try10.py b/examples/try10.py index 98c5f51..d61ff50 100755 --- a/examples/try10.py +++ b/examples/try10.py @@ -6,6 +6,6 @@ import pyvips a = pyvips.Image.black(100, 100) -a = a.draw_circle(128, 50, 50, 20) +a = a.draw_circle(128, 50, 50, 20) # type: ignore[arg-type] b = a.hough_circle(scale=1, min_radius=15, max_radius=25) b.write_to_file("x.v") diff --git a/examples/try11.py b/examples/try11.py index b88bddb..5ef1308 100755 --- a/examples/try11.py +++ b/examples/try11.py @@ -8,6 +8,6 @@ a = pyvips.Image.new_from_file(sys.argv[1]) ipct = a.get("ipct-data") -print("ipct = ", ipct.get()) +print("ipct = ", ipct.get()) # type: ignore[union-attr, call-arg] a.remove("ipct-data") a.write_to_file("x.jpg") diff --git a/examples/try5.py b/examples/try5.py index ebc307f..5acb960 100755 --- a/examples/try5.py +++ b/examples/try5.py @@ -11,7 +11,7 @@ def should_equal(test, a, b): if abs(a - b) > 0.01: - print(f'{test}: seen {a:g} and {b:g}') + print(f"{test}: seen {a:g} and {b:g}") sys.exit(1) @@ -23,15 +23,13 @@ def bandsplit(a): # addition b = a + 12 -should_equal('add constant', a.avg() + 12, b.avg()) +should_equal("add constant", a.avg() + 12, b.avg()) b = a + [12, 0, 0] -x = map(lambda x: x.avg()) -bandsplit(a) -y = map(lambda x: x.avg()) -bandsplit(b) +x = list(map(lambda img: img.avg(), bandsplit(a))) +y = list(map(lambda img: img.avg(), bandsplit(b))) x[0] += 12 -should_equal('add multiband constant', sum(x), sum(y)) +should_equal("add multiband constant", sum(x), sum(y)) b = a + [12, 0, 0] b = a + b @@ -48,7 +46,7 @@ def bandsplit(a): b = a * [12, 1, 1] b = a * b b = 12 * a -b = [12, 1, 1] * a +b = [12, 1, 1] * a # type: ignore b = a / 12 b = a / [12, 1, 1] @@ -66,11 +64,11 @@ def bandsplit(a): b = a % [12, 1, 1] b = a % b -b = a ** 12 +b = a**12 b = a ** [12, 1, 1] -b = 12 ** a +b = 12**a b = [12, 1, 1] ** a -b = a ** b +b = a**b b = a << 12 b = a << [12, 1, 1] diff --git a/examples/try7.py b/examples/try7.py index cb936eb..a44d7ca 100755 --- a/examples/try7.py +++ b/examples/try7.py @@ -10,6 +10,6 @@ b = a.write_to_memory() -c = pyvips.Image.new_from_memory(b, a.width, a.height, a.bands, a.bandfmt) +c = pyvips.Image.new_from_memory(b, a.width, a.height, a.bands, a.format) c.write_to_file("x.v") diff --git a/examples/watermark.py b/examples/watermark.py index 7babd40..2a00f4c 100755 --- a/examples/watermark.py +++ b/examples/watermark.py @@ -5,23 +5,27 @@ im = pyvips.Image.new_from_file(sys.argv[1], access="sequential") -text = pyvips.Image.text(f"{sys.argv[3]}", - width=500, - dpi=100, - align="centre", - rgba=True) +text = pyvips.Image.text( + f'{sys.argv[3]}', + width=500, + dpi=100, + align="centre", + rgba=True, +) # scale the alpha down to make the text semi-transparent -text = (text * [1, 1, 1, 0.3]).cast("uchar") +text = (text * [1, 1, 1, 0.3]).cast("uchar") # type: ignore text = text.rotate(45) # tile to the size of the image page, then tile again to the full image size text = text.embed(10, 10, text.width + 20, text.width + 20) page_height = im.get_page_height() -text = text.replicate(1 + im.width / text.width, 1 + page_height / text.height) +text = text.replicate( + int(1 + im.width / text.width), int(1 + page_height / text.height) +) text = text.crop(0, 0, im.width, page_height) -text = text.replicate(1, 1 + im.height / text.height) +text = text.replicate(1, int(1 + im.height / text.height)) text = text.crop(0, 0, im.width, im.height) # composite the two layers diff --git a/pyvips/__init__.pyi b/pyvips/__init__.pyi new file mode 100644 index 0000000..bc30b92 --- /dev/null +++ b/pyvips/__init__.pyi @@ -0,0 +1,657 @@ +"""Type stubs for pyvips. + +# flake8: noqa: E501 + +This file is automatically generated by examples/generate_type_stubs.py. + +RATIONALE FOR GENERATION: +- pyvips is a binding around libvips C library +- libvips has 300+ operations that change frequently +- Manual stub maintenance would be unmaintainable +- Generation uses same introspection as docs/ enums +- Ensures stubs stay synchronized with available operations + +To regenerate after libvips updates: + python examples/generate_type_stubs.py +""" + +from __future__ import annotations +from typing import Dict, List, Optional, Tuple, TypeVar, Union, overload + +# Exception classes +class Error(Exception): ... + +# GObject base classes +class GObject: + def signal_connect(self, name: str, callback: object) -> None: ... + +class VipsObject(GObject): ... + +class GValue: + gbool_type: int + gint_type: int + guint64_type: int + gdouble_type: int + gstr_type: int + genum_type: int + gflags_type: int + gobject_type: int + image_type: int + array_int_type: int + array_double_type: int + array_image_type: int + refstr_type: int + blob_type: int + source_type: int + target_type: int + format_type: int + blend_mode_type: int + ... + +# Connection classes +class Source: + @staticmethod + def new_from_descriptor(descriptor: int) -> Source: ... +class SourceCustom(Source): + def on_read(self, handler: object) -> None: ... + def on_seek(self, handler: object) -> None: ... +class Target: + @staticmethod + def new_to_descriptor(descriptor: int) -> Target: ... + +# Interpolator class +class Interpolate: + @staticmethod + def new(name: str) -> Interpolate: ... + +# Enum classes +class Access: ... +class Align: + LOW: str + CENTRE: str + HIGH: str + +class Angle: ... +class Angle45: ... +class BandFormat: ... +class BlendMode: ... +class Coding: ... +class Combine: ... +class CombineMode: ... +class CompassDirection: ... +class Direction: + HORIZONTAL: str + VERTICAL: str + +class Extend: ... +class FailOn: ... +class ForeignDzContainer: ... +class ForeignDzDepth: ... +class ForeignDzLayout: ... +class ForeignHeifCompression: ... +class ForeignHeifEncoder: ... +class ForeignPpmFormat: ... +class ForeignSubsample: ... +class ForeignTiffCompression: ... +class ForeignTiffPredictor: ... +class ForeignTiffResunit: ... +class ForeignWebpPreset: ... +class Intent: ... +class Interesting: ... +class Interpretation: ... +class Kernel: ... +class OperationBoolean: ... +class OperationComplex: ... +class OperationComplex2: ... +class OperationComplexget: ... +class OperationMath: ... +class OperationMath2: ... +class OperationMorphology: ... +class OperationRelational: ... +class OperationRound: ... +class PCS: ... +class Precision: ... +class RegionShrink: ... +class SdfShape: ... +class Shrink: ... +class Size: ... +class TextWrap: ... + + +class Image(VipsObject): + """Wrap a VipsImage object.""" + + # Properties + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... + @property + def bands(self) -> int: ... + @property + def format(self) -> Union[str, BandFormat]: ... + @property + def interpretation(self) -> Union[str, Interpretation]: ... + @property + def xres(self) -> float: ... + @property + def yres(self) -> float: ... + @property + def xoffset(self) -> int: ... + @property + def yoffset(self) -> int: ... + + # Metadata methods + # GValue can return: bool, int, float, str, Image, list[int], list[float], list[Image] + def get_typeof(self, name: str) -> int: ... + def get(self, name: str) -> Union[bool, int, float, str, Image, list[int], list[float], list[Image]]: ... + def get_fields(self) -> List[str]: ... + def set_type(self, gtype: int, name: str, value: Union[bool, int, float, str, Image, list[int], list[float], list[Image]]) -> None: ... + def set(self, name: str, value: Union[bool, int, float, str, Image, list[int], list[float], list[Image]]) -> None: ... + def remove(self, name: str) -> bool: ... + + # Constructors + @staticmethod + def new_from_file(vips_filename: str, **kwargs: object) -> Image: ... + @staticmethod + def new_from_buffer(data: Union[bytes, bytearray, memoryview], options: str, **kwargs: object) -> Image: ... + @staticmethod + def new_from_list(array: List[List[float]], scale: float =1.0, offset: float = 0.0) -> Image: ... + @classmethod + def new_from_array(cls, obj: Union[List, bytes, bytearray, memoryview], scale: float = 1.0, offset: float = 0.0, interpretation: Optional[Union[str, Interpretation]] = None) -> Image: ... + @staticmethod + def new_from_memory(data: Union[bytes, bytearray, memoryview], width: int, height: int, bands: int, format: Union[str, BandFormat]) -> Image: ... + @staticmethod + def new_from_source(source: Source, options: str, **kwargs: object) -> Image: ... + @staticmethod + def new_temp_file(format: str) -> Image: ... + def new_from_image(self, value: Union[float, int, List[float], List[int]]) -> Image: ... + def copy_memory(self) -> Image: ... + + # Writers + def write_to_file(self, vips_filename: str, **kwargs: object) -> None: ... + def write_to_buffer(self, format_string: str, **kwargs: object) -> bytes: ... + def write_to_target(self, target: Target, format_string: str, **kwargs: object) -> None: ... + def write_to_memory(self) -> bytes: ... + def write(self, other: Image) -> None: ... + + # Utility methods + def invalidate(self) -> None: ... + def set_progress(self, progress: bool) -> None: ... + def set_kill(self, kill: bool) -> None: ... + def copy(self, **kwargs: object) -> Image: ... + def tolist(self) -> List[List[float]]: ... + # numpy is optional dependency - use TYPE_CHECKING guard + def __array__(self, dtype: Optional[str] = None, copy: Optional[bool] = None) -> object: ... + def numpy(self, dtype: Optional[str] = None) -> object: ... + + # Hand-written bindings with type hints + def floor(self) -> Image: ... + def ceil(self) -> Image: ... + def rint(self) -> Image: ... + def bandsplit(self) -> List[Image]: ... + def bandjoin(self, other: Union[Image, float, int, List[Union[Image, float, int]]]) -> Image: ... + def bandrank(self, other: Union[Image, List[Image]], **kwargs: object) -> Image: ... + def composite(self, other: Union[Image, List[Image]], mode: Union[str, BlendMode, List[Union[str, BlendMode]]], **kwargs: object) -> Image: ... + def ifthenelse(self, in1: Union[Image, float, int], in2: Union[Image, float, int], **kwargs: object) -> Image: ... + def hasalpha(self) -> bool: ... + def get_n_pages(self) -> int: ... + def get_page_height(self) -> int: ... + def pagesplit(self) -> List[Image]: ... + def pagejoin(self, other: Union[Image, List[Image]]) -> Image: ... + def scaleimage(self, **kwargs: object) -> Image: ... + def erode(self, mask: Union[Image, List[List[int]]]) -> Image: ... + def dilate(self, mask: Union[Image, List[List[int]]]) -> Image: ... + + # Dynamically generated operations + def CMC2LCh(self) -> Image: ... + def CMYK2XYZ(self) -> Image: ... + def HSV2sRGB(self) -> Image: ... + def LCh2CMC(self) -> Image: ... + def LCh2Lab(self) -> Image: ... + def Lab2LCh(self) -> Image: ... + def Lab2LabQ(self) -> Image: ... + def Lab2LabS(self) -> Image: ... + def Lab2XYZ(self, temp: list[float] = ...) -> Image: ... + def LabQ2Lab(self) -> Image: ... + def LabQ2LabS(self) -> Image: ... + def LabQ2sRGB(self) -> Image: ... + def LabS2Lab(self) -> Image: ... + def LabS2LabQ(self) -> Image: ... + def XYZ2CMYK(self) -> Image: ... + def XYZ2Lab(self, temp: list[float] = ...) -> Image: ... + def XYZ2Yxy(self) -> Image: ... + def XYZ2scRGB(self) -> Image: ... + def Yxy2XYZ(self) -> Image: ... + def abs(self) -> Image: ... + def add(self, right: Image) -> Image: ... + def addalpha(self) -> Image: ... + def affine(self, matrix: list[float], interpolate: GObject = ..., oarea: list[int] = ..., odx: float = ..., ody: float = ..., idx: float = ..., idy: float = ..., background: list[float] = ..., premultiplied: bool = ..., extend: Union[str, Extend] = ...) -> Image: ... + @staticmethod + def analyzeload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def arrayjoin(in_: list[Image], across: int = ..., shim: int = ..., background: list[float] = ..., halign: Union[str, Align] = ..., valign: Union[str, Align] = ..., hspacing: int = ..., vspacing: int = ...) -> Image: ... + def autorot(self, angle: bool = ..., flip: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def avg(self) -> float: ... + def bandbool(self, boolean: Union[str, OperationBoolean]) -> Image: ... + def bandfold(self, factor: int = ...) -> Image: ... + def bandjoin_const(self, c: list[float]) -> Image: ... + def bandmean(self) -> Image: ... + def bandunfold(self, factor: int = ...) -> Image: ... + @staticmethod + def black(width: int, height: int, bands: int = ...) -> Image: ... + def boolean(self, right: Image, boolean: Union[str, OperationBoolean]) -> Image: ... + def boolean_const(self, boolean: Union[str, OperationBoolean], c: list[float]) -> Image: ... + def buildlut(self) -> Image: ... + def byteswap(self) -> Image: ... + def canny(self, sigma: float = ..., precision: Union[str, Precision] = ...) -> Image: ... + def case(self, cases: list[Image]) -> Image: ... + def cast(self, format: Union[str, BandFormat], shift: bool = ...) -> Image: ... + def clamp(self, min_: float = ..., max_: float = ...) -> Image: ... + def colourspace(self, space: Union[str, Interpretation], source_space: Union[str, Interpretation] = ...) -> Image: ... + def compass(self, mask: Image, times: int = ..., angle: Union[str, Angle45] = ..., combine: Union[str, Combine] = ..., precision: Union[str, Precision] = ..., layers: int = ..., cluster: int = ...) -> Image: ... + def complex(self, cmplx: Union[str, OperationComplex]) -> Image: ... + def complex2(self, right: Image, cmplx: Union[str, OperationComplex2]) -> Image: ... + def complexform(self, right: Image) -> Image: ... + def complexget(self, get: Union[str, OperationComplexget]) -> Image: ... + def composite2(self, overlay: Image, mode: Union[str, BlendMode], x: int = ..., y: int = ..., compositing_space: Union[str, Interpretation] = ..., premultiplied: bool = ...) -> Image: ... + def conv(self, mask: Image, precision: Union[str, Precision] = ..., layers: int = ..., cluster: int = ...) -> Image: ... + def conva(self, mask: Image, layers: int = ..., cluster: int = ...) -> Image: ... + def convasep(self, mask: Image, layers: int = ...) -> Image: ... + def convf(self, mask: Image) -> Image: ... + def convi(self, mask: Image) -> Image: ... + def convsep(self, mask: Image, precision: Union[str, Precision] = ..., layers: int = ..., cluster: int = ...) -> Image: ... + def countlines(self, direction: Union[str, Direction]) -> float: ... + def crop(self, left: int, top: int, width: int, height: int) -> Image: ... + @staticmethod + def csvload(filename: str, skip: int = ..., lines: int = ..., whitespace: str = ..., separator: str = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def csvload_source(source: Source, skip: int = ..., lines: int = ..., whitespace: str = ..., separator: str = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def csvsave(self, filename: str, separator: str = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def csvsave_target(self, target: Target, separator: str = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def dE00(self, right: Image) -> Image: ... + def dE76(self, right: Image) -> Image: ... + def dECMC(self, right: Image) -> Image: ... + def deviate(self) -> float: ... + def divide(self, right: Image) -> Image: ... + def draw_circle(self, ink: list[float], cx: int, cy: int, radius: int, fill: bool = ...) -> Image: ... + def draw_flood(self, ink: list[float], x: int, y: int, test: Image = ..., equal: bool = ..., left: bool = ..., top: bool = ..., width: bool = ..., height: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def draw_image(self, sub: Image, x: int, y: int, mode: Union[str, CombineMode] = ...) -> Image: ... + def draw_line(self, ink: list[float], x1: int, y1: int, x2: int, y2: int) -> Image: ... + def draw_mask(self, ink: list[float], mask: Image, x: int, y: int) -> Image: ... + def draw_rect(self, ink: list[float], left: int, top: int, width: int, height: int, fill: bool = ...) -> Image: ... + def draw_smudge(self, left: int, top: int, width: int, height: int) -> Image: ... + def dzsave(self, filename: str, imagename: str = ..., layout: Union[str, ForeignDzLayout] = ..., suffix: str = ..., overlap: int = ..., tile_size: int = ..., centre: bool = ..., depth: Union[str, ForeignDzDepth] = ..., angle: Union[str, Angle] = ..., container: Union[str, ForeignDzContainer] = ..., compression: int = ..., region_shrink: Union[str, RegionShrink] = ..., skip_blanks: int = ..., id: str = ..., Q: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def dzsave_buffer(self, imagename: str = ..., layout: Union[str, ForeignDzLayout] = ..., suffix: str = ..., overlap: int = ..., tile_size: int = ..., centre: bool = ..., depth: Union[str, ForeignDzDepth] = ..., angle: Union[str, Angle] = ..., container: Union[str, ForeignDzContainer] = ..., compression: int = ..., region_shrink: Union[str, RegionShrink] = ..., skip_blanks: int = ..., id: str = ..., Q: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def dzsave_target(self, target: Target, imagename: str = ..., layout: Union[str, ForeignDzLayout] = ..., suffix: str = ..., overlap: int = ..., tile_size: int = ..., centre: bool = ..., depth: Union[str, ForeignDzDepth] = ..., angle: Union[str, Angle] = ..., container: Union[str, ForeignDzContainer] = ..., compression: int = ..., region_shrink: Union[str, RegionShrink] = ..., skip_blanks: int = ..., id: str = ..., Q: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def embed(self, x: int, y: int, width: int, height: int, extend: Union[str, Extend] = ..., background: list[float] = ...) -> Image: ... + def extract_area(self, left: int, top: int, width: int, height: int) -> Image: ... + def extract_band(self, band: int, n: int = ...) -> Image: ... + @staticmethod + def eye(width: int, height: int, uchar: bool = ..., factor: float = ...) -> Image: ... + def falsecolour(self) -> Image: ... + def fastcor(self, ref: Image) -> Image: ... + def fill_nearest(self, distance: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def find_trim(self, threshold: float = ..., background: list[float] = ..., line_art: bool = ...) -> tuple[int, int, int, int]: ... + @staticmethod + def fitsload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def fitsload_source(source: Source, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def fitssave(self, filename: str, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def flatten(self, background: list[float] = ..., max_alpha: float = ...) -> Image: ... + def flip(self, direction: Union[str, Direction]) -> Image: ... + def float2rad(self) -> Image: ... + @staticmethod + def fractsurf(width: int, height: int, fractal_dimension: float) -> Image: ... + def freqmult(self, mask: Image) -> Image: ... + def fwfft(self) -> Image: ... + def gamma(self, exponent: float = ...) -> Image: ... + def gaussblur(self, sigma: float, min_ampl: float = ..., precision: Union[str, Precision] = ...) -> Image: ... + @staticmethod + def gaussmat(sigma: float, min_ampl: float, separable: bool = ..., precision: Union[str, Precision] = ...) -> Image: ... + @staticmethod + def gaussnoise(width: int, height: int, sigma: float = ..., mean: float = ..., seed: int = ...) -> Image: ... + def getpoint(self, x: int, y: int, unpack_complex: bool = ...) -> list[float]: ... + @staticmethod + def gifload(filename: str, n: int = ..., page: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def gifload_buffer(buffer: str, n: int = ..., page: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def gifload_source(source: Source, n: int = ..., page: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def gifsave(self, filename: str, dither: float = ..., effort: int = ..., bitdepth: int = ..., interframe_maxerror: float = ..., reuse: bool = ..., interpalette_maxerror: float = ..., interlace: bool = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def gifsave_buffer(self, dither: float = ..., effort: int = ..., bitdepth: int = ..., interframe_maxerror: float = ..., reuse: bool = ..., interpalette_maxerror: float = ..., interlace: bool = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def gifsave_target(self, target: Target, dither: float = ..., effort: int = ..., bitdepth: int = ..., interframe_maxerror: float = ..., reuse: bool = ..., interpalette_maxerror: float = ..., interlace: bool = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def globalbalance(self, gamma: float = ..., int_output: bool = ...) -> Image: ... + def gravity(self, direction: Union[str, CompassDirection], width: int, height: int, extend: Union[str, Extend] = ..., background: list[float] = ...) -> Image: ... + @staticmethod + def grey(width: int, height: int, uchar: bool = ...) -> Image: ... + def grid(self, tile_height: int, across: int, down: int) -> Image: ... + @staticmethod + def heifload(filename: str, page: int = ..., n: int = ..., thumbnail: bool = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def heifload_buffer(buffer: str, page: int = ..., n: int = ..., thumbnail: bool = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def heifload_source(source: Source, page: int = ..., n: int = ..., thumbnail: bool = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def heifsave(self, filename: str, Q: int = ..., bitdepth: int = ..., lossless: bool = ..., compression: Union[str, ForeignHeifCompression] = ..., effort: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., encoder: Union[str, ForeignHeifEncoder] = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def heifsave_buffer(self, Q: int = ..., bitdepth: int = ..., lossless: bool = ..., compression: Union[str, ForeignHeifCompression] = ..., effort: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., encoder: Union[str, ForeignHeifEncoder] = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def heifsave_target(self, target: Target, Q: int = ..., bitdepth: int = ..., lossless: bool = ..., compression: Union[str, ForeignHeifCompression] = ..., effort: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., encoder: Union[str, ForeignHeifEncoder] = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def hist_cum(self) -> Image: ... + def hist_entropy(self) -> float: ... + def hist_equal(self, band: int = ...) -> Image: ... + def hist_find(self, band: int = ...) -> Image: ... + def hist_find_indexed(self, index: Image, combine: Union[str, Combine] = ...) -> Image: ... + def hist_find_ndim(self, bins: int = ...) -> Image: ... + def hist_ismonotonic(self) -> bool: ... + def hist_local(self, width: int, height: int, max_slope: int = ...) -> Image: ... + def hist_match(self, ref: Image) -> Image: ... + def hist_norm(self) -> Image: ... + def hist_plot(self) -> Image: ... + def hough_circle(self, scale: int = ..., min_radius: int = ..., max_radius: int = ...) -> Image: ... + def hough_line(self, width: int = ..., height: int = ...) -> Image: ... + def icc_export(self, pcs: Union[str, PCS] = ..., intent: Union[str, Intent] = ..., black_point_compensation: bool = ..., output_profile: str = ..., depth: int = ...) -> Image: ... + def icc_import(self, pcs: Union[str, PCS] = ..., intent: Union[str, Intent] = ..., black_point_compensation: bool = ..., embedded: bool = ..., input_profile: str = ...) -> Image: ... + def icc_transform(self, output_profile: str, pcs: Union[str, PCS] = ..., intent: Union[str, Intent] = ..., black_point_compensation: bool = ..., embedded: bool = ..., input_profile: str = ..., depth: int = ...) -> Image: ... + @staticmethod + def identity(bands: int = ..., ushort: bool = ..., size: int = ...) -> Image: ... + def insert(self, sub: Image, x: int, y: int, expand: bool = ..., background: list[float] = ...) -> Image: ... + def invert(self) -> Image: ... + def invertlut(self, size: int = ...) -> Image: ... + def invfft(self, real: bool = ...) -> Image: ... + def join(self, in2: Image, direction: Union[str, Direction], expand: bool = ..., shim: int = ..., background: list[float] = ..., align: Union[str, Align] = ...) -> Image: ... + @staticmethod + def jp2kload(filename: str, page: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def jp2kload_buffer(buffer: str, page: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def jp2kload_source(source: Source, page: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def jp2ksave(self, filename: str, tile_width: int = ..., tile_height: int = ..., lossless: bool = ..., Q: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def jp2ksave_buffer(self, tile_width: int = ..., tile_height: int = ..., lossless: bool = ..., Q: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def jp2ksave_target(self, target: Target, tile_width: int = ..., tile_height: int = ..., lossless: bool = ..., Q: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + @staticmethod + def jpegload(filename: str, shrink: int = ..., autorotate: bool = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def jpegload_buffer(buffer: str, shrink: int = ..., autorotate: bool = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def jpegload_source(source: Source, shrink: int = ..., autorotate: bool = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def jpegsave(self, filename: str, Q: int = ..., optimize_coding: bool = ..., interlace: bool = ..., trellis_quant: bool = ..., overshoot_deringing: bool = ..., optimize_scans: bool = ..., quant_table: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., restart_interval: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def jpegsave_buffer(self, Q: int = ..., optimize_coding: bool = ..., interlace: bool = ..., trellis_quant: bool = ..., overshoot_deringing: bool = ..., optimize_scans: bool = ..., quant_table: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., restart_interval: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def jpegsave_mime(self, Q: int = ..., optimize_coding: bool = ..., interlace: bool = ..., trellis_quant: bool = ..., overshoot_deringing: bool = ..., optimize_scans: bool = ..., quant_table: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., restart_interval: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def jpegsave_target(self, target: Target, Q: int = ..., optimize_coding: bool = ..., interlace: bool = ..., trellis_quant: bool = ..., overshoot_deringing: bool = ..., optimize_scans: bool = ..., quant_table: int = ..., subsample_mode: Union[str, ForeignSubsample] = ..., restart_interval: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + @staticmethod + def jxlload(filename: str, page: int = ..., n: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def jxlload_buffer(buffer: str, page: int = ..., n: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def jxlload_source(source: Source, page: int = ..., n: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def jxlsave(self, filename: str, tier: int = ..., distance: float = ..., effort: int = ..., lossless: bool = ..., Q: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def jxlsave_buffer(self, tier: int = ..., distance: float = ..., effort: int = ..., lossless: bool = ..., Q: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def jxlsave_target(self, target: Target, tier: int = ..., distance: float = ..., effort: int = ..., lossless: bool = ..., Q: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def labelregions(self, segments: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def linear(self, a: list[float], b: list[float], uchar: bool = ...) -> Image: ... + def linecache(self, tile_height: int = ..., access: Union[str, Access] = ..., threaded: bool = ..., persistent: bool = ...) -> Image: ... + @staticmethod + def logmat(sigma: float, min_ampl: float, separable: bool = ..., precision: Union[str, Precision] = ...) -> Image: ... + @staticmethod + def magickload(filename: str, density: str = ..., page: int = ..., n: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def magickload_buffer(buffer: str, density: str = ..., page: int = ..., n: int = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def magicksave(self, filename: str, format: str = ..., quality: int = ..., optimize_gif_frames: bool = ..., optimize_gif_transparency: bool = ..., bitdepth: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def magicksave_buffer(self, format: str = ..., quality: int = ..., optimize_gif_frames: bool = ..., optimize_gif_transparency: bool = ..., bitdepth: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def mapim(self, index: Image, interpolate: GObject = ..., background: list[float] = ..., premultiplied: bool = ..., extend: Union[str, Extend] = ...) -> Image: ... + def maplut(self, lut: Image, band: int = ...) -> Image: ... + @staticmethod + def mask_butterworth(width: int, height: int, order: float, frequency_cutoff: float, amplitude_cutoff: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_butterworth_band(width: int, height: int, order: float, frequency_cutoff_x: float, frequency_cutoff_y: float, radius: float, amplitude_cutoff: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_butterworth_ring(width: int, height: int, order: float, frequency_cutoff: float, amplitude_cutoff: float, ringwidth: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_fractal(width: int, height: int, fractal_dimension: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_gaussian(width: int, height: int, frequency_cutoff: float, amplitude_cutoff: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_gaussian_band(width: int, height: int, frequency_cutoff_x: float, frequency_cutoff_y: float, radius: float, amplitude_cutoff: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_gaussian_ring(width: int, height: int, frequency_cutoff: float, amplitude_cutoff: float, ringwidth: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_ideal(width: int, height: int, frequency_cutoff: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_ideal_band(width: int, height: int, frequency_cutoff_x: float, frequency_cutoff_y: float, radius: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + @staticmethod + def mask_ideal_ring(width: int, height: int, frequency_cutoff: float, ringwidth: float, uchar: bool = ..., nodc: bool = ..., reject: bool = ..., optical: bool = ...) -> Image: ... + def match(self, sec: Image, xr1: int, yr1: int, xs1: int, ys1: int, xr2: int, yr2: int, xs2: int, ys2: int, hwindow: int = ..., harea: int = ..., search: bool = ..., interpolate: GObject = ...) -> Image: ... + def math(self, math: Union[str, OperationMath]) -> Image: ... + def math2(self, right: Image, math2: Union[str, OperationMath2]) -> Image: ... + def math2_const(self, math2: Union[str, OperationMath2], c: list[float]) -> Image: ... + @staticmethod + def matload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def matrixinvert(self) -> Image: ... + @staticmethod + def matrixload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def matrixload_source(source: Source, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def matrixprint(self, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def matrixsave(self, filename: str, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def matrixsave_target(self, target: Target, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def max(self, size: int = ..., x: bool = ..., y: bool = ..., out_array: bool = ..., x_array: bool = ..., y_array: bool = ...) -> Union[float, tuple[float, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def maxpair(self, right: Image) -> Image: ... + def measure(self, h: int, v: int, left: int = ..., top: int = ..., width: int = ..., height: int = ...) -> Image: ... + def merge(self, sec: Image, direction: Union[str, Direction], dx: int, dy: int, mblend: int = ...) -> Image: ... + def min(self, size: int = ..., x: bool = ..., y: bool = ..., out_array: bool = ..., x_array: bool = ..., y_array: bool = ...) -> Union[float, tuple[float, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def minpair(self, right: Image) -> Image: ... + def morph(self, mask: Image, morph: Union[str, OperationMorphology]) -> Image: ... + def mosaic(self, sec: Image, direction: Union[str, Direction], xref: int, yref: int, xsec: int, ysec: int, hwindow: int = ..., harea: int = ..., mblend: int = ..., bandno: int = ..., dx0: bool = ..., dy0: bool = ..., scale1: bool = ..., angle1: bool = ..., dy1: bool = ..., dx1: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def mosaic1(self, sec: Image, direction: Union[str, Direction], xr1: int, yr1: int, xs1: int, ys1: int, xr2: int, yr2: int, xs2: int, ys2: int, hwindow: int = ..., harea: int = ..., search: bool = ..., interpolate: GObject = ..., mblend: int = ...) -> Image: ... + def msb(self, band: int = ...) -> Image: ... + def multiply(self, right: Image) -> Image: ... + @staticmethod + def openexrload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def openslideload(filename: str, level: int = ..., autocrop: bool = ..., associated: str = ..., attach_associated: bool = ..., rgb: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def openslideload_source(source: Source, level: int = ..., autocrop: bool = ..., associated: str = ..., attach_associated: bool = ..., rgb: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def pdfload(filename: str, page: int = ..., n: int = ..., dpi: float = ..., scale: float = ..., background: list[float] = ..., password: str = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def pdfload_buffer(buffer: str, page: int = ..., n: int = ..., dpi: float = ..., scale: float = ..., background: list[float] = ..., password: str = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def pdfload_source(source: Source, page: int = ..., n: int = ..., dpi: float = ..., scale: float = ..., background: list[float] = ..., password: str = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def percent(self, percent: float) -> int: ... + @staticmethod + def perlin(width: int, height: int, cell_size: int = ..., uchar: bool = ..., seed: int = ...) -> Image: ... + def phasecor(self, in2: Image) -> Image: ... + @staticmethod + def pngload(filename: str, unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def pngload_buffer(buffer: str, unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def pngload_source(source: Source, unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def pngsave(self, filename: str, compression: int = ..., interlace: bool = ..., filter: int = ..., palette: bool = ..., Q: int = ..., dither: float = ..., bitdepth: int = ..., effort: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def pngsave_buffer(self, compression: int = ..., interlace: bool = ..., filter: int = ..., palette: bool = ..., Q: int = ..., dither: float = ..., bitdepth: int = ..., effort: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def pngsave_target(self, target: Target, compression: int = ..., interlace: bool = ..., filter: int = ..., palette: bool = ..., Q: int = ..., dither: float = ..., bitdepth: int = ..., effort: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + @staticmethod + def ppmload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def ppmload_source(source: Source, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def ppmsave(self, filename: str, format: Union[str, ForeignPpmFormat] = ..., ascii: bool = ..., bitdepth: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def ppmsave_target(self, target: Target, format: Union[str, ForeignPpmFormat] = ..., ascii: bool = ..., bitdepth: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def premultiply(self, max_alpha: float = ...) -> Image: ... + def prewitt(self) -> Image: ... + def profile(self) -> tuple[Image, Image]: ... + @staticmethod + def profile_load(name: str) -> str: ... + def project(self) -> tuple[Image, Image]: ... + def quadratic(self, coeff: Image, interpolate: GObject = ...) -> Image: ... + def rad2float(self) -> Image: ... + @staticmethod + def radload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def radload_buffer(buffer: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def radload_source(source: Source, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def radsave(self, filename: str, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def radsave_buffer(self, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def radsave_target(self, target: Target, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def rank(self, width: int, height: int, index: int) -> Image: ... + @staticmethod + def rawload(filename: str, width: int, height: int, bands: int, offset: int = ..., format: Union[str, BandFormat] = ..., interpretation: Union[str, Interpretation] = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def rawsave(self, filename: str, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def rawsave_buffer(self, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def rawsave_target(self, target: Target, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def recomb(self, m: Image) -> Image: ... + def reduce(self, hshrink: float, vshrink: float, kernel: Union[str, Kernel] = ..., gap: float = ...) -> Image: ... + def reduceh(self, hshrink: float, kernel: Union[str, Kernel] = ..., gap: float = ...) -> Image: ... + def reducev(self, vshrink: float, kernel: Union[str, Kernel] = ..., gap: float = ...) -> Image: ... + def relational(self, right: Image, relational: Union[str, OperationRelational]) -> Image: ... + def relational_const(self, relational: Union[str, OperationRelational], c: list[float]) -> Image: ... + def remainder(self, right: Image) -> Image: ... + def remainder_const(self, c: list[float]) -> Image: ... + def replicate(self, across: int, down: int) -> Image: ... + def resize(self, scale: float, kernel: Union[str, Kernel] = ..., gap: float = ..., vscale: float = ...) -> Image: ... + def rot(self, angle: Union[str, Angle]) -> Image: ... + def rot45(self, angle: Union[str, Angle45] = ...) -> Image: ... + def rotate(self, angle: float, interpolate: GObject = ..., background: list[float] = ..., odx: float = ..., ody: float = ..., idx: float = ..., idy: float = ...) -> Image: ... + def round(self, round: Union[str, OperationRound]) -> Image: ... + def sRGB2HSV(self) -> Image: ... + def sRGB2scRGB(self) -> Image: ... + def scRGB2BW(self, depth: int = ...) -> Image: ... + def scRGB2XYZ(self) -> Image: ... + def scRGB2sRGB(self, depth: int = ...) -> Image: ... + def scharr(self) -> Image: ... + @staticmethod + def sdf(width: int, height: int, shape: Union[str, SdfShape], r: float = ..., a: list[float] = ..., b: list[float] = ..., corners: list[float] = ...) -> Image: ... + def sequential(self, tile_height: int = ...) -> Image: ... + def sharpen(self, sigma: float = ..., x1: float = ..., y2: float = ..., y3: float = ..., m1: float = ..., m2: float = ...) -> Image: ... + def shrink(self, hshrink: float, vshrink: float, ceil: bool = ...) -> Image: ... + def shrinkh(self, hshrink: int, ceil: bool = ...) -> Image: ... + def shrinkv(self, vshrink: int, ceil: bool = ...) -> Image: ... + def sign(self) -> Image: ... + def similarity(self, scale: float = ..., angle: float = ..., interpolate: GObject = ..., background: list[float] = ..., odx: float = ..., ody: float = ..., idx: float = ..., idy: float = ...) -> Image: ... + @staticmethod + def sines(width: int, height: int, uchar: bool = ..., hfreq: float = ..., vfreq: float = ...) -> Image: ... + def smartcrop(self, width: int, height: int, interesting: Union[str, Interesting] = ..., premultiplied: bool = ..., attention_x: bool = ..., attention_y: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def sobel(self) -> Image: ... + def spcor(self, ref: Image) -> Image: ... + def spectrum(self) -> Image: ... + def stats(self) -> Image: ... + def stdif(self, width: int, height: int, s0: float = ..., b: float = ..., m0: float = ..., a: float = ...) -> Image: ... + def subsample(self, xfac: int, yfac: int, point: bool = ...) -> Image: ... + def subtract(self, right: Image) -> Image: ... + @staticmethod + def sum(in_: list[Image]) -> Image: ... + @staticmethod + def svgload(filename: str, dpi: float = ..., scale: float = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def svgload_buffer(buffer: str, dpi: float = ..., scale: float = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def svgload_source(source: Source, dpi: float = ..., scale: float = ..., unlimited: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def switch(tests: list[Image]) -> Image: ... + @staticmethod + def system(cmd_format: str, in_: list[Image] = ..., out_format: str = ..., in_format: str = ..., out: bool = ..., log: bool = ...) -> Union[None, tuple[Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def text(text: str, font: str = ..., width: int = ..., height: int = ..., align: Union[str, Align] = ..., justify: bool = ..., dpi: int = ..., spacing: int = ..., fontfile: str = ..., rgba: bool = ..., wrap: Union[str, TextWrap] = ..., autofit_dpi: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def thumbnail(filename: str, width: int, height: int = ..., size: Union[str, Size] = ..., no_rotate: bool = ..., crop: Union[str, Interesting] = ..., linear: bool = ..., import_profile: str = ..., export_profile: str = ..., intent: Union[str, Intent] = ..., fail_on: Union[str, FailOn] = ...) -> Image: ... + @staticmethod + def thumbnail_buffer(buffer: str, width: int, option_string: str = ..., height: int = ..., size: Union[str, Size] = ..., no_rotate: bool = ..., crop: Union[str, Interesting] = ..., linear: bool = ..., import_profile: str = ..., export_profile: str = ..., intent: Union[str, Intent] = ..., fail_on: Union[str, FailOn] = ...) -> Image: ... + def thumbnail_image(self, width: int, height: int = ..., size: Union[str, Size] = ..., no_rotate: bool = ..., crop: Union[str, Interesting] = ..., linear: bool = ..., import_profile: str = ..., export_profile: str = ..., intent: Union[str, Intent] = ..., fail_on: Union[str, FailOn] = ...) -> Image: ... + @staticmethod + def thumbnail_source(source: Source, width: int, option_string: str = ..., height: int = ..., size: Union[str, Size] = ..., no_rotate: bool = ..., crop: Union[str, Interesting] = ..., linear: bool = ..., import_profile: str = ..., export_profile: str = ..., intent: Union[str, Intent] = ..., fail_on: Union[str, FailOn] = ...) -> Image: ... + @staticmethod + def tiffload(filename: str, page: int = ..., subifd: int = ..., n: int = ..., autorotate: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def tiffload_buffer(buffer: str, page: int = ..., subifd: int = ..., n: int = ..., autorotate: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def tiffload_source(source: Source, page: int = ..., subifd: int = ..., n: int = ..., autorotate: bool = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def tiffsave(self, filename: str, compression: Union[str, ForeignTiffCompression] = ..., Q: int = ..., predictor: Union[str, ForeignTiffPredictor] = ..., tile: bool = ..., tile_width: int = ..., tile_height: int = ..., pyramid: bool = ..., miniswhite: bool = ..., bitdepth: int = ..., resunit: Union[str, ForeignTiffResunit] = ..., xres: float = ..., yres: float = ..., bigtiff: bool = ..., properties: bool = ..., region_shrink: Union[str, RegionShrink] = ..., level: int = ..., lossless: bool = ..., depth: Union[str, ForeignDzDepth] = ..., subifd: bool = ..., premultiply: bool = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def tiffsave_buffer(self, compression: Union[str, ForeignTiffCompression] = ..., Q: int = ..., predictor: Union[str, ForeignTiffPredictor] = ..., tile: bool = ..., tile_width: int = ..., tile_height: int = ..., pyramid: bool = ..., miniswhite: bool = ..., bitdepth: int = ..., resunit: Union[str, ForeignTiffResunit] = ..., xres: float = ..., yres: float = ..., bigtiff: bool = ..., properties: bool = ..., region_shrink: Union[str, RegionShrink] = ..., level: int = ..., lossless: bool = ..., depth: Union[str, ForeignDzDepth] = ..., subifd: bool = ..., premultiply: bool = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def tiffsave_target(self, target: Target, compression: Union[str, ForeignTiffCompression] = ..., Q: int = ..., predictor: Union[str, ForeignTiffPredictor] = ..., tile: bool = ..., tile_width: int = ..., tile_height: int = ..., pyramid: bool = ..., miniswhite: bool = ..., bitdepth: int = ..., resunit: Union[str, ForeignTiffResunit] = ..., xres: float = ..., yres: float = ..., bigtiff: bool = ..., properties: bool = ..., region_shrink: Union[str, RegionShrink] = ..., level: int = ..., lossless: bool = ..., depth: Union[str, ForeignDzDepth] = ..., subifd: bool = ..., premultiply: bool = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def tilecache(self, tile_width: int = ..., tile_height: int = ..., max_tiles: int = ..., access: Union[str, Access] = ..., threaded: bool = ..., persistent: bool = ...) -> Image: ... + @staticmethod + def tonelut(in_max: int = ..., out_max: int = ..., Lb: float = ..., Lw: float = ..., Ps: float = ..., Pm: float = ..., Ph: float = ..., S: float = ..., M: float = ..., H: float = ...) -> Image: ... + def transpose3d(self, page_height: int = ...) -> Image: ... + def unpremultiply(self, max_alpha: float = ..., alpha_band: int = ...) -> Image: ... + @staticmethod + def vipsload(filename: str, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def vipsload_source(source: Source, memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def vipssave(self, filename: str, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def vipssave_target(self, target: Target, keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + @staticmethod + def webpload(filename: str, page: int = ..., n: int = ..., scale: float = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def webpload_buffer(buffer: str, page: int = ..., n: int = ..., scale: float = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + @staticmethod + def webpload_source(source: Source, page: int = ..., n: int = ..., scale: float = ..., memory: bool = ..., access: Union[str, Access] = ..., fail_on: Union[str, FailOn] = ..., revalidate: bool = ..., flags: bool = ...) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + def webpsave(self, filename: str, Q: int = ..., lossless: bool = ..., preset: Union[str, ForeignWebpPreset] = ..., smart_subsample: bool = ..., near_lossless: bool = ..., alpha_q: int = ..., min_size: bool = ..., kmin: int = ..., kmax: int = ..., effort: int = ..., target_size: int = ..., mixed: bool = ..., smart_deblock: bool = ..., passes: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def webpsave_buffer(self, Q: int = ..., lossless: bool = ..., preset: Union[str, ForeignWebpPreset] = ..., smart_subsample: bool = ..., near_lossless: bool = ..., alpha_q: int = ..., min_size: bool = ..., kmin: int = ..., kmax: int = ..., effort: int = ..., target_size: int = ..., mixed: bool = ..., smart_deblock: bool = ..., passes: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> str: ... + def webpsave_mime(self, Q: int = ..., lossless: bool = ..., preset: Union[str, ForeignWebpPreset] = ..., smart_subsample: bool = ..., near_lossless: bool = ..., alpha_q: int = ..., min_size: bool = ..., kmin: int = ..., kmax: int = ..., effort: int = ..., target_size: int = ..., mixed: bool = ..., smart_deblock: bool = ..., passes: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + def webpsave_target(self, target: Target, Q: int = ..., lossless: bool = ..., preset: Union[str, ForeignWebpPreset] = ..., smart_subsample: bool = ..., near_lossless: bool = ..., alpha_q: int = ..., min_size: bool = ..., kmin: int = ..., kmax: int = ..., effort: int = ..., target_size: int = ..., mixed: bool = ..., smart_deblock: bool = ..., passes: int = ..., keep: int = ..., background: list[float] = ..., page_height: int = ..., profile: str = ...) -> None: ... + @staticmethod + def worley(width: int, height: int, cell_size: int = ..., seed: int = ...) -> Image: ... + def wrap(self, x: int = ..., y: int = ...) -> Image: ... + @staticmethod + def xyz(width: int, height: int, csize: int = ..., dsize: int = ..., esize: int = ...) -> Image: ... + @staticmethod + def zone(width: int, height: int, uchar: bool = ...) -> Image: ... + def zoom(self, xfac: int, yfac: int) -> Image: ... + + # Operator overloads + def __add__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __radd__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __sub__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rsub__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __mul__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rmul__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __truediv__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rtruediv__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __floordiv__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rfloordiv__(self, other: Union[float, int, List[float], List[int]]) -> Image: ... + def __mod__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __pow__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rpow__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __abs__(self) -> Image: ... + def __neg__(self) -> Image: ... + def __pos__(self) -> Image: ... + def __invert__(self) -> Image: ... + def __lshift__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rshift__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __and__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rand__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __or__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __ror__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __xor__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __rxor__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __gt__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __ge__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __lt__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + def __le__(self, other: Union[Image, float, int, List[float], List[int]]) -> Image: ... + + def __getitem__(self, arg: Union[int, slice, List[int], List[bool]]) -> Image: ... + def __call__(self, x: int, y: int) -> List[float]: ... + def __repr__(self) -> str: ... + + +class Operation: ... +class Introspect: ... + +# Global functions +def cache_set_max(mx: int) -> None: ... +def cache_set_max_mem(mx: int) -> None: ... +def cache_set_max_files(mx: int) -> None: ... +def cache_set_trace(trace: bool) -> None: ... +def cache_get_max() -> int: ... +def cache_get_size() -> int: ... +def cache_get_max_mem() -> int: ... +def cache_get_max_files() -> int: ... +def block_untrusted_set(state: bool) -> None: ... +def operation_block_set(name: str, state: bool) -> None: ... +def leak_set(leak: bool) -> None: ... +def shutdown() -> None: ... +def call(operation_name: str, *args: object, **kwargs: object) -> Union[Image, tuple[Image, Dict[str, Union[bool, int, float, str, Image, list[int], list[float], list[Image]]]]]: ... + +# Module-level constants +API_mode: bool diff --git a/pyvips/version.py b/pyvips/version.py index 7942927..b263dbd 100644 --- a/pyvips/version.py +++ b/pyvips/version.py @@ -1,4 +1,4 @@ # this is used in pyproject.toml and imported into __init__.py -__version__ = '3.1.1' +__version__ = "3.2.0" -__all__ = ['__version__'] +__all__ = ["__version__"] diff --git a/tests/test_type_hints.py b/tests/test_type_hints.py new file mode 100644 index 0000000..d86cffd --- /dev/null +++ b/tests/test_type_hints.py @@ -0,0 +1,39 @@ +""" +Test type stubs are valid and work with mypy. + +This test verifies the type stubs can be used for type checking. +Run with: pytest -q tests/test_type_hints.py + +Also verify with mypy: + mypy tests/test_type_hints.py +""" + + +def test_type_stubs_basic(): + """Test basic type stubs functionality.""" + import pyvips + + # Basic type checking - these should all be valid + img = pyvips.Image.black(100, 100, bands=3) + assert isinstance(img, pyvips.Image) + + # Test method calls + inverted = img.invert() + assert isinstance(inverted, pyvips.Image) + + resized = img.resize(0.5) + assert isinstance(resized, pyvips.Image) + + # Test operators + result = img + 5 + assert isinstance(result, pyvips.Image) + + # Test metadata + typeof_val = img.get_typeof("xres") + assert isinstance(typeof_val, int) + + # Test get returns union type + metadata = img.get("xres") + assert isinstance(metadata, float) + + print("Type stubs test passed!")