mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-06 21:03:18 +03:00
Merge branch 'main' into iptc
This commit is contained in:
commit
3ef7b93846
18
.github/problem-matchers/gcc.json
vendored
Normal file
18
.github/problem-matchers/gcc.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"__comment": "Based on vscode-cpptools' Extension/package.json gcc rule",
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "gcc-problem-matcher",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^\\s*(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"severity": 4,
|
||||||
|
"message": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -86,6 +86,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Register gcc problem matcher
|
||||||
|
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
|
||||||
|
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
.ci/build.sh
|
.ci/build.sh
|
||||||
|
|
27
CHANGES.rst
27
CHANGES.rst
|
@ -5,9 +5,36 @@ Changelog (Pillow)
|
||||||
10.2.0 (unreleased)
|
10.2.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Allow uncompressed TIFF images to be saved in chunks #7650
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Concatenate multiple JPEG EXIF markers #7496
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed IPTC tile tuple to match other plugins #7661
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not assign new fp attribute when exiting context manager #7566
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support arbitrary masks for uncompressed RGB DDS images #7589
|
||||||
|
[radarhere, akx]
|
||||||
|
|
||||||
|
- Support setting ROWSPERSTRIP tag #7654
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() #7662
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Optimise ``ImageColor`` using ``functools.lru_cache`` #7657
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
- Restricted environment keys for ImageMath.eval() #7655
|
- Restricted environment keys for ImageMath.eval() #7655
|
||||||
[wiredfool, radarhere]
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Optimise ``ImageMode.getmode`` using ``functools.lru_cache`` #7641
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
- Fix incorrect color blending for overlapping glyphs #7497
|
- Fix incorrect color blending for overlapping glyphs #7497
|
||||||
[ZachNagengast, nulano, radarhere]
|
[ZachNagengast, nulano, radarhere]
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
|
Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors.
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source HPND License:
|
Like PIL, Pillow is licensed under the open source HPND License:
|
||||||
|
|
||||||
|
|
BIN
Tests/images/bgr15.dds
Normal file
BIN
Tests/images/bgr15.dds
Normal file
Binary file not shown.
BIN
Tests/images/bgr15.png
Normal file
BIN
Tests/images/bgr15.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
Tests/images/multiple_exif.jpg
Normal file
BIN
Tests/images/multiple_exif.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 364 B |
Binary file not shown.
|
@ -32,6 +32,7 @@ TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SR
|
||||||
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
|
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
|
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
|
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
|
||||||
|
TEST_FILE_UNCOMPRESSED_BGR15 = "Tests/images/bgr15.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
||||||
|
|
||||||
|
|
||||||
|
@ -249,6 +250,7 @@ def test_dx10_r8g8b8a8_unorm_srgb():
|
||||||
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
|
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
|
||||||
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
|
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
|
||||||
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
|
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
|
||||||
|
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_BGR15),
|
||||||
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
|
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -341,16 +343,9 @@ def test_palette():
|
||||||
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
def test_unsupported_bitcount():
|
||||||
"test_file",
|
|
||||||
(
|
|
||||||
"Tests/images/unsupported_bitcount_rgb.dds",
|
|
||||||
"Tests/images/unsupported_bitcount_luminance.dds",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_unsupported_bitcount(test_file):
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
with Image.open(test_file):
|
with Image.open("Tests/images/unsupported_bitcount.dds"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -90,24 +90,28 @@ def test_i():
|
||||||
c = b"a"
|
c = b"a"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
ret = IptcImagePlugin.i(c)
|
with pytest.warns(DeprecationWarning):
|
||||||
|
ret = IptcImagePlugin.i(c)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert ret == 97
|
assert ret == 97
|
||||||
|
|
||||||
|
|
||||||
def test_dump():
|
def test_dump(monkeypatch):
|
||||||
# Arrange
|
# Arrange
|
||||||
c = b"abc"
|
c = b"abc"
|
||||||
# Temporarily redirect stdout
|
# Temporarily redirect stdout
|
||||||
old_stdout = sys.stdout
|
mystdout = StringIO()
|
||||||
sys.stdout = mystdout = StringIO()
|
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
IptcImagePlugin.dump(c)
|
with pytest.warns(DeprecationWarning):
|
||||||
|
IptcImagePlugin.dump(c)
|
||||||
# Reset stdout
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mystdout.getvalue() == "61 62 63 \n"
|
assert mystdout.getvalue() == "61 62 63 \n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pad_deprecation():
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert IptcImagePlugin.PAD == b"\0\0\0\0"
|
||||||
|
|
|
@ -840,6 +840,10 @@ class TestFileJpeg:
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
assert im._getexif()[306] == "2017:03:13 23:03:09"
|
assert im._getexif()[306] == "2017:03:13 23:03:09"
|
||||||
|
|
||||||
|
def test_multiple_exif(self):
|
||||||
|
with Image.open("Tests/images/multiple_exif.jpg") as im:
|
||||||
|
assert im.info["exif"] == b"Exif\x00\x00firstsecond"
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -484,13 +484,13 @@ class TestFileTiff:
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
|
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
|
||||||
exif = im.getexif()
|
exif = im.getexif()
|
||||||
exif[256] = 100
|
exif[264] = 100
|
||||||
|
|
||||||
im.save(outfile, exif=exif)
|
im.save(outfile, exif=exif)
|
||||||
|
|
||||||
with Image.open(outfile) as im:
|
with Image.open(outfile) as im:
|
||||||
exif = im.getexif()
|
exif = im.getexif()
|
||||||
assert exif[256] == 100
|
assert exif[264] == 100
|
||||||
|
|
||||||
def test_reload_exif_after_seek(self):
|
def test_reload_exif_after_seek(self):
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
|
@ -612,6 +612,14 @@ class TestFileTiff:
|
||||||
|
|
||||||
assert_image_equal_tofile(im, tmpfile)
|
assert_image_equal_tofile(im, tmpfile)
|
||||||
|
|
||||||
|
def test_rowsperstrip(self, tmp_path):
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
im = hopper()
|
||||||
|
im.save(outfile, tiffinfo={278: 256})
|
||||||
|
|
||||||
|
with Image.open(outfile) as im:
|
||||||
|
assert im.tag_v2[278] == 256
|
||||||
|
|
||||||
def test_strip_raw(self):
|
def test_strip_raw(self):
|
||||||
infile = "Tests/images/tiff_strip_raw.tif"
|
infile = "Tests/images/tiff_strip_raw.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
|
@ -773,6 +781,27 @@ class TestFileTiff:
|
||||||
4001,
|
4001,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def test_tiff_chunks(self, tmp_path):
|
||||||
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
with open(tmpfile, "wb") as fp:
|
||||||
|
for y in range(0, 128, 32):
|
||||||
|
chunk = im.crop((0, y, 128, y + 32))
|
||||||
|
if y == 0:
|
||||||
|
chunk.save(
|
||||||
|
fp,
|
||||||
|
"TIFF",
|
||||||
|
tiffinfo={
|
||||||
|
TiffImagePlugin.IMAGEWIDTH: 128,
|
||||||
|
TiffImagePlugin.IMAGELENGTH: 128,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fp.write(chunk.tobytes())
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, tmpfile)
|
||||||
|
|
||||||
def test_close_on_load_exclusive(self, tmp_path):
|
def test_close_on_load_exclusive(self, tmp_path):
|
||||||
# similar to test_fd_leak, but runs on unixlike os
|
# similar to test_fd_leak, but runs on unixlike os
|
||||||
tmpfile = str(tmp_path / "temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
|
@ -123,6 +123,7 @@ def test_write_metadata(tmp_path):
|
||||||
"""Test metadata writing through the python code"""
|
"""Test metadata writing through the python code"""
|
||||||
with Image.open("Tests/images/hopper.tif") as img:
|
with Image.open("Tests/images/hopper.tif") as img:
|
||||||
f = str(tmp_path / "temp.tiff")
|
f = str(tmp_path / "temp.tiff")
|
||||||
|
del img.tag[278]
|
||||||
img.save(f, tiffinfo=img.tag)
|
img.save(f, tiffinfo=img.tag)
|
||||||
|
|
||||||
original = img.tag_v2.named()
|
original = img.tag_v2.named()
|
||||||
|
@ -159,9 +160,11 @@ def test_change_stripbytecounts_tag_type(tmp_path):
|
||||||
out = str(tmp_path / "temp.tiff")
|
out = str(tmp_path / "temp.tiff")
|
||||||
with Image.open("Tests/images/hopper.tif") as im:
|
with Image.open("Tests/images/hopper.tif") as im:
|
||||||
info = im.tag_v2
|
info = im.tag_v2
|
||||||
|
del info[278]
|
||||||
|
|
||||||
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
|
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
|
||||||
im = im.resize((500, 500))
|
im = im.resize((500, 500))
|
||||||
|
info[TiffImagePlugin.IMAGEWIDTH] = im.width
|
||||||
|
|
||||||
# STRIPBYTECOUNTS can be a SHORT or a LONG
|
# STRIPBYTECOUNTS can be a SHORT or a LONG
|
||||||
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
|
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
|
||||||
|
|
|
@ -1016,6 +1016,11 @@ class TestImage:
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
assert str(e) == "buffer overrun when reading image file"
|
assert str(e) == "buffer overrun when reading image file"
|
||||||
|
|
||||||
|
def test_exit_fp(self):
|
||||||
|
with Image.new("L", (1, 1)) as im:
|
||||||
|
pass
|
||||||
|
assert not hasattr(im, "fp")
|
||||||
|
|
||||||
def test_close_graceful(self, caplog):
|
def test_close_graceful(self, caplog):
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
copy = im.copy()
|
copy = im.copy()
|
||||||
|
|
|
@ -1058,6 +1058,8 @@ def test_too_many_characters(font):
|
||||||
imagefont.getlength("A" * 1_000_001)
|
imagefont.getlength("A" * 1_000_001)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
imagefont.getbbox("A" * 1_000_001)
|
imagefont.getbbox("A" * 1_000_001)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
imagefont.getmask("A" * 1_000_001)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors
|
Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source PIL
|
Like PIL, Pillow is licensed under the open source PIL
|
||||||
Software License:
|
Software License:
|
||||||
|
|
|
@ -54,7 +54,7 @@ master_doc = "index"
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = "Pillow (PIL Fork)"
|
project = "Pillow (PIL Fork)"
|
||||||
copyright = (
|
copyright = (
|
||||||
"1995-2011 Fredrik Lundh, 2010-2023 Jeffrey A. Clark (Alex) and contributors"
|
"1995-2011 Fredrik Lundh, 2010-2024 Jeffrey A. Clark (Alex) and contributors"
|
||||||
)
|
)
|
||||||
author = "Fredrik Lundh, Jeffrey A. Clark (Alex), contributors"
|
author = "Fredrik Lundh, Jeffrey A. Clark (Alex), contributors"
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,17 @@ ImageFile.raise_oserror
|
||||||
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
||||||
automatically.
|
automatically.
|
||||||
|
|
||||||
|
IptcImageFile helper functions
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 10.2.0
|
||||||
|
|
||||||
|
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
||||||
|
``IptcImageFile.PAD`` have been deprecated and will be removed in Pillow
|
||||||
|
12.0.0 (2025-10-15). These are undocumented helper functions intended
|
||||||
|
for internal use, so there is no replacement. They can each be replaced
|
||||||
|
by a single line of code using builtin functions in Python.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,14 @@ ImageFile.raise_oserror
|
||||||
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
||||||
automatically.
|
automatically.
|
||||||
|
|
||||||
TODO
|
IptcImageFile helper functions
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
||||||
|
``IptcImageFile.PAD`` have been deprecated and will be removed in Pillow
|
||||||
|
12.0.0 (2025-10-15). These are undocumented helper functions intended
|
||||||
|
for internal use, so there is no replacement. They can each be replaced
|
||||||
|
by a single line of code using builtin functions in Python.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
@ -62,8 +66,19 @@ output only the quantization and Huffman tables for the image.
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
Restricted environment keys for ImageMath.eval
|
ImageFont.getmask: Applied ImageFont.MAX_STRING_LENGTH
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To protect against potential DOS attacks when using arbitrary strings as text input,
|
||||||
|
Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into
|
||||||
|
:py:meth:`PIL.ImageFont.ImageFont.getmask` is over a certain limit,
|
||||||
|
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
|
||||||
|
|
||||||
|
This threshold can be changed by setting :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It
|
||||||
|
can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||||
|
|
||||||
|
ImageMath.eval: Restricted environment keys
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
:cve:`2023-50447`: If an attacker has control over the keys passed to the
|
:cve:`2023-50447`: If an attacker has control over the keys passed to the
|
||||||
``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute
|
``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute
|
||||||
|
|
|
@ -16,15 +16,16 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
from typing import IO, AnyStr, Generic, Literal
|
||||||
|
|
||||||
|
|
||||||
class ContainerIO:
|
class ContainerIO(Generic[AnyStr]):
|
||||||
"""
|
"""
|
||||||
A file object that provides read access to a part of an existing
|
A file object that provides read access to a part of an existing
|
||||||
file (for example a TAR file).
|
file (for example a TAR file).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, file, offset, length) -> None:
|
def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
|
||||||
"""
|
"""
|
||||||
Create file object.
|
Create file object.
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ class ContainerIO:
|
||||||
:param offset: Start of region, in bytes.
|
:param offset: Start of region, in bytes.
|
||||||
:param length: Size of region, in bytes.
|
:param length: Size of region, in bytes.
|
||||||
"""
|
"""
|
||||||
self.fh = file
|
self.fh: IO[AnyStr] = file
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.length = length
|
self.length = length
|
||||||
|
@ -41,10 +42,10 @@ class ContainerIO:
|
||||||
##
|
##
|
||||||
# Always false.
|
# Always false.
|
||||||
|
|
||||||
def isatty(self):
|
def isatty(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def seek(self, offset, mode=io.SEEK_SET):
|
def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
|
||||||
"""
|
"""
|
||||||
Move file pointer.
|
Move file pointer.
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ class ContainerIO:
|
||||||
self.pos = max(0, min(self.pos, self.length))
|
self.pos = max(0, min(self.pos, self.length))
|
||||||
self.fh.seek(self.offset + self.pos)
|
self.fh.seek(self.offset + self.pos)
|
||||||
|
|
||||||
def tell(self):
|
def tell(self) -> int:
|
||||||
"""
|
"""
|
||||||
Get current file pointer.
|
Get current file pointer.
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ class ContainerIO:
|
||||||
"""
|
"""
|
||||||
return self.pos
|
return self.pos
|
||||||
|
|
||||||
def read(self, n=0):
|
def read(self, n: int = 0) -> AnyStr:
|
||||||
"""
|
"""
|
||||||
Read data.
|
Read data.
|
||||||
|
|
||||||
|
@ -84,17 +85,17 @@ class ContainerIO:
|
||||||
else:
|
else:
|
||||||
n = self.length - self.pos
|
n = self.length - self.pos
|
||||||
if not n: # EOF
|
if not n: # EOF
|
||||||
return b"" if "b" in self.fh.mode else ""
|
return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
|
||||||
self.pos = self.pos + n
|
self.pos = self.pos + n
|
||||||
return self.fh.read(n)
|
return self.fh.read(n)
|
||||||
|
|
||||||
def readline(self):
|
def readline(self) -> AnyStr:
|
||||||
"""
|
"""
|
||||||
Read a line of text.
|
Read a line of text.
|
||||||
|
|
||||||
:returns: An 8-bit string.
|
:returns: An 8-bit string.
|
||||||
"""
|
"""
|
||||||
s = b"" if "b" in self.fh.mode else ""
|
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
|
||||||
newline_character = b"\n" if "b" in self.fh.mode else "\n"
|
newline_character = b"\n" if "b" in self.fh.mode else "\n"
|
||||||
while True:
|
while True:
|
||||||
c = self.read(1)
|
c = self.read(1)
|
||||||
|
@ -105,7 +106,7 @@ class ContainerIO:
|
||||||
break
|
break
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def readlines(self):
|
def readlines(self) -> list[AnyStr]:
|
||||||
"""
|
"""
|
||||||
Read multiple lines of text.
|
Read multiple lines of text.
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from enum import IntEnum, IntFlag
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
from ._binary import o8
|
||||||
from ._binary import o32le as o32
|
from ._binary import o32le as o32
|
||||||
|
|
||||||
# Magic ("DDS ")
|
# Magic ("DDS ")
|
||||||
|
@ -341,6 +342,7 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||||
self._size = (width, height)
|
self._size = (width, height)
|
||||||
|
extents = (0, 0) + self.size
|
||||||
|
|
||||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||||
struct.unpack("<11I", header.read(44)) # reserved
|
struct.unpack("<11I", header.read(44)) # reserved
|
||||||
|
@ -351,22 +353,16 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
rawmode = None
|
rawmode = None
|
||||||
if pfflags & DDPF.RGB:
|
if pfflags & DDPF.RGB:
|
||||||
# Texture contains uncompressed RGB data
|
# Texture contains uncompressed RGB data
|
||||||
masks = struct.unpack("<4I", header.read(16))
|
if pfflags & DDPF.ALPHAPIXELS:
|
||||||
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
|
|
||||||
if bitcount == 24:
|
|
||||||
self._mode = "RGB"
|
|
||||||
rawmode = masks[0x000000FF] + masks[0x0000FF00] + masks[0x00FF0000]
|
|
||||||
elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS:
|
|
||||||
self._mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
rawmode = (
|
mask_count = 4
|
||||||
masks[0x000000FF]
|
|
||||||
+ masks[0x0000FF00]
|
|
||||||
+ masks[0x00FF0000]
|
|
||||||
+ masks[0xFF000000]
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported bitcount {bitcount} for {pfflags}"
|
self._mode = "RGB"
|
||||||
raise OSError(msg)
|
mask_count = 3
|
||||||
|
|
||||||
|
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
|
||||||
|
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
|
||||||
|
return
|
||||||
elif pfflags & DDPF.LUMINANCE:
|
elif pfflags & DDPF.LUMINANCE:
|
||||||
if bitcount == 8:
|
if bitcount == 8:
|
||||||
self._mode = "L"
|
self._mode = "L"
|
||||||
|
@ -464,7 +460,6 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
msg = f"Unknown pixel format flags {pfflags}"
|
msg = f"Unknown pixel format flags {pfflags}"
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
extents = (0, 0) + self.size
|
|
||||||
if n:
|
if n:
|
||||||
self.tile = [
|
self.tile = [
|
||||||
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
|
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
|
||||||
|
@ -476,6 +471,39 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DdsRgbDecoder(ImageFile.PyDecoder):
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
bitcount, masks = self.args
|
||||||
|
|
||||||
|
# Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
|
||||||
|
# Calculate how many zeros each mask is padded with
|
||||||
|
mask_offsets = []
|
||||||
|
# And the maximum value of each channel without the padding
|
||||||
|
mask_totals = []
|
||||||
|
for mask in masks:
|
||||||
|
offset = 0
|
||||||
|
if mask != 0:
|
||||||
|
while mask >> (offset + 1) << (offset + 1) == mask:
|
||||||
|
offset += 1
|
||||||
|
mask_offsets.append(offset)
|
||||||
|
mask_totals.append(mask >> offset)
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
bytecount = bitcount // 8
|
||||||
|
while len(data) < self.state.xsize * self.state.ysize * len(masks):
|
||||||
|
value = int.from_bytes(self.fd.read(bytecount), "little")
|
||||||
|
for i, mask in enumerate(masks):
|
||||||
|
masked_value = value & mask
|
||||||
|
# Remove the zero padding, and scale it to 8 bits
|
||||||
|
data += o8(
|
||||||
|
int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
|
||||||
|
)
|
||||||
|
self.set_as_raw(bytes(data))
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
if im.mode not in ("RGB", "RGBA", "L", "LA"):
|
if im.mode not in ("RGB", "RGBA", "L", "LA"):
|
||||||
msg = f"cannot write mode {im.mode} as DDS"
|
msg = f"cannot write mode {im.mode} as DDS"
|
||||||
|
@ -533,5 +561,6 @@ def _accept(prefix):
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
|
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
|
||||||
|
Image.register_decoder("dds_rgb", DdsRgbDecoder)
|
||||||
Image.register_save(DdsImageFile.format, _save)
|
Image.register_save(DdsImageFile.format, _save)
|
||||||
Image.register_extension(DdsImageFile.format, ".dds")
|
Image.register_extension(DdsImageFile.format, ".dds")
|
||||||
|
|
|
@ -530,15 +530,19 @@ class Image:
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def _close_fp(self):
|
||||||
|
if getattr(self, "_fp", False):
|
||||||
|
if self._fp != self.fp:
|
||||||
|
self._fp.close()
|
||||||
|
self._fp = DeferredError(ValueError("Operation on closed image"))
|
||||||
|
if self.fp:
|
||||||
|
self.fp.close()
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False):
|
if hasattr(self, "fp"):
|
||||||
if getattr(self, "_fp", False):
|
if getattr(self, "_exclusive_fp", False):
|
||||||
if self._fp != self.fp:
|
self._close_fp()
|
||||||
self._fp.close()
|
self.fp = None
|
||||||
self._fp = DeferredError(ValueError("Operation on closed image"))
|
|
||||||
if self.fp:
|
|
||||||
self.fp.close()
|
|
||||||
self.fp = None
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
|
@ -554,12 +558,7 @@ class Image:
|
||||||
"""
|
"""
|
||||||
if hasattr(self, "fp"):
|
if hasattr(self, "fp"):
|
||||||
try:
|
try:
|
||||||
if getattr(self, "_fp", False):
|
self._close_fp()
|
||||||
if self._fp != self.fp:
|
|
||||||
self._fp.close()
|
|
||||||
self._fp = DeferredError(ValueError("Operation on closed image"))
|
|
||||||
if self.fp:
|
|
||||||
self.fp.close()
|
|
||||||
self.fp = None
|
self.fp = None
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
logger.debug("Error closing: %s", msg)
|
logger.debug("Error closing: %s", msg)
|
||||||
|
@ -3191,7 +3190,7 @@ def _decompression_bomb_check(size):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def open(fp, mode="r", formats=None):
|
def open(fp, mode="r", formats=None) -> Image:
|
||||||
"""
|
"""
|
||||||
Opens and identifies the given image file.
|
Opens and identifies the given image file.
|
||||||
|
|
||||||
|
@ -3416,7 +3415,7 @@ def merge(mode, bands):
|
||||||
# Plugin registry
|
# Plugin registry
|
||||||
|
|
||||||
|
|
||||||
def register_open(id, factory, accept=None):
|
def register_open(id, factory, accept=None) -> None:
|
||||||
"""
|
"""
|
||||||
Register an image file plugin. This function should not be used
|
Register an image file plugin. This function should not be used
|
||||||
in application code.
|
in application code.
|
||||||
|
@ -3470,7 +3469,7 @@ def register_save_all(id, driver):
|
||||||
SAVE_ALL[id.upper()] = driver
|
SAVE_ALL[id.upper()] = driver
|
||||||
|
|
||||||
|
|
||||||
def register_extension(id, extension):
|
def register_extension(id, extension) -> None:
|
||||||
"""
|
"""
|
||||||
Registers an image extension. This function should not be
|
Registers an image extension. This function should not be
|
||||||
used in application code.
|
used in application code.
|
||||||
|
|
|
@ -149,6 +149,7 @@ class ImageFont:
|
||||||
:return: An internal PIL storage memory instance as defined by the
|
:return: An internal PIL storage memory instance as defined by the
|
||||||
:py:mod:`PIL.Image.core` interface module.
|
:py:mod:`PIL.Image.core` interface module.
|
||||||
"""
|
"""
|
||||||
|
_string_length_check(text)
|
||||||
return self.font.getmask(text, mode)
|
return self.font.getmask(text, mode)
|
||||||
|
|
||||||
def getbbox(self, text, *args, **kwargs):
|
def getbbox(self, text, *args, **kwargs):
|
||||||
|
|
|
@ -17,28 +17,47 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i8, o8
|
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
COMPRESSION = {1: "raw", 5: "jpeg"}
|
COMPRESSION = {1: "raw", 5: "jpeg"}
|
||||||
|
|
||||||
PAD = o8(0) * 4
|
|
||||||
|
def __getattr__(name: str) -> bytes:
|
||||||
|
if name == "PAD":
|
||||||
|
deprecate("IptcImagePlugin.PAD", 12)
|
||||||
|
return b"\0\0\0\0"
|
||||||
|
msg = f"module '{__name__}' has no attribute '{name}'"
|
||||||
|
raise AttributeError(msg)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
||||||
|
|
||||||
def i(c):
|
def _i(c: bytes) -> int:
|
||||||
return i32((PAD + c)[-4:])
|
return i32((b"\0\0\0\0" + c)[-4:])
|
||||||
|
|
||||||
|
|
||||||
def dump(c):
|
def _i8(c: int | bytes) -> int:
|
||||||
|
return c if isinstance(c, int) else c[0]
|
||||||
|
|
||||||
|
|
||||||
|
def i(c: bytes) -> int:
|
||||||
|
""".. deprecated:: 10.2.0"""
|
||||||
|
deprecate("IptcImagePlugin.i", 12)
|
||||||
|
return _i(c)
|
||||||
|
|
||||||
|
|
||||||
|
def dump(c: Sequence[int | bytes]) -> None:
|
||||||
|
""".. deprecated:: 10.2.0"""
|
||||||
|
deprecate("IptcImagePlugin.dump", 12)
|
||||||
for i in c:
|
for i in c:
|
||||||
print("%02x" % i8(i), end=" ")
|
print("%02x" % _i8(i), end=" ")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,10 +70,10 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
format = "IPTC"
|
format = "IPTC"
|
||||||
format_description = "IPTC/NAA"
|
format_description = "IPTC/NAA"
|
||||||
|
|
||||||
def getint(self, key):
|
def getint(self, key: tuple[int, int]) -> int:
|
||||||
return i(self.info[key])
|
return _i(self.info[key])
|
||||||
|
|
||||||
def field(self):
|
def field(self) -> tuple[tuple[int, int] | None, int]:
|
||||||
#
|
#
|
||||||
# get a IPTC field header
|
# get a IPTC field header
|
||||||
s = self.fp.read(5)
|
s = self.fp.read(5)
|
||||||
|
@ -76,13 +95,13 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
elif size == 128:
|
elif size == 128:
|
||||||
size = 0
|
size = 0
|
||||||
elif size > 128:
|
elif size > 128:
|
||||||
size = i(self.fp.read(size - 128))
|
size = _i(self.fp.read(size - 128))
|
||||||
else:
|
else:
|
||||||
size = i16(s, 3)
|
size = i16(s, 3)
|
||||||
|
|
||||||
return tag, size
|
return tag, size
|
||||||
|
|
||||||
def _open(self):
|
def _open(self) -> None:
|
||||||
# load descriptive fields
|
# load descriptive fields
|
||||||
while True:
|
while True:
|
||||||
offset = self.fp.tell()
|
offset = self.fp.tell()
|
||||||
|
@ -102,10 +121,10 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
self.info[tag] = tagdata
|
self.info[tag] = tagdata
|
||||||
|
|
||||||
# mode
|
# mode
|
||||||
layers = i8(self.info[(3, 60)][0])
|
layers = self.info[(3, 60)][0]
|
||||||
component = i8(self.info[(3, 60)][1])
|
component = self.info[(3, 60)][1]
|
||||||
if (3, 65) in self.info:
|
if (3, 65) in self.info:
|
||||||
id = i8(self.info[(3, 65)][0]) - 1
|
id = self.info[(3, 65)][0] - 1
|
||||||
else:
|
else:
|
||||||
id = 0
|
id = 0
|
||||||
if layers == 1 and not component:
|
if layers == 1 and not component:
|
||||||
|
|
|
@ -87,10 +87,12 @@ def APP(self, marker):
|
||||||
self.info["dpi"] = jfif_density
|
self.info["dpi"] = jfif_density
|
||||||
self.info["jfif_unit"] = jfif_unit
|
self.info["jfif_unit"] = jfif_unit
|
||||||
self.info["jfif_density"] = jfif_density
|
self.info["jfif_density"] = jfif_density
|
||||||
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
|
elif marker == 0xFFE1 and s[:6] == b"Exif\0\0":
|
||||||
if "exif" not in self.info:
|
# extract EXIF information
|
||||||
# extract EXIF information (incomplete)
|
if "exif" in self.info:
|
||||||
self.info["exif"] = s # FIXME: value will change
|
self.info["exif"] += s[6:]
|
||||||
|
else:
|
||||||
|
self.info["exif"] = s
|
||||||
self._exif_offset = self.fp.tell() - n + 6
|
self._exif_offset = self.fp.tell() - n + 6
|
||||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||||
# extract FlashPix information (incomplete)
|
# extract FlashPix information (incomplete)
|
||||||
|
|
|
@ -21,7 +21,7 @@ from types import TracebackType
|
||||||
from . import ContainerIO
|
from . import ContainerIO
|
||||||
|
|
||||||
|
|
||||||
class TarIO(ContainerIO.ContainerIO):
|
class TarIO(ContainerIO.ContainerIO[bytes]):
|
||||||
"""A file object that provides read access to a given member of a TAR file."""
|
"""A file object that provides read access to a given member of a TAR file."""
|
||||||
|
|
||||||
def __init__(self, tarfile: str, file: str) -> None:
|
def __init__(self, tarfile: str, file: str) -> None:
|
||||||
|
|
|
@ -1704,25 +1704,27 @@ def _save(im, fp, filename):
|
||||||
colormap += [0] * (256 - colors)
|
colormap += [0] * (256 - colors)
|
||||||
ifd[COLORMAP] = colormap
|
ifd[COLORMAP] = colormap
|
||||||
# data orientation
|
# data orientation
|
||||||
stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
|
w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH]
|
||||||
# aim for given strip size (64 KB by default) when using libtiff writer
|
stride = len(bits) * ((w * bits[0] + 7) // 8)
|
||||||
if libtiff:
|
if ROWSPERSTRIP not in ifd:
|
||||||
im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
|
# aim for given strip size (64 KB by default) when using libtiff writer
|
||||||
rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, im.size[1])
|
if libtiff:
|
||||||
# JPEG encoder expects multiple of 8 rows
|
im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
|
||||||
if compression == "jpeg":
|
rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
|
||||||
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
|
# JPEG encoder expects multiple of 8 rows
|
||||||
else:
|
if compression == "jpeg":
|
||||||
rows_per_strip = im.size[1]
|
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h)
|
||||||
if rows_per_strip == 0:
|
else:
|
||||||
rows_per_strip = 1
|
rows_per_strip = h
|
||||||
strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip
|
if rows_per_strip == 0:
|
||||||
strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
|
rows_per_strip = 1
|
||||||
ifd[ROWSPERSTRIP] = rows_per_strip
|
ifd[ROWSPERSTRIP] = rows_per_strip
|
||||||
|
strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP]
|
||||||
|
strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
|
||||||
if strip_byte_counts >= 2**16:
|
if strip_byte_counts >= 2**16:
|
||||||
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
|
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
|
||||||
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
|
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
|
||||||
stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
|
stride * h - strip_byte_counts * (strips_per_image - 1),
|
||||||
)
|
)
|
||||||
ifd[STRIPOFFSETS] = tuple(
|
ifd[STRIPOFFSETS] = tuple(
|
||||||
range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
|
range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
|
||||||
|
|
|
@ -18,16 +18,16 @@ from __future__ import annotations
|
||||||
from struct import pack, unpack_from
|
from struct import pack, unpack_from
|
||||||
|
|
||||||
|
|
||||||
def i8(c) -> int:
|
def i8(c: bytes) -> int:
|
||||||
return c if c.__class__ is int else c[0]
|
return c[0]
|
||||||
|
|
||||||
|
|
||||||
def o8(i):
|
def o8(i: int) -> bytes:
|
||||||
return bytes((i & 255,))
|
return bytes((i & 255,))
|
||||||
|
|
||||||
|
|
||||||
# Input, le = little endian, be = big endian
|
# Input, le = little endian, be = big endian
|
||||||
def i16le(c, o=0):
|
def i16le(c: bytes, o: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Converts a 2-bytes (16 bits) string to an unsigned integer.
|
Converts a 2-bytes (16 bits) string to an unsigned integer.
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ def i16le(c, o=0):
|
||||||
return unpack_from("<H", c, o)[0]
|
return unpack_from("<H", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def si16le(c, o=0):
|
def si16le(c: bytes, o: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Converts a 2-bytes (16 bits) string to a signed integer.
|
Converts a 2-bytes (16 bits) string to a signed integer.
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ def si16le(c, o=0):
|
||||||
return unpack_from("<h", c, o)[0]
|
return unpack_from("<h", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def si16be(c, o=0):
|
def si16be(c: bytes, o: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Converts a 2-bytes (16 bits) string to a signed integer, big endian.
|
Converts a 2-bytes (16 bits) string to a signed integer, big endian.
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ def si16be(c, o=0):
|
||||||
return unpack_from(">h", c, o)[0]
|
return unpack_from(">h", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def i32le(c, o=0) -> int:
|
def i32le(c: bytes, o: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Converts a 4-bytes (32 bits) string to an unsigned integer.
|
Converts a 4-bytes (32 bits) string to an unsigned integer.
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ def i32le(c, o=0) -> int:
|
||||||
return unpack_from("<I", c, o)[0]
|
return unpack_from("<I", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def si32le(c, o=0):
|
def si32le(c: bytes, o: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Converts a 4-bytes (32 bits) string to a signed integer.
|
Converts a 4-bytes (32 bits) string to a signed integer.
|
||||||
|
|
||||||
|
@ -77,26 +77,26 @@ def si32le(c, o=0):
|
||||||
return unpack_from("<i", c, o)[0]
|
return unpack_from("<i", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def i16be(c, o=0):
|
def i16be(c: bytes, o: int = 0) -> int:
|
||||||
return unpack_from(">H", c, o)[0]
|
return unpack_from(">H", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def i32be(c, o=0):
|
def i32be(c: bytes, o: int = 0) -> int:
|
||||||
return unpack_from(">I", c, o)[0]
|
return unpack_from(">I", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
# Output, le = little endian, be = big endian
|
# Output, le = little endian, be = big endian
|
||||||
def o16le(i):
|
def o16le(i: int) -> bytes:
|
||||||
return pack("<H", i)
|
return pack("<H", i)
|
||||||
|
|
||||||
|
|
||||||
def o32le(i):
|
def o32le(i: int) -> bytes:
|
||||||
return pack("<I", i)
|
return pack("<I", i)
|
||||||
|
|
||||||
|
|
||||||
def o16be(i) -> bytes:
|
def o16be(i: int) -> bytes:
|
||||||
return pack(">H", i)
|
return pack(">H", i)
|
||||||
|
|
||||||
|
|
||||||
def o32be(i):
|
def o32be(i: int) -> bytes:
|
||||||
return pack(">I", i)
|
return pack(">I", i)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user