mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
Merge branch 'main' into winbuild-update
This commit is contained in:
commit
147c52f92f
24
CHANGES.rst
24
CHANGES.rst
|
@ -5,6 +5,30 @@ Changelog (Pillow)
|
||||||
9.3.0 (unreleased)
|
9.3.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Don't reassign crc on ChunkStream close #6627
|
||||||
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Raise a warning if NumPy failed to raise an error during conversion #6594
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Show all frames in ImageShow #6611
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow FLI palette chunk to not be first #6626
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If first GIF frame has transparency for RGB_ALWAYS loading strategy, use RGBA mode #6592
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Round box position to integer when pasting embedded color #6517
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Removed EXIF prefix when saving WebP #6582
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Pad IM palette to 768 bytes when saving #6579
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Added DDS BC6 reading #6449
|
- Added DDS BC6 reading #6449
|
||||||
[ShadelessFox, REDxEYE, radarhere]
|
[ShadelessFox, REDxEYE, radarhere]
|
||||||
|
|
||||||
|
|
BIN
Tests/images/bw_gradient.imt
Normal file
BIN
Tests/images/bw_gradient.imt
Normal file
Binary file not shown.
BIN
Tests/images/hopper_palette_chunk_second.fli
Normal file
BIN
Tests/images/hopper_palette_chunk_second.fli
Normal file
Binary file not shown.
BIN
Tests/images/text_float_coord.png
Normal file
BIN
Tests/images/text_float_coord.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
Tests/images/text_float_coord_1_alt.png
Normal file
BIN
Tests/images/text_float_coord_1_alt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 807 B |
|
@ -4,7 +4,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import FliImagePlugin, Image
|
from PIL import FliImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, is_pypy
|
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
|
||||||
|
|
||||||
# created as an export of a palette image from Gimp2.6
|
# created as an export of a palette image from Gimp2.6
|
||||||
# save as...-> hopper.fli, default options.
|
# save as...-> hopper.fli, default options.
|
||||||
|
@ -79,6 +79,12 @@ def test_invalid_file():
|
||||||
FliImagePlugin.FliImageFile(invalid_file)
|
FliImagePlugin.FliImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_palette_chunk_second():
|
||||||
|
with Image.open("Tests/images/hopper_palette_chunk_second.fli") as im:
|
||||||
|
with Image.open(static_test_file) as expected:
|
||||||
|
assert_image_equal(im.convert("RGB"), expected.convert("RGB"))
|
||||||
|
|
||||||
|
|
||||||
def test_n_frames():
|
def test_n_frames():
|
||||||
with Image.open(static_test_file) as im:
|
with Image.open(static_test_file) as im:
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
|
|
|
@ -84,17 +84,24 @@ def test_l_mode_transparency():
|
||||||
|
|
||||||
|
|
||||||
def test_strategy():
|
def test_strategy():
|
||||||
|
with Image.open("Tests/images/iss634.gif") as im:
|
||||||
|
expected_rgb_always = im.convert("RGB")
|
||||||
|
|
||||||
with Image.open("Tests/images/chi.gif") as im:
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
expected_zero = im.convert("RGB")
|
expected_rgb_always_rgba = im.convert("RGBA")
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
expected_one = im.convert("RGB")
|
expected_different = im.convert("RGB")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
|
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
|
||||||
with Image.open("Tests/images/chi.gif") as im:
|
with Image.open("Tests/images/iss634.gif") as im:
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
assert_image_equal(im, expected_zero)
|
assert_image_equal(im, expected_rgb_always)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
assert_image_equal(im, expected_rgb_always_rgba)
|
||||||
|
|
||||||
GifImagePlugin.LOADING_STRATEGY = (
|
GifImagePlugin.LOADING_STRATEGY = (
|
||||||
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||||
|
@ -105,7 +112,7 @@ def test_strategy():
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
assert_image_equal(im.convert("RGB"), expected_one)
|
assert_image_equal(im.convert("RGB"), expected_different)
|
||||||
|
|
||||||
# Change to RGB mode when a frame has an individual palette
|
# Change to RGB mode when a frame has an individual palette
|
||||||
with Image.open("Tests/images/iss634.gif") as im:
|
with Image.open("Tests/images/iss634.gif") as im:
|
||||||
|
|
|
@ -86,6 +86,18 @@ def test_roundtrip(mode, tmp_path):
|
||||||
assert_image_equal_tofile(im, out)
|
assert_image_equal_tofile(im, out)
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_palette(tmp_path):
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
colors = [0, 1, 2]
|
||||||
|
im.putpalette(colors)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.im")
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpalette() == colors + [0] * 765
|
||||||
|
|
||||||
|
|
||||||
def test_save_unsupported_mode(tmp_path):
|
def test_save_unsupported_mode(tmp_path):
|
||||||
out = str(tmp_path / "temp.im")
|
out = str(tmp_path / "temp.im")
|
||||||
im = hopper("HSV")
|
im = hopper("HSV")
|
||||||
|
|
19
Tests/test_file_imt.py
Normal file
19
Tests/test_file_imt.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, ImtImagePlugin
|
||||||
|
|
||||||
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity():
|
||||||
|
with Image.open("Tests/images/bw_gradient.imt") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n"))
|
||||||
|
def test_invalid_file(data):
|
||||||
|
with io.BytesIO(data) as fp:
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
ImtImagePlugin.ImtImageFile(fp)
|
|
@ -55,9 +55,7 @@ def test_write_exif_metadata():
|
||||||
test_buffer.seek(0)
|
test_buffer.seek(0)
|
||||||
with Image.open(test_buffer) as webp_image:
|
with Image.open(test_buffer) as webp_image:
|
||||||
webp_exif = webp_image.info.get("exif", None)
|
webp_exif = webp_image.info.get("exif", None)
|
||||||
assert webp_exif
|
assert webp_exif == expected_exif[6:], "WebP EXIF didn't match"
|
||||||
if webp_exif:
|
|
||||||
assert webp_exif == expected_exif, "WebP EXIF didn't match"
|
|
||||||
|
|
||||||
|
|
||||||
def test_read_icc_profile():
|
def test_read_icc_profile():
|
||||||
|
|
|
@ -35,10 +35,13 @@ def test_toarray():
|
||||||
test_with_dtype(numpy.float64)
|
test_with_dtype(numpy.float64)
|
||||||
test_with_dtype(numpy.uint8)
|
test_with_dtype(numpy.uint8)
|
||||||
|
|
||||||
if parse_version(numpy.__version__) >= parse_version("1.23"):
|
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
||||||
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
if parse_version(numpy.__version__) >= parse_version("1.23"):
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
numpy.array(im_truncated)
|
numpy.array(im_truncated)
|
||||||
|
else:
|
||||||
|
with pytest.warns(UserWarning):
|
||||||
|
numpy.array(im_truncated)
|
||||||
|
|
||||||
|
|
||||||
def test_fromarray():
|
def test_fromarray():
|
||||||
|
|
|
@ -935,7 +935,30 @@ def test_standard_embedded_color(layout_engine):
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
|
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2)
|
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fontmode", ("1", "L", "RGBA"))
|
||||||
|
def test_float_coord(layout_engine, fontmode):
|
||||||
|
txt = "Hello World!"
|
||||||
|
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (300, 64), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
if fontmode == "1":
|
||||||
|
d.fontmode = "1"
|
||||||
|
|
||||||
|
embedded_color = fontmode == "RGBA"
|
||||||
|
d.text((9.5, 9.5), txt, font=ttf, fill="#fa6", embedded_color=embedded_color)
|
||||||
|
try:
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/text_float_coord.png", 3.9)
|
||||||
|
except AssertionError:
|
||||||
|
if fontmode == "1" and layout_engine == ImageFont.Layout.BASIC:
|
||||||
|
assert_image_similar_tofile(
|
||||||
|
im, "Tests/images/text_float_coord_1_alt.png", 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def test_cbdt(layout_engine):
|
def test_cbdt(layout_engine):
|
||||||
|
|
|
@ -6,10 +6,8 @@ from PIL import Image, ImageMath
|
||||||
def pixel(im):
|
def pixel(im):
|
||||||
if hasattr(im, "im"):
|
if hasattr(im, "im"):
|
||||||
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
||||||
else:
|
elif isinstance(im, int):
|
||||||
if isinstance(im, int):
|
return int(im) # hack to deal with booleans
|
||||||
return int(im) # hack to deal with booleans
|
|
||||||
print(im)
|
|
||||||
|
|
||||||
|
|
||||||
A = Image.new("L", (1, 1), 1)
|
A = Image.new("L", (1, 1), 1)
|
||||||
|
|
|
@ -60,7 +60,10 @@ Pillow also provides limited support for a few additional modes, including:
|
||||||
* ``BGR;24`` (24-bit reversed true colour)
|
* ``BGR;24`` (24-bit reversed true colour)
|
||||||
* ``BGR;32`` (32-bit reversed true colour)
|
* ``BGR;32`` (32-bit reversed true colour)
|
||||||
|
|
||||||
However, Pillow doesn’t support user-defined modes; if you need to handle band
|
Apart from these additional modes, Pillow doesn't yet support multichannel
|
||||||
|
images with a depth of more than 8 bits per channel.
|
||||||
|
|
||||||
|
Pillow also doesn’t support user-defined modes; if you need to handle band
|
||||||
combinations that are not listed above, use a sequence of Image objects.
|
combinations that are not listed above, use a sequence of Image objects.
|
||||||
|
|
||||||
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`
|
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
|
@ -80,11 +81,19 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if i16(s, 4) == 0xF1FA:
|
if i16(s, 4) == 0xF1FA:
|
||||||
# look for palette chunk
|
# look for palette chunk
|
||||||
s = self.fp.read(6)
|
number_of_subchunks = i16(s, 6)
|
||||||
if i16(s, 4) == 11:
|
chunk_size = None
|
||||||
self._palette(palette, 2)
|
for _ in range(number_of_subchunks):
|
||||||
elif i16(s, 4) == 4:
|
if chunk_size is not None:
|
||||||
self._palette(palette, 0)
|
self.fp.seek(chunk_size - 6, os.SEEK_CUR)
|
||||||
|
s = self.fp.read(6)
|
||||||
|
chunk_type = i16(s, 4)
|
||||||
|
if chunk_type in (4, 11):
|
||||||
|
self._palette(palette, 2 if chunk_type == 11 else 0)
|
||||||
|
break
|
||||||
|
chunk_size = i32(s)
|
||||||
|
if not chunk_size:
|
||||||
|
break
|
||||||
|
|
||||||
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
|
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
|
||||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||||
|
|
|
@ -299,11 +299,13 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.im.paste(self.dispose, self.dispose_extent)
|
self.im.paste(self.dispose, self.dispose_extent)
|
||||||
|
|
||||||
self._frame_palette = palette or self.global_palette
|
self._frame_palette = palette or self.global_palette
|
||||||
|
self._frame_transparency = frame_transparency
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
if self._frame_palette:
|
if self._frame_palette:
|
||||||
self.mode = (
|
if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||||
"RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P"
|
self.mode = "RGBA" if frame_transparency is not None else "RGB"
|
||||||
)
|
else:
|
||||||
|
self.mode = "P"
|
||||||
else:
|
else:
|
||||||
self.mode = "L"
|
self.mode = "L"
|
||||||
|
|
||||||
|
@ -313,7 +315,6 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
palette = copy(self.global_palette)
|
palette = copy(self.global_palette)
|
||||||
self.palette = palette
|
self.palette = palette
|
||||||
else:
|
else:
|
||||||
self._frame_transparency = frame_transparency
|
|
||||||
if self.mode == "P":
|
if self.mode == "P":
|
||||||
if (
|
if (
|
||||||
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||||
|
@ -386,7 +387,8 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
transparency = -1
|
transparency = -1
|
||||||
if frame_transparency is not None:
|
if frame_transparency is not None:
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
self.info["transparency"] = frame_transparency
|
if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS:
|
||||||
|
self.info["transparency"] = frame_transparency
|
||||||
elif self.mode not in ("RGB", "RGBA"):
|
elif self.mode not in ("RGB", "RGBA"):
|
||||||
transparency = frame_transparency
|
transparency = frame_transparency
|
||||||
self.tile = [
|
self.tile = [
|
||||||
|
@ -410,9 +412,9 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
temp_mode = "P" if self._frame_palette else "L"
|
temp_mode = "P" if self._frame_palette else "L"
|
||||||
self._prev_im = None
|
self._prev_im = None
|
||||||
if self.__frame == 0:
|
if self.__frame == 0:
|
||||||
if "transparency" in self.info:
|
if self._frame_transparency is not None:
|
||||||
self.im = Image.core.fill(
|
self.im = Image.core.fill(
|
||||||
temp_mode, self.size, self.info["transparency"]
|
temp_mode, self.size, self._frame_transparency
|
||||||
)
|
)
|
||||||
elif self.mode in ("RGB", "RGBA"):
|
elif self.mode in ("RGB", "RGBA"):
|
||||||
self._prev_im = self.im
|
self._prev_im = self.im
|
||||||
|
@ -429,8 +431,12 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
def load_end(self):
|
def load_end(self):
|
||||||
if self.__frame == 0:
|
if self.__frame == 0:
|
||||||
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||||
self.mode = "RGB"
|
if self._frame_transparency is not None:
|
||||||
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||||
|
self.mode = "RGBA"
|
||||||
|
else:
|
||||||
|
self.mode = "RGB"
|
||||||
|
self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
|
||||||
return
|
return
|
||||||
if self.mode == "P" and self._prev_im:
|
if self.mode == "P" and self._prev_im:
|
||||||
if self._frame_transparency is not None:
|
if self._frame_transparency is not None:
|
||||||
|
|
|
@ -352,7 +352,13 @@ def _save(im, fp, filename):
|
||||||
fp.write(b"Lut: 1\r\n")
|
fp.write(b"Lut: 1\r\n")
|
||||||
fp.write(b"\000" * (511 - fp.tell()) + b"\032")
|
fp.write(b"\000" * (511 - fp.tell()) + b"\032")
|
||||||
if im.mode in ["P", "PA"]:
|
if im.mode in ["P", "PA"]:
|
||||||
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
|
im_palette = im.im.getpalette("RGB", "RGB;L")
|
||||||
|
colors = len(im_palette) // 3
|
||||||
|
palette = b""
|
||||||
|
for i in range(3):
|
||||||
|
palette += im_palette[colors * i : colors * (i + 1)]
|
||||||
|
palette += b"\x00" * (256 - colors)
|
||||||
|
fp.write(palette) # 768 bytes
|
||||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
|
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -679,12 +679,24 @@ class Image:
|
||||||
new["shape"] = shape
|
new["shape"] = shape
|
||||||
new["typestr"] = typestr
|
new["typestr"] = typestr
|
||||||
new["version"] = 3
|
new["version"] = 3
|
||||||
if self.mode == "1":
|
try:
|
||||||
# Binary images need to be extended from bits to bytes
|
if self.mode == "1":
|
||||||
# See: https://github.com/python-pillow/Pillow/issues/350
|
# Binary images need to be extended from bits to bytes
|
||||||
new["data"] = self.tobytes("raw", "L")
|
# See: https://github.com/python-pillow/Pillow/issues/350
|
||||||
else:
|
new["data"] = self.tobytes("raw", "L")
|
||||||
new["data"] = self.tobytes()
|
else:
|
||||||
|
new["data"] = self.tobytes()
|
||||||
|
except Exception as e:
|
||||||
|
if not isinstance(e, (MemoryError, RecursionError)):
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
from packaging.version import parse as parse_version
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if parse_version(numpy.__version__) < parse_version("1.23"):
|
||||||
|
warnings.warn(e)
|
||||||
|
raise
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
|
|
|
@ -482,8 +482,8 @@ class ImageDraw:
|
||||||
# extract mask and set text alpha
|
# extract mask and set text alpha
|
||||||
color, mask = mask, mask.getband(3)
|
color, mask = mask, mask.getband(3)
|
||||||
color.fillband(3, (ink >> 24) & 0xFF)
|
color.fillband(3, (ink >> 24) & 0xFF)
|
||||||
coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1]
|
x, y = (int(c) for c in coord)
|
||||||
self.im.paste(color, coord + coord2, mask)
|
self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
|
||||||
else:
|
else:
|
||||||
self.draw.draw_bitmap(coord, mask, ink)
|
self.draw.draw_bitmap(coord, mask, ink)
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ class WindowsViewer(Viewer):
|
||||||
"""The default viewer on Windows is the default system application for PNG files."""
|
"""The default viewer on Windows is the default system application for PNG files."""
|
||||||
|
|
||||||
format = "PNG"
|
format = "PNG"
|
||||||
options = {"compress_level": 1}
|
options = {"compress_level": 1, "save_all": True}
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
return (
|
return (
|
||||||
|
@ -154,7 +154,7 @@ class MacViewer(Viewer):
|
||||||
"""The default viewer on macOS using ``Preview.app``."""
|
"""The default viewer on macOS using ``Preview.app``."""
|
||||||
|
|
||||||
format = "PNG"
|
format = "PNG"
|
||||||
options = {"compress_level": 1}
|
options = {"compress_level": 1, "save_all": True}
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
# on darwin open returns immediately resulting in the temp
|
# on darwin open returns immediately resulting in the temp
|
||||||
|
@ -197,7 +197,7 @@ if sys.platform == "darwin":
|
||||||
|
|
||||||
class UnixViewer(Viewer):
|
class UnixViewer(Viewer):
|
||||||
format = "PNG"
|
format = "PNG"
|
||||||
options = {"compress_level": 1}
|
options = {"compress_level": 1, "save_all": True}
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
command = self.get_command_ex(file, **options)[0]
|
command = self.get_command_ex(file, **options)[0]
|
||||||
|
|
|
@ -39,15 +39,19 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
# Quick rejection: if there's not a LF among the first
|
# Quick rejection: if there's not a LF among the first
|
||||||
# 100 bytes, this is (probably) not a text header.
|
# 100 bytes, this is (probably) not a text header.
|
||||||
|
|
||||||
if b"\n" not in self.fp.read(100):
|
buffer = self.fp.read(100)
|
||||||
|
if b"\n" not in buffer:
|
||||||
raise SyntaxError("not an IM file")
|
raise SyntaxError("not an IM file")
|
||||||
self.fp.seek(0)
|
|
||||||
|
|
||||||
xsize = ysize = 0
|
xsize = ysize = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
s = self.fp.read(1)
|
if buffer:
|
||||||
|
s = buffer[:1]
|
||||||
|
buffer = buffer[1:]
|
||||||
|
else:
|
||||||
|
s = self.fp.read(1)
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -55,7 +59,12 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# image data begins
|
# image data begins
|
||||||
self.tile = [
|
self.tile = [
|
||||||
("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))
|
(
|
||||||
|
"raw",
|
||||||
|
(0, 0) + self.size,
|
||||||
|
self.fp.tell() - len(buffer),
|
||||||
|
(self.mode, 0, 1),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -63,8 +72,11 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# read key/value pair
|
# read key/value pair
|
||||||
# FIXME: dangerous, may read whole file
|
if b"\n" not in buffer:
|
||||||
s = s + self.fp.readline()
|
buffer += self.fp.read(100)
|
||||||
|
lines = buffer.split(b"\n")
|
||||||
|
s += lines.pop(0)
|
||||||
|
buffer = b"\n".join(lines)
|
||||||
if len(s) == 1 or len(s) > 100:
|
if len(s) == 1 or len(s) > 100:
|
||||||
break
|
break
|
||||||
if s[0] == ord(b"*"):
|
if s[0] == ord(b"*"):
|
||||||
|
@ -74,13 +86,13 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
if not m:
|
if not m:
|
||||||
break
|
break
|
||||||
k, v = m.group(1, 2)
|
k, v = m.group(1, 2)
|
||||||
if k == "width":
|
if k == b"width":
|
||||||
xsize = int(v)
|
xsize = int(v)
|
||||||
self._size = xsize, ysize
|
self._size = xsize, ysize
|
||||||
elif k == "height":
|
elif k == b"height":
|
||||||
ysize = int(v)
|
ysize = int(v)
|
||||||
self._size = xsize, ysize
|
self._size = xsize, ysize
|
||||||
elif k == "pixel" and v == "n8":
|
elif k == b"pixel" and v == b"n8":
|
||||||
self.mode = "L"
|
self.mode = "L"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -189,7 +189,7 @@ class ChunkStream:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.queue = self.crc = self.fp = None
|
self.queue = self.fp = None
|
||||||
|
|
||||||
def push(self, cid, pos, length):
|
def push(self, cid, pos, length):
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ class ChunkStream:
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
def crc_skip(self, cid, data):
|
def crc_skip(self, cid, data):
|
||||||
"""Read checksum. Used if the C module is not present"""
|
"""Read checksum"""
|
||||||
|
|
||||||
self.fp.read(4)
|
self.fp.read(4)
|
||||||
|
|
||||||
|
|
|
@ -311,9 +311,11 @@ def _save(im, fp, filename):
|
||||||
lossless = im.encoderinfo.get("lossless", False)
|
lossless = im.encoderinfo.get("lossless", False)
|
||||||
quality = im.encoderinfo.get("quality", 80)
|
quality = im.encoderinfo.get("quality", 80)
|
||||||
icc_profile = im.encoderinfo.get("icc_profile") or ""
|
icc_profile = im.encoderinfo.get("icc_profile") or ""
|
||||||
exif = im.encoderinfo.get("exif", "")
|
exif = im.encoderinfo.get("exif", b"")
|
||||||
if isinstance(exif, Image.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
|
if exif.startswith(b"Exif\x00\x00"):
|
||||||
|
exif = exif[6:]
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
method = im.encoderinfo.get("method", 4)
|
method = im.encoderinfo.get("method", 4)
|
||||||
|
|
||||||
|
|
|
@ -138,9 +138,9 @@ deps = {
|
||||||
"bins": ["cjpeg.exe", "djpeg.exe"],
|
"bins": ["cjpeg.exe", "djpeg.exe"],
|
||||||
},
|
},
|
||||||
"zlib": {
|
"zlib": {
|
||||||
"url": "https://zlib.net/zlib1212.zip",
|
"url": "https://zlib.net/zlib1213.zip",
|
||||||
"filename": "zlib1212.zip",
|
"filename": "zlib1213.zip",
|
||||||
"dir": "zlib-1.2.12",
|
"dir": "zlib-1.2.13",
|
||||||
"license": "README",
|
"license": "README",
|
||||||
"license_pattern": "Copyright notice:\n\n(.+)$",
|
"license_pattern": "Copyright notice:\n\n(.+)$",
|
||||||
"build": [
|
"build": [
|
||||||
|
|
Loading…
Reference in New Issue
Block a user