Repository: AnonymouX47/term-image (language: python)

Issue:
Redesign `iterm2` native animation
- Adds `ANIM` render method to the `iterm2` render style.
- Removes *native* and *stall_native* style-specific render parameters.
- Modifies the *method* field of the style-speific format spec.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f8f9407..31e2a6b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
     - `BaseImage.set_size()`
     - `BaseImage.size`
 - Support for terminal size-relative padding ([#91]).
+- `ANIM` render method to the `iterm2` render style ([#92]).
 
 ### Changed
 - `UrwidImage.clear_all()` -> `UrwidImageScreen.clear_images()` ([08f4e4d]).
@@ -36,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - No longer accept `None`.
   - Now accept non-positive integers.
   - Changed default values to `0` and `-2` respectively.
+- Swapped `N` for `A` in the *method* field of the `iterm2` style-speific render format specification ([#92]).
 
 ### Removed
 - Image scaling ([#88]).
@@ -43,10 +45,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - `scale`, `scale_x` and `scale_y` properties of `BaseImage`.
 - Image sizing allowance ([#89]).
   - *h_allow* and *v_allow* parameters of `BaseImage.set_size()`.
+- *native* and *stall_native* `iterm2` style-specific render parameters ([#92]).
+  - Replaced by the `ANIM` render method.
 
 [#87]: https://github.com/AnonymouX47/term-image/pull/87
 [#88]: https://github.com/AnonymouX47/term-image/pull/88
 [#89]: https://github.com/AnonymouX47/term-image/pull/89
+[#92]: https://github.com/AnonymouX47/term-image/pull/92
 [08f4e4d]: https://github.com/AnonymouX47/term-image/commit/08f4e4d1514313bbd4278dadde46d21d0b11ed1f
 [fa47742]: https://github.com/AnonymouX47/term-image/commit/fa477424c83474256d4972c4b2cdd4a765bc1cda
 [ed3baa3]: https://github.com/AnonymouX47/term-image/commit/ed3baa38d7621720c007f4662f89d7abadd76ec3
diff --git a/src/term_image/image/common.py b/src/term_image/image/common.py
index 05591989..ac64b6be 100644
--- a/src/term_image/image/common.py
+++ b/src/term_image/image/common.py
@@ -758,7 +758,6 @@ def draw(
           * *scroll* is ignored.
           * Image size is always validated, if set.
           * :term:`Padding height` is always validated.
-          * **with the exception of native animations provided by some render styles**.
 
         * Animations, **by default**, are infinitely looped and can be terminated
           with :py:data:`~signal.SIGINT` (``CTRL + C``), **without** raising
@@ -785,13 +784,9 @@ def draw(
                 "pad_width", pad_width, got_extra=f"terminal_width={terminal_width}"
             )
 
-        animation = not style.get("native") and self._is_animated and animate
-        if (
-            not style.get("native")
-            and self._is_animated
-            and animate
-            and pad_height > terminal_height
-        ):
+        animation = self._is_animated and animate
+
+        if animation and pad_height > terminal_height:
             raise arg_value_error_range(
                 "pad_height",
                 pad_height,
@@ -810,7 +805,7 @@ def render(image: PIL.Image.Image) -> None:
             sys.stdout.isatty() and print(HIDE_CURSOR, end="", flush=True)
             try:
                 style_args = self._check_style_args(style)
-                if self._is_animated and animate:
+                if animation:
                     self._display_animated(
                         image, alpha, fmt, repeat, cached, **style_args
                     )
diff --git a/src/term_image/image/iterm2.py b/src/term_image/image/iterm2.py
index e99dc275..434a8888 100644
--- a/src/term_image/image/iterm2.py
+++ b/src/term_image/image/iterm2.py
@@ -9,7 +9,6 @@
 import warnings
 from base64 import standard_b64encode
 from operator import mul
-from threading import Event
 from typing import Any, Dict, Optional, Set, Tuple, Union
 
 import PIL
@@ -33,6 +32,7 @@
 # Constants for render methods
 LINES = "lines"
 WHOLE = "whole"
+ANIM = "anim"
 
 
 class ITerm2Image(GraphicsImage):
@@ -52,17 +52,16 @@ class ITerm2Image(GraphicsImage):
 
        Pros:
 
-       * Good for use cases where it might be required to trim some lines of the
-         image.
+       * Good for use cases where it might be required to trim some lines of the image.
 
        Cons:
 
-       * Image drawing is very slow on iTerm2 due to the terminal emulator's
+       * Image drawing is significantly slower on iTerm2 due to the terminal emulator's
          performance.
 
     WHOLE
        Renders an image all at once i.e the entire image data is encoded into one
-       line of the :term:`rendered` output, such that the entire image is drawn once
+       line of the :term:`render` output, such that the entire image is drawn once
        by the terminal and still occupies the correct amount of lines and columns.
 
        Pros:
@@ -77,6 +76,21 @@ class ITerm2Image(GraphicsImage):
        * This method currently doesn't work well on iTerm2 and WezTerm when the image
          height is greater than the terminal height.
 
+    ANIM
+        Renders an animated image to utilize the protocol's native animation feature
+        [1]_.
+
+        Similar to the **WHOLE** render method, except that the terminal emulator
+        animates the image, provided it supports the feature of the protocol.
+        The animation is completely controled by the terminal emulator.
+
+        .. note::
+            * If the image data size (in bytes) is greater than the value of
+              :py:attr:`native_anim_max_bytes`, a warning is issued.
+            * If used with :py:class:`~term_image.image.ImageIterator` or an animation,
+              the **WHOLE** render method is used instead.
+            * If the image is non-animated, the **WHOLE** render method is used instead.
+
     NOTE:
         The **LINES** method is the default only because it works properly in all cases,
         it's more advisable to use the **WHOLE** method except when the image height is
@@ -114,24 +128,6 @@ class ITerm2Image(GraphicsImage):
       * *default* → ``4``
       * Results in a trade-off between render time and data size/draw speed
 
-    * **native** (*bool*) → Native animation policy. [1]_
-
-      * ``True`` → use the protocol's native animation feature
-      * ``False`` → use the normal animation
-      * *default* → ``False``
-      * *alpha*, *repeat*, *cached* and *style* do not apply
-      * Ignored if the image is not animated or *animate* is ``False``
-      * Normal restrictions for sizing of animations do not apply
-      * Uses **WHOLE** render method
-      * The terminal emulator completely controls the animation
-
-    * **stall_native** (*bool*) → Native animation execution control.
-
-      * ``True`` → block until :py:data:`~signal.SIGINT` (``CTRL + C``) is recieved
-        (like non-native animation)
-      * ``False`` → return as soon as the image is transmitted
-      * *default* → ``True``
-
     |
 
     **Format Specification**
@@ -146,8 +142,7 @@ class ITerm2Image(GraphicsImage):
 
       * ``L`` → **LINES** render method (current frame only, for animated images)
       * ``W`` → **WHOLE** render method (current frame only, for animated images)
-      * ``N`` → Native animation [1]_ (ignored when used with non-animated images or
-        :py:class:`~term_image.image.ImageIterator`)
+      * ``A`` → **ANIM** render method [1]_
       * *default* → current effective render method of the instance
 
     * ``m`` → cell content inter-mix policy (**Only supported in WezTerm**, ignored
@@ -183,6 +178,7 @@ class ITerm2Image(GraphicsImage):
         * `Konsole <https://konsole.kde.org>`_ >= 22.04.0
         * `WezTerm <https://wezfurlong.org/wezterm/>`_
 
+
     .. [1] Native animation support:
 
        * Not all animated image formats may be supported by every supported terminal
@@ -194,9 +190,9 @@ class ITerm2Image(GraphicsImage):
     """
 
     _FORMAT_SPEC: Tuple[re.Pattern] = tuple(
-        map(re.compile, "[LWN] m[01] c[0-9]".split(" "))
+        map(re.compile, "[LWA] m[01] c[0-9]".split(" "))
     )
-    _render_methods: Set[str] = {LINES, WHOLE}
+    _render_methods: Set[str] = {LINES, WHOLE, ANIM}
     _default_render_method: str = LINES
     _render_method: str = LINES
     _style_args = {
@@ -230,22 +226,6 @@ class ITerm2Image(GraphicsImage):
                 "Compression level must be between 0 and 9, both inclusive",
             ),
         ),
-        "native": (
-            False,
-            (
-                lambda x: isinstance(x, bool),
-                "Native animation policy must be a boolean",
-            ),
-            (lambda _: True, ""),
-        ),
-        "stall_native": (
-            True,
-            (
-                lambda x: isinstance(x, bool),
-                "Native animation execution policy must be a boolean",
-            ),
-            (lambda _: True, ""),
-        ),
     }
 
     _TERM: str = ""
@@ -483,17 +463,6 @@ def clear(cls, cursor: bool = False, now: bool = False) -> None:
                 else (ctlseqs.KITTY_DELETE_ALL_b if now else ctlseqs.KITTY_DELETE_ALL)
             )
 
-    def draw(self, *args, **kwargs):
-        # Ignore (and omit) native animation arguments for non-animations
-        if not (self._is_animated and kwargs.get("animate", True)):
-            for arg_name in ("native", "stall_native"):
-                try:
-                    del kwargs[arg_name]
-                except KeyError:
-                    pass
-
-        super().draw(*args, **kwargs)
-
     @classmethod
     def is_supported(cls):
         if cls._supported is None:
@@ -518,10 +487,8 @@ def _check_style_format_spec(cls, spec: str, original: str) -> Dict[str, Any]:
         args = {}
         if parent:
             args.update(super()._check_style_format_spec(parent, original))
-        if method == "N":
-            args["native"] = True
-        elif method:
-            args["method"] = LINES if method == "L" else WHOLE
+        if method:
+            args["method"] = {"L": LINES, "W": WHOLE, "A": ANIM}[method]
         if mix:
             args["mix"] = bool(int(mix[-1]))
         if compress:
@@ -536,55 +503,34 @@ def _display_animated(
         fmt,
         *args,
         mix: bool = False,
-        native: bool = False,
-        stall_native: bool = True,
         **kwargs,
     ):
-        if native:
-            try:
-                print(
-                    self._format_render(
-                        self._render_image(img, alpha, mix=mix, native=True),
-                        *fmt,
-                    ),
-                    end="",
-                    flush=True,
-                )
-            except (KeyboardInterrupt, Exception):
-                self._handle_interrupted_draw()
-                raise
-            else:
-                try:
-                    stall_native and native_anim.wait()
-                except KeyboardInterrupt:
-                    pass
-        else:
-            if not mix and self._TERM == "wezterm":
-                pad_height = fmt[-1]
-                lines = max(
-                    (
-                        pad_height
-                        if pad_height > 0
-                        else get_terminal_size().lines + pad_height
-                    ),
-                    self.rendered_height,
-                )
-                r_width = self.rendered_width
-                erase_and_move_cursor = ERASE_CHARS % r_width + CURSOR_FORWARD % r_width
-                first_frame = self._format_render(
-                    f"{erase_and_move_cursor}\n" * (lines - 1) + erase_and_move_cursor,
-                    *fmt,
-                )
-                print(
-                    first_frame,
-                    "\r",
-                    CURSOR_UP % (lines - 1),
-                    sep="",
-                    end="",
-                    flush=True,
-                )
+        if not mix and self._TERM == "wezterm":
+            pad_height = fmt[-1]
+            lines = max(
+                (
+                    pad_height
+                    if pad_height > 0
+                    else get_terminal_size().lines + pad_height
+                ),
+                self.rendered_height,
+            )
+            r_width = self.rendered_width
+            erase_and_move_cursor = ERASE_CHARS % r_width + CURSOR_FORWARD % r_width
+            first_frame = self._format_render(
+                f"{erase_and_move_cursor}\n" * (lines - 1) + erase_and_move_cursor,
+                *fmt,
+            )
+            print(
+                first_frame,
+                "\r",
+                CURSOR_UP % (lines - 1),
+                sep="",
+                end="",
+                flush=True,
+            )
 
-            super()._display_animated(img, alpha, fmt, *args, mix=True, **kwargs)
+        super()._display_animated(img, alpha, fmt, *args, mix=True, **kwargs)
 
     @staticmethod
     def _handle_interrupted_draw():
@@ -609,7 +555,6 @@ def _render_image(
         method: Optional[str] = None,
         mix: bool = False,
         compress: int = 4,
-        native: bool = False,
     ) -> str:
         # Using `width=<columns>`, `height=<lines>` and `preserveAspectRatio=0` ensures
         # that an image always occupies the correct amount of columns and lines even if
@@ -618,6 +563,7 @@ def _render_image(
         # upscaling an image on this end; ensures minimal payload.
 
         r_width, r_height = self.rendered_size
+        render_method = (method or self._render_method).lower()
 
         # Workarounds
         is_on_konsole = self._TERM == "konsole"
@@ -633,7 +579,7 @@ def _render_image(
             except (AttributeError, OSError):
                 file_is_readable = False
 
-        if native and self._is_animated and not frame:
+        if render_method == ANIM and self._is_animated and not frame:
             if self._source_type is ImageSource.PIL_IMAGE:
                 if file_is_readable:
                     compressed_image = open(img.filename, "rb")
@@ -686,7 +632,6 @@ def _render_image(
                     )
                 )
 
-        render_method = (method or self._render_method).lower()
         width, height = self._get_minimal_render_size(adjust=render_method == LINES)
 
         if (  # Read directly from file when possible and reasonable
@@ -807,5 +752,4 @@ def _render_image(
             )
 
 
-native_anim = Event()
 _stdout_write = sys.stdout.write


Test patch:
diff --git a/tests/test_image/test_iterm2.py b/tests/test_image/test_iterm2.py
index 982a5a58..cb3a77f4 100644
--- a/tests/test_image/test_iterm2.py
+++ b/tests/test_image/test_iterm2.py
@@ -1,7 +1,6 @@
 """ITerm2Image-specific tests"""
 
 import io
-import sys
 from base64 import standard_b64decode
 from contextlib import contextmanager
 
@@ -14,12 +13,11 @@
 from term_image import ctlseqs
 from term_image.exceptions import ITerm2ImageError, TermImageWarning
 from term_image.image import iterm2
-from term_image.image.iterm2 import LINES, WHOLE, ITerm2Image
+from term_image.image.iterm2 import ANIM, LINES, WHOLE, ITerm2Image
 
 from .. import set_fg_bg_colors
 from . import common
 from .common import _size, get_actual_render_size, python_img, setup_common
-from .test_base import clear_stdout, stdout
 
 ITerm2Image.read_from_file = False
 
@@ -450,6 +448,10 @@ def test_set_render_method():
         assert ITerm2Image.set_render_method(WHOLE.lower()) is None
         assert ITerm2Image.set_render_method(LINES.upper()) is None
         assert ITerm2Image.set_render_method(LINES.lower()) is None
+        assert ITerm2Image.set_render_method(ANIM.upper()) is None
+        assert ITerm2Image.set_render_method(ANIM.lower()) is None
+
+        # WHOLE
 
         assert ITerm2Image.set_render_method(WHOLE) is None
         assert ITerm2Image._render_method == WHOLE
@@ -458,9 +460,14 @@ def test_set_render_method():
         assert image.set_render_method(LINES) is None
         assert image._render_method == LINES
 
+        assert image.set_render_method(ANIM) is None
+        assert image._render_method == ANIM
+
         assert image.set_render_method() is None
         assert image._render_method == WHOLE
 
+        # LINES
+
         assert ITerm2Image.set_render_method(LINES) is None
         assert ITerm2Image._render_method == LINES
         assert image._render_method == LINES
@@ -468,9 +475,29 @@ def test_set_render_method():
         assert image.set_render_method(WHOLE) is None
         assert image._render_method == WHOLE
 
+        assert image.set_render_method(ANIM) is None
+        assert image._render_method == ANIM
+
         assert image.set_render_method() is None
         assert image._render_method == LINES
 
+        # ANIM
+
+        assert ITerm2Image.set_render_method(ANIM) is None
+        assert ITerm2Image._render_method == ANIM
+        assert image._render_method == ANIM
+
+        assert image.set_render_method(WHOLE) is None
+        assert image._render_method == WHOLE
+
+        assert image.set_render_method(LINES) is None
+        assert image._render_method == LINES
+
+        assert image.set_render_method() is None
+        assert image._render_method == ANIM
+
+        # default
+
         assert ITerm2Image.set_render_method(WHOLE) is None
         assert ITerm2Image._render_method == WHOLE
         assert image._render_method == WHOLE
@@ -487,7 +514,7 @@ def test_style_format_spec():
         " ",
         "x",
         "LW",
-        "WN",
+        "WA",
         "c1m0",
         "0c",
         "m2",
@@ -506,7 +533,7 @@ def test_style_format_spec():
         ("", {}),
         ("L", {"method": LINES}),
         ("W", {"method": WHOLE}),
-        ("N", {"native": True}),
+        ("A", {"method": ANIM}),
         ("m0", {}),
         ("m1", {"mix": True}),
         ("c4", {}),
@@ -531,10 +558,8 @@ def test_method(self):
             with pytest.raises(ValueError):
                 ITerm2Image._check_style_args({"method": value})
 
-        for value in (LINES, WHOLE):
+        for value in (ANIM, LINES, WHOLE):
             assert ITerm2Image._check_style_args({"method": value}) == {"method": value}
-        assert ITerm2Image._check_style_args({"native": False}) == {}
-        assert ITerm2Image._check_style_args({"native": True}) == {"native": True}
 
     def test_mix(self):
         for value in (0, 1.0, (), [], "2"):
@@ -569,7 +594,7 @@ def expand_control_data(control_data):
     return control_codes
 
 
-def decode_image(data, term="", jpeg=False, native=False, read_from_file=False):
+def decode_image(data, term="", jpeg=False, anim=False, read_from_file=False):
     fill_1, start, data = data.partition(ctlseqs.ITERM2_START)
     assert start == ctlseqs.ITERM2_START
     if term == "konsole":
@@ -592,11 +617,11 @@ def decode_image(data, term="", jpeg=False, native=False, read_from_file=False):
 
     compressed_image = standard_b64decode(payload.encode())
     img = Image.open(io.BytesIO(compressed_image))
-    if native:
+    if anim:
         assert isinstance(img, (GifImageFile, PngImageFile, WebPImageFile))
         assert img.is_animated
 
-    if read_from_file or native:
+    if read_from_file or anim:
         raw_image = None
     else:
         raw_image = img.tobytes()
@@ -875,22 +900,20 @@ def render_image(self, alpha=0.0, *, m=False, c=4):
         return self.trans._renderer(self.trans._render_image, alpha, mix=m, compress=c)
 
     @staticmethod
-    def _test_image_size(
-        image, term="", jpeg=False, native=False, read_from_file=False
-    ):
+    def _test_image_size(image, term="", jpeg=False, anim=False, read_from_file=False):
         w, h = get_actual_render_size(image)
         cols, lines = image.rendered_size
         size_control_data = f"width={cols},height={lines}"
-        render = f"{image:1.1+N}" if native else str(image)
+        render = f"{image:1.1+A}" if anim else str(image)
 
         assert render.count("\n") + 1 == lines
         control_codes, format, mode, _, raw_image, fill = decode_image(
-            render, term=term, jpeg=jpeg, native=native, read_from_file=read_from_file
+            render, term=term, jpeg=jpeg, anim=anim, read_from_file=read_from_file
         )
         assert (
             code in control_codes for code in expand_control_data(size_control_data)
         )
-        if not (read_from_file or native):
+        if not (read_from_file or anim):
             assert len(raw_image) == w * h * len(mode)
         assert fill.count("\n") + 1 == lines
         *fills, last_fill = fill.split("\n")
@@ -1129,6 +1152,78 @@ def test_jpeg(self):
             del ITerm2Image.jpeg_quality
 
 
+class TestRenderAnim:
+    _test_image_size = staticmethod(TestRenderWhole._test_image_size)
+
+    with open("tests/images/elephant.png", "rb") as f:
+        apng_file = f.read()
+    apng_image = ITerm2Image.from_file("tests/images/elephant.png", height=_size)
+
+    with open("tests/images/lion.gif", "rb") as f:
+        gif_file = f.read()
+    gif_image = ITerm2Image.from_file("tests/images/lion.gif", height=_size)
+
+    with open("tests/images/anim.webp", "rb") as f:
+        webp_file = f.read()
+    webp_image = ITerm2Image.from_file("tests/images/anim.webp", height=_size)
+
+    img = Image.open("tests/images/lion.gif")
+    img_image = ITerm2Image(img, height=_size)
+
+    no_file_img = Image.open(open("tests/images/lion.gif", "rb"))
+    no_file_image = ITerm2Image(no_file_img, height=_size)
+
+    def render_native_anim(self, image):
+        return image._renderer(image._render_image, 0.0, method=ANIM)
+
+    # Reads from file when possible
+    def test_read_from_file(self):
+        for image, file in (
+            (self.apng_image, self.apng_file),
+            (self.webp_image, self.webp_file),
+            (self.gif_image, self.gif_file),
+            (self.img_image, self.gif_file),
+        ):
+            for ITerm2Image._TERM in supported_terminals:
+                assert (
+                    file
+                    == decode_image(
+                        self.render_native_anim(image),
+                        term=ITerm2Image._TERM,
+                        anim=True,
+                    )[3]
+                )
+                self._test_image_size(image, term=ITerm2Image._TERM, anim=True)
+
+    # Re-encodes when image file is not accessible
+    def test_re_encode(self):
+        for ITerm2Image._TERM in supported_terminals:
+            assert (
+                self.gif_file
+                != decode_image(
+                    self.render_native_anim(self.no_file_image),
+                    term=ITerm2Image._TERM,
+                    anim=True,
+                )[3]
+            )
+            self._test_image_size(self.no_file_image, term=ITerm2Image._TERM, anim=True)
+
+    # No image file and unknown format
+    def test_unknown_format(self):
+        self.no_file_img.format = None
+        with pytest.raises(
+            ITerm2ImageError, match="Native animation .* unknown format"
+        ):
+            self.render_native_anim(self.no_file_image)
+
+    # Image data size limit
+    def test_max_bytes(self):
+        ITerm2Image.native_anim_max_bytes = 300000
+        with pytest.warns(TermImageWarning, match="maximum for native animation"):
+            self.render_native_anim(self.apng_image)
+        self.render_native_anim(self.gif_image)
+
+
 def test_read_from_file():
     test_image_size = TestRenderWhole._test_image_size
 
@@ -1190,79 +1285,6 @@ def test_read_from_file():
         ITerm2Image.read_from_file = False
 
 
-def test_native_anim():
-    def render_native(image):
-        return image._renderer(image._render_image, 0.0, native=True)
-
-    test_image_size = TestRenderWhole._test_image_size
-
-    with open("tests/images/elephant.png", "rb") as f:
-        apng_file = f.read()
-    apng_image = ITerm2Image.from_file("tests/images/elephant.png", height=_size)
-
-    with open("tests/images/lion.gif", "rb") as f:
-        gif_file = f.read()
-    gif_image = ITerm2Image.from_file("tests/images/lion.gif", height=_size)
-
-    with open("tests/images/anim.webp", "rb") as f:
-        webp_file = f.read()
-    webp_image = ITerm2Image.from_file("tests/images/anim.webp", height=_size)
-
-    img = Image.open("tests/images/lion.gif")
-    img_image = ITerm2Image(img, height=_size)
-
-    no_file_img = Image.open(open("tests/images/lion.gif", "rb"))
-    no_file_image = ITerm2Image(no_file_img, height=_size)
-
-    # Reads from file when possible
-    for image, file in (
-        (apng_image, apng_file),
-        (webp_image, webp_file),
-        (gif_image, gif_file),
-        (img_image, gif_file),
-    ):
-        for ITerm2Image._TERM in supported_terminals:
-            assert (
-                file
-                == decode_image(
-                    render_native(image), term=ITerm2Image._TERM, native=True
-                )[3]
-            )
-            test_image_size(image, term=ITerm2Image._TERM, native=True)
-
-            # No exception is raised
-            try:
-                sys.stdout = stdout
-                image.draw(native=True, stall_native=False)
-            finally:
-                clear_stdout()
-                sys.stdout = sys.__stdout__
-
-    # Re-encodes when image file is not accessible
-    for ITerm2Image._TERM in supported_terminals:
-        assert (
-            gif_file
-            != decode_image(
-                render_native(no_file_image), term=ITerm2Image._TERM, native=True
-            )[3]
-        )
-        test_image_size(no_file_image, term=ITerm2Image._TERM, native=True)
-
-    # No image file and unknown format
-    no_file_img.format = None
-    with pytest.raises(ITerm2ImageError, match="Native animation .* unknown format"):
-        render_native(no_file_image)
-
-    # Image data size limit
-    ITerm2Image.native_anim_max_bytes = 300000
-    with pytest.warns(TermImageWarning, match="maximum for native animation"):
-        render_native(apng_image)
-    render_native(gif_image)
-
-    img.close()
-    no_file_img.close()
-
-
 class TestClear:
     @contextmanager
     def setup_buffer(self):