Skip to content

Commit

Permalink
Merge branch 'main' into enum
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Dec 30, 2022
2 parents 21e8111 + 907d597 commit b4347da
Show file tree
Hide file tree
Showing 93 changed files with 1,050 additions and 546 deletions.
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ repos:
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
additional_dependencies:
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]

- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0
Expand Down
24 changes: 24 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ Changelog (Pillow)
9.4.0 (unreleased)
------------------

- Improve exception traceback readability #6836
[hugovk, radarhere]

- Do not attempt to read IFD1 if absent #6840
[radarhere]

- Fixed writing int as ASCII tag #6800
[radarhere]

- If available, use wl-paste or xclip for grabclipboard() on Linux #6783
[radarhere]

- Added signed option when saving JPEG2000 images #6709
[radarhere]

- Patch OpenJPEG to include ARM64 fix #6718
[radarhere]

- Added support for I;16 modes in putdata() #6825
[radarhere]

- Added conversion from RGBa to RGB #6708
[radarhere]

- Added DDS support for uncompressed L and LA images #6820
[radarhere, REDxEYE]

Expand Down
14 changes: 14 additions & 0 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,20 @@ def test_mct():
assert_image_similar(im, jp2, 1.0e-3)


def test_sgnd(tmp_path):
outfile = str(tmp_path / "temp.jp2")

im = Image.new("L", (1, 1))
im.save(outfile)
with Image.open(outfile) as reloaded:
assert reloaded.getpixel((0, 0)) == 0

im = Image.new("L", (1, 1))
im.save(outfile, signed=True)
with Image.open(outfile) as reloaded_signed:
assert reloaded_signed.getpixel((0, 0)) == 128


def test_rgba():
# Arrange
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
Expand Down
9 changes: 5 additions & 4 deletions Tests/test_file_tiff_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,20 +185,21 @@ def test_iptc(tmp_path):
im.save(out)


def test_writing_bytes_to_ascii(tmp_path):
im = hopper()
@pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1")))
def test_writing_other_types_to_ascii(value, expected, tmp_path):
info = TiffImagePlugin.ImageFileDirectory_v2()

tag = TiffTags.TAGS_V2[271]
assert tag.type == TiffTags.ASCII

info[271] = b"test"
info[271] = value

im = hopper()
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)

with Image.open(out) as reloaded:
assert reloaded.tag_v2[271] == "test"
assert reloaded.tag_v2[271] == expected


def test_writing_int_to_bytes(tmp_path):
Expand Down
7 changes: 7 additions & 0 deletions Tests/test_image_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ def test_rgba_p():
assert_image_similar(im, comparable, 20)


def test_rgba():
with Image.open("Tests/images/transparent.png") as im:
assert im.mode == "RGBA"

assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)


def test_trns_p(tmp_path):
im = hopper("P")
im.info["transparency"] = 0
Expand Down
5 changes: 3 additions & 2 deletions Tests/test_image_putdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ def test_mode_with_L_with_float():
assert im.getpixel((0, 0)) == 2


def test_mode_i():
@pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B"))
def test_mode_i(mode):
src = hopper("L")
data = list(src.getdata())
im = Image.new("I", src.size, 0)
im = Image.new(mode, src.size, 0)
im.putdata(data, 2, 256)

target = [2 * elt + 256 for elt in data]
Expand Down
10 changes: 7 additions & 3 deletions Tests/test_imagegrab.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ def test_grabclipboard(self):
)
p.communicate()
else:
with pytest.raises(NotImplementedError) as e:
ImageGrab.grabclipboard()
assert str(e.value) == "ImageGrab.grabclipboard() is macOS and Windows only"
if not shutil.which("wl-paste"):
with pytest.raises(
NotImplementedError,
match="wl-paste or xclip is required for"
r" ImageGrab.grabclipboard\(\) on Linux",
):
ImageGrab.grabclipboard()
return

ImageGrab.grabclipboard()
Expand Down
18 changes: 12 additions & 6 deletions docs/example/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,16 @@ class DdsImageFile(ImageFile.ImageFile):

