2023-12-21 14:13:31 +03:00
|
|
|
from __future__ import annotations
|
2024-01-20 14:23:03 +03:00
|
|
|
|
2014-01-23 08:40:37 +04:00
|
|
|
import io
|
2024-01-31 12:12:58 +03:00
|
|
|
from pathlib import Path
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2020-02-17 11:42:33 +03:00
|
|
|
import pytest
|
2020-08-07 13:28:33 +03:00
|
|
|
|
2019-07-06 23:40:53 +03:00
|
|
|
from PIL import BmpImagePlugin, Image
|
|
|
|
|
2022-03-09 14:35:48 +03:00
|
|
|
from .helper import (
|
|
|
|
assert_image_equal,
|
|
|
|
assert_image_equal_tofile,
|
|
|
|
assert_image_similar_tofile,
|
|
|
|
hopper,
|
|
|
|
)
|
2019-07-06 23:40:53 +03:00
|
|
|
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_sanity(tmp_path: Path) -> None:
|
2024-02-17 07:00:38 +03:00
|
|
|
def roundtrip(im: Image.Image) -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
outfile = str(tmp_path / "temp.bmp")
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2019-06-13 18:53:42 +03:00
|
|
|
im.save(outfile, "BMP")
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(outfile) as reloaded:
|
|
|
|
reloaded.load()
|
2020-02-17 11:42:33 +03:00
|
|
|
assert im.mode == reloaded.mode
|
|
|
|
assert im.size == reloaded.size
|
|
|
|
assert reloaded.format == "BMP"
|
|
|
|
assert reloaded.get_format_mimetype() == "image/bmp"
|
|
|
|
|
|
|
|
roundtrip(hopper())
|
|
|
|
|
|
|
|
roundtrip(hopper("1"))
|
|
|
|
roundtrip(hopper("L"))
|
|
|
|
roundtrip(hopper("P"))
|
|
|
|
roundtrip(hopper("RGB"))
|
|
|
|
|
2019-03-30 07:03:57 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_invalid_file() -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
|
|
|
with pytest.raises(SyntaxError):
|
|
|
|
BmpImagePlugin.BmpImageFile(fp)
|
2019-03-06 05:59:07 +03:00
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_fallback_if_mmap_errors() -> None:
|
2022-08-17 14:13:09 +03:00
|
|
|
# This image has been truncated,
|
|
|
|
# so that the buffer is not large enough when using mmap
|
|
|
|
with Image.open("Tests/images/mmap_error.bmp") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp")
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_to_bytes() -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
output = io.BytesIO()
|
|
|
|
im = hopper()
|
|
|
|
im.save(output, "BMP")
|
2019-03-06 05:59:07 +03:00
|
|
|
|
2020-02-17 11:42:33 +03:00
|
|
|
output.seek(0)
|
|
|
|
with Image.open(output) as reloaded:
|
|
|
|
assert im.mode == reloaded.mode
|
|
|
|
assert im.size == reloaded.size
|
|
|
|
assert reloaded.format == "BMP"
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_small_palette(tmp_path: Path) -> None:
|
2022-09-13 02:10:03 +03:00
|
|
|
im = Image.new("P", (1, 1))
|
|
|
|
colors = [0, 0, 0, 125, 125, 125, 255, 255, 255]
|
|
|
|
im.putpalette(colors)
|
|
|
|
|
|
|
|
out = str(tmp_path / "temp.bmp")
|
|
|
|
im.save(out)
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
assert reloaded.getpalette() == colors
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_too_large(tmp_path: Path) -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
outfile = str(tmp_path / "temp.bmp")
|
|
|
|
with Image.new("RGB", (1, 1)) as im:
|
|
|
|
im._size = (37838, 37838)
|
|
|
|
with pytest.raises(ValueError):
|
2019-11-25 23:03:23 +03:00
|
|
|
im.save(outfile)
|
2019-03-06 05:59:07 +03:00
|
|
|
|
2019-03-08 11:26:01 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_dpi() -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
dpi = (72, 72)
|
|
|
|
|
|
|
|
output = io.BytesIO()
|
|
|
|
with hopper() as im:
|
|
|
|
im.save(output, "BMP", dpi=dpi)
|
|
|
|
|
|
|
|
output.seek(0)
|
|
|
|
with Image.open(output) as reloaded:
|
2021-05-07 13:50:18 +03:00
|
|
|
assert reloaded.info["dpi"] == (72.008961115161, 72.008961115161)
|
2020-02-17 11:42:33 +03:00
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_bmp_with_dpi(tmp_path: Path) -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
# Test for #1301
|
|
|
|
# Arrange
|
|
|
|
outfile = str(tmp_path / "temp.jpg")
|
|
|
|
with Image.open("Tests/images/hopper.bmp") as im:
|
2021-05-07 13:50:18 +03:00
|
|
|
assert im.info["dpi"] == (95.98654816726399, 95.98654816726399)
|
2020-02-17 11:42:33 +03:00
|
|
|
|
|
|
|
# Act
|
|
|
|
im.save(outfile, "JPEG", dpi=im.info["dpi"])
|
|
|
|
|
|
|
|
# Assert
|
|
|
|
with Image.open(outfile) as reloaded:
|
|
|
|
reloaded.load()
|
2021-05-07 13:50:18 +03:00
|
|
|
assert reloaded.info["dpi"] == (96, 96)
|
|
|
|
assert reloaded.size == im.size
|
2020-02-17 11:42:33 +03:00
|
|
|
assert reloaded.format == "JPEG"
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_float_dpi(tmp_path: Path) -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
outfile = str(tmp_path / "temp.bmp")
|
|
|
|
with Image.open("Tests/images/hopper.bmp") as im:
|
2021-05-07 13:50:18 +03:00
|
|
|
im.save(outfile, dpi=(72.21216100543306, 72.21216100543306))
|
2020-02-17 11:42:33 +03:00
|
|
|
with Image.open(outfile) as reloaded:
|
2021-05-07 13:50:18 +03:00
|
|
|
assert reloaded.info["dpi"] == (72.21216100543306, 72.21216100543306)
|
2020-02-17 11:42:33 +03:00
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_load_dib() -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
# test for #1293, Imagegrab returning Unsupported Bitfields Format
|
|
|
|
with Image.open("Tests/images/clipboard.dib") as im:
|
|
|
|
assert im.format == "DIB"
|
|
|
|
assert im.get_format_mimetype() == "image/bmp"
|
|
|
|
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/clipboard_target.png")
|
2020-02-17 11:42:33 +03:00
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_dib(tmp_path: Path) -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
outfile = str(tmp_path / "temp.dib")
|
|
|
|
|
|
|
|
with Image.open("Tests/images/clipboard.dib") as im:
|
|
|
|
im.save(outfile)
|
|
|
|
|
|
|
|
with Image.open(outfile) as reloaded:
|
|
|
|
assert reloaded.format == "DIB"
|
|
|
|
assert reloaded.get_format_mimetype() == "image/bmp"
|
|
|
|
assert_image_equal(im, reloaded)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_rgba_bitfields() -> None:
|
2020-02-17 11:42:33 +03:00
|
|
|
# This test image has been manually hexedited
|
|
|
|
# to change the bitfield compression in the header from XBGR to RGBA
|
|
|
|
with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
|
|
|
|
# So before the comparing the image, swap the channels
|
|
|
|
b, g, r = im.split()[1:]
|
|
|
|
im = Image.merge("RGB", (r, g, b))
|
|
|
|
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
2021-12-21 04:43:50 +03:00
|
|
|
|
2022-07-14 00:25:28 +03:00
|
|
|
# This test image has been manually hexedited
|
|
|
|
# to change the bitfield compression in the header from XBGR to ABGR
|
|
|
|
with Image.open("Tests/images/rgb32bf-abgr.bmp") as im:
|
|
|
|
assert_image_equal_tofile(
|
|
|
|
im.convert("RGB"), "Tests/images/bmp/q/rgb32bf-xbgr.bmp"
|
|
|
|
)
|
|
|
|
|
2021-12-21 04:43:50 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_rle8() -> None:
|
2022-03-09 14:35:48 +03:00
|
|
|
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
|
|
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
|
|
|
|
2023-10-19 11:12:01 +03:00
|
|
|
with Image.open("Tests/images/hopper_rle8_grayscale.bmp") as im:
|
2022-06-20 14:01:10 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
|
|
|
|
2022-03-02 03:55:18 +03:00
|
|
|
# This test image has been manually hexedited
|
|
|
|
# to have rows with too much data
|
|
|
|
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
|
|
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
|
|
|
|
2022-03-05 01:15:07 +03:00
|
|
|
# Signal end of bitmap before the image is finished
|
|
|
|
with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp:
|
|
|
|
data = fp.read(1063) + b"\x01"
|
|
|
|
with Image.open(io.BytesIO(data)) as im:
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.load()
|
|
|
|
|
2022-10-22 02:41:26 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_rle4() -> None:
|
2022-10-22 02:45:05 +03:00
|
|
|
with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im:
|
|
|
|
assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12)
|
|
|
|
|
2022-03-09 14:35:48 +03:00
|
|
|
|
2022-03-09 14:35:00 +03:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"file_name,length",
|
|
|
|
(
|
|
|
|
# EOF immediately after the header
|
|
|
|
("Tests/images/hopper_rle8.bmp", 1078),
|
|
|
|
# EOF during delta
|
|
|
|
("Tests/images/bmp/q/pal8rletrns.bmp", 3670),
|
|
|
|
# EOF when reading data in absolute mode
|
|
|
|
("Tests/images/bmp/g/pal8rle.bmp", 1064),
|
|
|
|
),
|
|
|
|
)
|
2024-02-17 07:00:38 +03:00
|
|
|
def test_rle8_eof(file_name: str, length: int) -> None:
|
2022-03-09 14:35:00 +03:00
|
|
|
with open(file_name, "rb") as fp:
|
|
|
|
data = fp.read(length)
|
|
|
|
with Image.open(io.BytesIO(data)) as im:
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.load()
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_offset() -> None:
|
2021-12-21 04:43:50 +03:00
|
|
|
# This image has been hexedited
|
|
|
|
# to exclude the palette size from the pixel data offset
|
|
|
|
with Image.open("Tests/images/pal8_offset.bmp") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
|