Pillow

Форк
0
/
test_file_webp.py 
256 строк · 8.5 Кб
1
from __future__ import annotations
2

3
import io
4
import re
5
import sys
6
import warnings
7
from pathlib import Path
8
from typing import Any
9

10
import pytest
11

12
from PIL import Image, WebPImagePlugin, features
13

14
from .helper import (
15
    assert_image_equal,
16
    assert_image_similar,
17
    assert_image_similar_tofile,
18
    hopper,
19
    skip_unless_feature,
20
)
21

22
try:
23
    from PIL import _webp
24

25
    HAVE_WEBP = True
26
except ImportError:
27
    HAVE_WEBP = False
28

29

30
class TestUnsupportedWebp:
31
    def test_unsupported(self) -> None:
32
        if HAVE_WEBP:
33
            WebPImagePlugin.SUPPORTED = False
34

35
        file_path = "Tests/images/hopper.webp"
36
        with pytest.warns(UserWarning):
37
            with pytest.raises(OSError):
38
                with Image.open(file_path):
39
                    pass
40

41
        if HAVE_WEBP:
42
            WebPImagePlugin.SUPPORTED = True
43

44

45
@skip_unless_feature("webp")
46
class TestFileWebp:
47
    def setup_method(self) -> None:
48
        self.rgb_mode = "RGB"
49

50
    def test_version(self) -> None:
51
        version = features.version_module("webp")
52
        assert version is not None
53
        assert re.search(r"\d+\.\d+\.\d+$", version)
54

55
    def test_read_rgb(self) -> None:
56
        """
57
        Can we read a RGB mode WebP file without error?
58
        Does it have the bits we expect?
59
        """
60

61
        with Image.open("Tests/images/hopper.webp") as image:
62
            assert image.mode == self.rgb_mode
63
            assert image.size == (128, 128)
64
            assert image.format == "WEBP"
65
            image.load()
66
            image.getdata()
67

68
            # generated with:
69
            # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
70
            assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
71

72
    def _roundtrip(
73
        self, tmp_path: Path, mode: str, epsilon: float, args: dict[str, Any] = {}
74
    ) -> None:
75
        temp_file = str(tmp_path / "temp.webp")
76

77
        hopper(mode).save(temp_file, **args)
78
        with Image.open(temp_file) as image:
79
            assert image.mode == self.rgb_mode
80
            assert image.size == (128, 128)
81
            assert image.format == "WEBP"
82
            image.load()
83
            image.getdata()
84

85
            if mode == self.rgb_mode:
86
                # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
87
                assert_image_similar_tofile(
88
                    image, "Tests/images/hopper_webp_write.ppm", 12.0
89
                )
90

91
            # This test asserts that the images are similar. If the average pixel
92
            # difference between the two images is less than the epsilon value,
93
            # then we're going to accept that it's a reasonable lossy version of
94
            # the image.
95
            target = hopper(mode)
96
            if mode != self.rgb_mode:
97
                target = target.convert(self.rgb_mode)
98
            assert_image_similar(image, target, epsilon)
99

100
    def test_write_rgb(self, tmp_path: Path) -> None:
101
        """
102
        Can we write a RGB mode file to webp without error?
103
        Does it have the bits we expect?
104
        """
105

106
        self._roundtrip(tmp_path, self.rgb_mode, 12.5)
107

108
    def test_write_method(self, tmp_path: Path) -> None:
109
        self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
110

111
        buffer_no_args = io.BytesIO()
112
        hopper().save(buffer_no_args, format="WEBP")
113

114
        buffer_method = io.BytesIO()
115
        hopper().save(buffer_method, format="WEBP", method=6)
116
        assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
117

118
    def test_save_all(self, tmp_path: Path) -> None:
119
        temp_file = str(tmp_path / "temp.webp")
120
        im = Image.new("RGB", (1, 1))
121
        im2 = Image.new("RGB", (1, 1), "#f00")
122
        im.save(temp_file, save_all=True, append_images=[im2])
123

124
        with Image.open(temp_file) as reloaded:
125
            assert_image_equal(im, reloaded)
126

127
            reloaded.seek(1)
128
            assert_image_similar(im2, reloaded, 1)
129

130
    def test_icc_profile(self, tmp_path: Path) -> None:
131
        self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None})
132
        self._roundtrip(
133
            tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True}
134
        )
135

136
    def test_write_unsupported_mode_L(self, tmp_path: Path) -> None:
137
        """
138
        Saving a black-and-white file to WebP format should work, and be
139
        similar to the original file.
140
        """
141

142
        self._roundtrip(tmp_path, "L", 10.0)