def _open(self):
if not _accept(self.fp.read(4)):
raise SyntaxError("not a DDS file")
msg = "not a DDS file"
raise SyntaxError(msg)
(header_size,) = struct.unpack("<I", self.fp.read(4))
if header_size != 124:
raise OSError(f"Unsupported header size {repr(header_size)}")
msg = f"Unsupported header size {repr(header_size)}"
raise OSError(msg)
header_bytes = self.fp.read(header_size - 4)
if len(header_bytes) != 120:
raise OSError(f"Incomplete header: {len(header_bytes)} bytes")
msg = f"Incomplete header: {len(header_bytes)} bytes"
raise OSError(msg)
header = BytesIO(header_bytes)

flags, height, width = struct.unpack("<3I", header.read(12))
Expand All @@ -237,7 +240,8 @@ def _open(self):
elif fourcc == b"DXT5":
self.decoder = "DXT5"
else:
raise NotImplementedError(f"Unimplemented pixel format {fourcc}")
msg = f"Unimplemented pixel format {fourcc}"
raise NotImplementedError(msg)

self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]

Expand All @@ -252,7 +256,8 @@ def decode(self, buffer):
try:
self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize))
except struct.error as e:
raise OSError("Truncated DDS file") from e
msg = "Truncated DDS file"
raise OSError(msg) from e
return -1, 0


Expand All @@ -263,7 +268,8 @@ def decode(self, buffer):
try:
self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize))
except struct.error as e:
raise OSError("Truncated DDS file") from e
msg = "Truncated DDS file"
raise OSError(msg) from e
return -1, 0


Expand Down
18 changes: 18 additions & 0 deletions docs/handbook/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ Pillow also provides limited support for a few additional modes, including:
* ``BGR;24`` (24-bit reversed true colour)
* ``BGR;32`` (32-bit reversed true colour)

Premultiplied alpha is where the values for each other channel have been
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``
would convert to an RGBa pixel of ``(5, 10, 15, 127)``. The values of the R,
G and B channels are halved as a result of the half transparency in the alpha
channel.

Apart from these additional modes, Pillow doesn't yet support multichannel
images with a depth of more than 8 bits per channel.

Expand Down Expand Up @@ -111,6 +117,18 @@ the file format handler (see the chapter on :ref:`image-file-formats`). Most
handlers add properties to the :py:attr:`~PIL.Image.Image.info` attribute when
loading an image, but ignore it when saving images.

Transparency
------------

If an image does not have an alpha band, transparency may be specified in the
:py:attr:`~PIL.Image.Image.info` attribute with a "transparency" key.

Most of the time, the "transparency" value is a single integer, describing
which pixel value is transparent in a "1", "L", "I" or "P" mode image.
However, PNG images may have three values, one for each channel in an "RGB"
mode image, or can have a byte string for a "P" mode image, to specify the
alpha value for each palette entry.

Orientation
-----------

Expand Down
5 changes: 5 additions & 0 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
encoded using RLCP mode will have increasing resolutions decoded as they
arrive, and so on.

**signed**
If true, then tell the encoder to save the image as signed.

.. versionadded:: 9.4.0

**cinema_mode**
Set the encoder to produce output compliant with the digital cinema
specifications. The options here are ``"no"`` (the default),
Expand Down
3 changes: 2 additions & 1 deletion docs/handbook/writing-your-own-image-plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ true color.
elif bits == 24:
self.mode = "RGB"
else:
raise SyntaxError("unknown number of bits")
msg = "unknown number of bits"
raise SyntaxError(msg)

# data descriptor
self.tile = [("raw", (0, 0) + self.size, 128, (self.mode, 0, 1))]
Expand Down
6 changes: 6 additions & 0 deletions docs/releasenotes/9.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ removes the hidden RGB values for better compression by default in libwebp 0.5
or later. By setting this option to ``True``, the encoder will keep the hidden
RGB values.

Added ``signed`` option when saving JPEG2000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If the ``signed`` keyword argument is present and true when saving JPEG2000
images, then tell the encoder to save the image as signed.

Added IFD, Interop and LightSource ExifTags enums
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
18 changes: 8 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,15 +362,15 @@ def finalize_options(self):
self.feature.required.discard(x)
_dbg("Disabling %s", x)
if getattr(self, f"enable_{x}"):
raise ValueError(
f"Conflicting options: --enable-{x} and --disable-{x}"
)
msg = f"Conflicting options: --enable-{x} and --disable-{x}"
raise ValueError(msg)
if x == "freetype":
_dbg("--disable-freetype implies --disable-raqm")
if getattr(self, "enable_raqm"):
raise ValueError(
msg = (
"Conflicting options: --enable-raqm and --disable-freetype"
)
raise ValueError(msg)
setattr(self, "disable_raqm", True)
if getattr(self, f"enable_{x}"):
_dbg("Requiring %s", x)
Expand All @@ -381,13 +381,11 @@ def finalize_options(self):
for x in ("raqm", "fribidi"):
if getattr(self, f"vendor_{x}"):
if getattr(self, "disable_raqm"):
raise ValueError(
f"Conflicting options: --vendor-{x} and --disable-raqm"
)
msg = f"Conflicting options: --vendor-{x} and --disable-raqm"
raise ValueError(msg)
if x == "fribidi" and not getattr(self, "vendor_raqm"):
raise ValueError(
f"Conflicting options: --vendor-{x} and not --vendor-raqm"
)
msg = f"Conflicting options: --vendor-{x} and not --vendor-raqm"
raise ValueError(msg)
_dbg("Using vendored version of %s", x)
self.feature.vendor.add(x)

Expand Down
3 changes: 2 additions & 1 deletion src/PIL/BdfFontFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def __init__(self, fp):

s = fp.readline()
if s[:13] != b"STARTFONT 2.1":
raise SyntaxError("not a valid BDF file")
msg = "not a valid BDF file"
raise SyntaxError(msg)

props = {}
comments = []
Expand Down
35 changes: 18 additions & 17 deletions src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def __getattr__(name):
if name in enum.__members__:
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
return enum[name]
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
msg = f"module '{__name__}' has no attribute '{name}'"
raise AttributeError(msg)


def unpack_565(i):
Expand Down Expand Up @@ -278,7 +279,8 @@ def _open(self):
if self.magic in (b"BLP1", b"BLP2"):
decoder = self.magic.decode()
else:
raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}")
msg = f"Bad BLP magic {repr(self.magic)}"
raise BLPFormatError(msg)

self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
Expand All @@ -292,7 +294,8 @@ def decode(self, buffer):
self._read_blp_header()
self._load()
except struct.error as e:
raise OSError("Truncated BLP file") from e
msg = "Truncated BLP file"
raise OSError(msg) from e
return -1, 0

def _read_blp_header(self):
Expand Down Expand Up @@ -354,13 +357,11 @@ def _load(self):
data = self._read_bgra(palette)
self.set_as_raw(bytes(data))
else:
raise BLPFormatError(
f"Unsupported BLP encoding {repr(self._blp_encoding)}"
)
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
raise BLPFormatError(msg)
else:
raise BLPFormatError(
f"Unsupported BLP compression {repr(self._blp_encoding)}"
)
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
raise BLPFormatError(msg)

def _decode_jpeg_stream(self):
from .JpegImagePlugin import JpegImageFile
Expand Down Expand Up @@ -415,16 +416,15 @@ def _load(self):
for d in decode_dxt5(self._safe_read(linesize)):
data += d
else:
raise BLPFormatError(
f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
)
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
raise BLPFormatError(msg)
else:
raise BLPFormatError(f"Unknown BLP encoding {repr(self._blp_encoding)}")
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
raise BLPFormatError(msg)

else:
raise BLPFormatError(
f"Unknown BLP compression {repr(self._blp_compression)}"
)
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
raise BLPFormatError(msg)

self.set_as_raw(bytes(data))

Expand Down Expand Up @@ -460,7 +460,8 @@ def encode(self, bufsize):

def _save(im, fp, filename, save_all=False):
if im.mode != "P":
raise ValueError("Unsupported BLP image mode")
msg = "Unsupported BLP image mode"
raise ValueError(msg)

magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
fp.write(magic)
Expand Down

0 comments on commit b4347da

Please sign in to comment.