143

144
    def test_write_unsupported_mode_P(self, tmp_path: Path) -> None:
145
        """
146
        Saving a palette-based file to WebP format should work, and be
147
        similar to the original file.
148
        """
149

150
        self._roundtrip(tmp_path, "P", 50.0)
151

152
    @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
153
    def test_write_encoding_error_message(self, tmp_path: Path) -> None:
154
        temp_file = str(tmp_path / "temp.webp")
155
        im = Image.new("RGB", (15000, 15000))
156
        with pytest.raises(ValueError) as e:
157
            im.save(temp_file, method=0)
158
        assert str(e.value) == "encoding error 6"
159

160
    @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
161
    def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None:
162
        temp_file = str(tmp_path / "temp.webp")
163
        im = Image.new("L", (16384, 16384))
164
        with pytest.raises(ValueError) as e:
165
            im.save(temp_file)
166
        assert (
167
            str(e.value)
168
            == "encoding error 5: Image size exceeds WebP limit of 16383 pixels"
169
        )
170

171
    def test_WebPEncode_with_invalid_args(self) -> None:
172
        """
173
        Calling encoder functions with no arguments should result in an error.
174
        """
175
        with pytest.raises(TypeError):
176
            _webp.WebPAnimEncoder()
177
        with pytest.raises(TypeError):
178
            _webp.WebPEncode()
179

180
    def test_WebPAnimDecoder_with_invalid_args(self) -> None:
181
        """
182
        Calling decoder functions with no arguments should result in an error.
183
        """
184
        with pytest.raises(TypeError):
185
            _webp.WebPAnimDecoder()
186

187
    def test_no_resource_warning(self, tmp_path: Path) -> None:
188
        file_path = "Tests/images/hopper.webp"
189
        with Image.open(file_path) as image:
190
            temp_file = str(tmp_path / "temp.webp")
191
            with warnings.catch_warnings():
192
                image.save(temp_file)
193

194
    def test_file_pointer_could_be_reused(self) -> None:
195
        file_path = "Tests/images/hopper.webp"
196
        with open(file_path, "rb") as blob:
197
            Image.open(blob).load()
198
            Image.open(blob).load()
199

200
    @pytest.mark.parametrize(
201
        "background",
202
        (0, (0,), (-1, 0, 1, 2), (253, 254, 255, 256)),
203
    )
204
    def test_invalid_background(
205
        self, background: int | tuple[int, ...], tmp_path: Path
206
    ) -> None:
207
        temp_file = str(tmp_path / "temp.webp")
208
        im = hopper()
209
        with pytest.raises(OSError):
210
            im.save(temp_file, save_all=True, append_images=[im], background=background)
211

212
    def test_background_from_gif(self, tmp_path: Path) -> None:
213
        # Save L mode GIF with background
214
        with Image.open("Tests/images/no_palette_with_background.gif") as im:
215
            out_webp = str(tmp_path / "temp.webp")
216
            im.save(out_webp, save_all=True)
217

218
        # Save P mode GIF with background
219
        with Image.open("Tests/images/chi.gif") as im:
220
            original_value = im.convert("RGB").getpixel((1, 1))
221

222
            # Save as WEBP
223
            out_webp = str(tmp_path / "temp.webp")
224
            im.save(out_webp, save_all=True)
225

226
        # Save as GIF
227
        out_gif = str(tmp_path / "temp.gif")
228
        with Image.open(out_webp) as im:
229
            im.save(out_gif)
230

231
        with Image.open(out_gif) as reread:
232
            reread_value = reread.convert("RGB").getpixel((1, 1))
233
        difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3))
234
        assert difference < 5
235

236
    def test_duration(self, tmp_path: Path) -> None:
237
        with Image.open("Tests/images/dispose_bgnd.gif") as im:
238
            assert im.info["duration"] == 1000
239

240
            out_webp = str(tmp_path / "temp.webp")
241
            im.save(out_webp, save_all=True)
242

243
        with Image.open(out_webp) as reloaded:
244
            reloaded.load()
245
            assert reloaded.info["duration"] == 1000
246

247
    def test_roundtrip_rgba_palette(self, tmp_path: Path) -> None:
248
        temp_file = str(tmp_path / "temp.webp")
249
        im = Image.new("RGBA", (1, 1)).convert("P")
250
        assert im.mode == "P"
251
        assert im.palette is not None
252
        assert im.palette.mode == "RGBA"
253
        im.save(temp_file)
254

255
        with Image.open(temp_file) as im:
256
            assert im.getpixel((0, 0)) == (0, 0, 0, 0)
257

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.