mirror of
https://github.com/python-pillow/Pillow.git
synced 2026-02-05 06:49:32 +03:00
Added support for 1 mode images
This commit is contained in:
parent
ffa84e5668
commit
e24b3ebaab
Binary file not shown.
Binary file not shown.
BIN
Tests/images/jxl/hopper.jxl
Normal file
BIN
Tests/images/jxl/hopper.jxl
Normal file
Binary file not shown.
BIN
Tests/images/jxl/hopper_bw_500.jxl
Normal file
BIN
Tests/images/jxl/hopper_bw_500.jxl
Normal file
Binary file not shown.
BIN
Tests/images/jxl/hopper_gray.jxl
Normal file
BIN
Tests/images/jxl/hopper_gray.jxl
Normal file
Binary file not shown.
BIN
Tests/images/jxl/unknown_mode.jxl
Normal file
BIN
Tests/images/jxl/unknown_mode.jxl
Normal file
Binary file not shown.
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, JpegXlImagePlugin, features
|
||||
from PIL import Image, JpegXlImagePlugin, UnidentifiedImageError, features
|
||||
|
||||
from .helper import assert_image_similar_tofile, skip_unless_feature
|
||||
|
||||
|
|
@ -13,10 +14,6 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
# cjxl v0.9.2 41b8cdab
|
||||
# hopper.jxl: cjxl hopper.png hopper.jxl -q 75 -e 8
|
||||
# 16_bit_binary.jxl: cjxl 16_bit_binary.pgm 16_bit_binary.jxl -q 100 -e 9
|
||||
|
||||
|
||||
class TestUnsupportedJpegXl:
|
||||
def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
|
@ -24,7 +21,7 @@ class TestUnsupportedJpegXl:
|
|||
|
||||
with pytest.raises(OSError):
|
||||
with pytest.warns(UserWarning, match="JXL support not installed"):
|
||||
Image.open("Tests/images/hopper.jxl")
|
||||
Image.open("Tests/images/jxl/hopper.jxl")
|
||||
|
||||
|
||||
@skip_unless_feature("jpegxl")
|
||||
|
|
@ -34,53 +31,34 @@ class TestFileJpegXl:
|
|||
assert version is not None
|
||||
assert re.search(r"\d+\.\d+\.\d+$", version)
|
||||
|
||||
def test_read_rgb(self) -> None:
|
||||
"""
|
||||
Can we read an RGB mode JPEG XL file without error?
|
||||
Does it have the bits we expect?
|
||||
"""
|
||||
|
||||
with Image.open("Tests/images/hopper.jxl") as im:
|
||||
assert im.mode == "RGB"
|
||||
assert im.size == (128, 128)
|
||||
@pytest.mark.parametrize(
|
||||
"mode, test_file",
|
||||
(
|
||||
("1", "hopper_bw_500.png"),
|
||||
("L", "hopper_gray.jpg"),
|
||||
("I;16", "jxl/16bit_subcutaneous.cropped.png"),
|
||||
("RGB", "hopper.jpg"),
|
||||
("RGBA", "transparent.png"),
|
||||
),
|
||||
)
|
||||
def test_read(self, mode: str, test_file: str) -> None:
|
||||
with Image.open(
|
||||
"Tests/images/jxl/"
|
||||
+ os.path.splitext(os.path.basename(test_file))[0]
|
||||
+ ".jxl"
|
||||
) as im:
|
||||
assert im.format == "JPEG XL"
|
||||
im.getdata()
|
||||
assert im.mode == mode
|
||||
|
||||
# generated with:
|
||||
# djxl hopper.jxl hopper_jxl_bits.ppm
|
||||
assert_image_similar_tofile(im, "Tests/images/hopper_jxl_bits.ppm", 1)
|
||||
assert_image_similar_tofile(im, "Tests/images/" + test_file, 1.9)
|
||||
|
||||
def test_read_rgba(self) -> None:
|
||||
# Generated with `cjxl transparent.png transparent.jxl -q 100 -e 8`
|
||||
with Image.open("Tests/images/transparent.jxl") as im:
|
||||
assert im.mode == "RGBA"
|
||||
assert im.size == (200, 150)
|
||||
assert im.format == "JPEG XL"
|
||||
im.getdata()
|
||||
|
||||
im.tobytes()
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/transparent.png", 1)
|
||||
|
||||
def test_read_i16(self) -> None:
|
||||
"""
|
||||
Can we read 16-bit Grayscale JPEG XL image?
|
||||
"""
|
||||
|
||||
with Image.open("Tests/images/jxl/16bit_subcutaneous.cropped.jxl") as im:
|
||||
assert im.mode == "I;16"
|
||||
assert im.size == (128, 64)
|
||||
assert im.format == "JPEG XL"
|
||||
im.getdata()
|
||||
|
||||
assert_image_similar_tofile(
|
||||
im, "Tests/images/jxl/16bit_subcutaneous.cropped.png", 1
|
||||
)
|
||||
def test_unknown_mode(self) -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
Image.open("Tests/images/jxl/unknown_mode.jxl")
|
||||
|
||||
def test_JpegXlDecode_with_invalid_args(self) -> None:
|
||||
"""
|
||||
Calling decoder functions with no arguments should result in an error.
|
||||
"""
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_jpegxl.JpegXlDecoder()
|
||||
|
|
|
|||
|
|
@ -12,17 +12,17 @@ pytestmark = skip_unless_feature("jpegxl")
|
|||
def test_n_frames() -> None:
|
||||
"""Ensure that jxl format sets n_frames and is_animated attributes correctly."""
|
||||
|
||||
with Image.open("Tests/images/hopper.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/hopper.jxl") as im:
|
||||
assert im.n_frames == 1
|
||||
assert not im.is_animated
|
||||
|
||||
with Image.open("Tests/images/iss634.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/iss634.jxl") as im:
|
||||
assert im.n_frames == 41
|
||||
assert im.is_animated
|
||||
|
||||
|
||||
def test_duration() -> None:
|
||||
with Image.open("Tests/images/iss634.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/iss634.jxl") as im:
|
||||
assert im.info["duration"] == 70
|
||||
assert im.info["timestamp"] == 0
|
||||
|
||||
|
|
@ -43,17 +43,17 @@ def test_seek() -> None:
|
|||
assert im1.is_animated
|
||||
|
||||
# Traverse frames in reverse, checking timestamps and durations
|
||||
total_dur = 0
|
||||
total_duration = 0
|
||||
for frame in reversed(range(im1.n_frames)):
|
||||
im1.seek(frame)
|
||||
im2.seek(frame)
|
||||
|
||||
assert_image_equal(im1.convert("RGB"), im2.convert("RGB"))
|
||||
|
||||
total_dur += im1.info["duration"]
|
||||
total_duration += im1.info["duration"]
|
||||
assert im1.info["duration"] == im2.info["duration"]
|
||||
assert im1.info["timestamp"] == im1.info["timestamp"]
|
||||
assert total_dur == 8000
|
||||
assert total_duration == 8000
|
||||
|
||||
assert im1.tell() == 0
|
||||
assert im2.tell() == 0
|
||||
|
|
@ -65,7 +65,7 @@ def test_seek() -> None:
|
|||
|
||||
|
||||
def test_seek_errors() -> None:
|
||||
with Image.open("Tests/images/iss634.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/iss634.jxl") as im:
|
||||
with pytest.raises(EOFError, match="attempt to seek outside sequence"):
|
||||
im.seek(-1)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ except ImportError:
|
|||
|
||||
|
||||
def test_read_exif_metadata() -> None:
|
||||
with Image.open("Tests/images/flower.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/flower.jxl") as im:
|
||||
assert im.format == "JPEG XL"
|
||||
exif_data = im.info["exif"]
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ def test_read_exif_metadata() -> None:
|
|||
|
||||
|
||||
def test_read_exif_metadata_without_prefix() -> None:
|
||||
with Image.open("Tests/images/flower2.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/flower2.jxl") as im:
|
||||
# Assert prefix is not present
|
||||
assert im.info["exif"][:6] != b"Exif\x00\x00"
|
||||
|
||||
|
|
@ -53,12 +53,12 @@ def test_read_exif_metadata_without_prefix() -> None:
|
|||
|
||||
|
||||
def test_read_icc_profile() -> None:
|
||||
with Image.open("Tests/images/flower.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/flower.jxl") as im:
|
||||
assert "icc_profile" in im.info
|
||||
|
||||
|
||||
def test_getxmp() -> None:
|
||||
with Image.open("Tests/images/flower.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/flower.jxl") as im:
|
||||
assert "xmp" not in im.info
|
||||
if ElementTree is None:
|
||||
with pytest.warns(
|
||||
|
|
@ -70,7 +70,7 @@ def test_getxmp() -> None:
|
|||
xmp = im.getxmp()
|
||||
assert xmp == {}
|
||||
|
||||
with Image.open("Tests/images/flower2.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/flower2.jxl") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
|
|
@ -105,10 +105,10 @@ def test_4_byte_exif(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
|
||||
monkeypatch.setattr(JpegXlImagePlugin, "_jpegxl", _mock_jpegxl)
|
||||
|
||||
with Image.open("Tests/images/hopper.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/hopper.jxl") as im:
|
||||
assert "exif" not in im.info
|
||||
|
||||
|
||||
def test_read_exif_metadata_empty() -> None:
|
||||
with Image.open("Tests/images/hopper.jxl") as im:
|
||||
with Image.open("Tests/images/jxl/hopper.jxl") as im:
|
||||
assert im.getexif() == {}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ class JpegXlImageFile(ImageFile.ImageFile):
|
|||
self.info["exif"] = exif[exif_start_offset + 4 :]
|
||||
if xmp := self._decoder.get_xmp():
|
||||
self.info["xmp"] = xmp
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.mode)]
|
||||
rawmode = "L" if self.mode == "1" else self.mode
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, rawmode)]
|
||||
|
||||
@property
|
||||
def n_frames(self) -> int:
|
||||
|
|
|
|||
|
|
@ -14,21 +14,16 @@
|
|||
void
|
||||
_jxl_get_pixel_format(JxlPixelFormat *pf, const JxlBasicInfo *bi) {
|
||||
pf->num_channels = bi->num_color_channels + bi->num_extra_channels;
|
||||
|
||||
if (bi->exponent_bits_per_sample) {
|
||||
pf->data_type = JXL_TYPE_FLOAT;
|
||||
} else if (bi->bits_per_sample > 8) {
|
||||
pf->data_type = JXL_TYPE_UINT16;
|
||||
} else {
|
||||
pf->data_type = JXL_TYPE_UINT8;
|
||||
}
|
||||
|
||||
pf->data_type = bi->bits_per_sample > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
|
||||
pf->align = 0;
|
||||
}
|
||||
|
||||
char *
|
||||
_jxl_get_mode(const JxlBasicInfo *bi) {
|
||||
if (bi->num_color_channels == 1 && !bi->alpha_bits) {
|
||||
if (bi->bits_per_sample == 1) {
|
||||
return "1";
|
||||
}
|
||||
if (bi->bits_per_sample == 16) {
|
||||
return "I;16";
|
||||
}
|
||||
|
|
@ -40,9 +35,6 @@ _jxl_get_mode(const JxlBasicInfo *bi) {
|
|||
if (bi->num_color_channels == 3) {
|
||||
return bi->alpha_premultiplied ? "RGBa" : "RGBA";
|
||||
}
|
||||
if (bi->num_color_channels == 1) {
|
||||
return bi->alpha_premultiplied ? "La" : "LA";
|
||||
}
|
||||
} else {
|
||||
// image has no transparency
|
||||
if (bi->num_color_channels == 3) {
|
||||
|
|
@ -290,7 +282,7 @@ decoder_loop_skip_process:
|
|||
realloc(final_jxl_buf, final_jxl_buf_len + compressed_box_size);
|
||||
if (!_new_jxl_buf) {
|
||||
PyErr_SetString(PyExc_OSError, "failed to allocate final_jxl_buf");
|
||||
goto end;
|
||||
goto end_with_custom_error;
|
||||
}
|
||||
final_jxl_buf = _new_jxl_buf;
|
||||
|
||||
|
|
|
|||
|
|
@ -256,6 +256,14 @@ unpack18(UINT8 *out, const UINT8 *in, int pixels) {
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
unpack1L(UINT8 *out, const UINT8 *in, int pixels) {
|
||||
int i;
|
||||
for (i = 0; i < pixels; i++) {
|
||||
out[i] = in[i] > 128 ? 255 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unpack to "L" image */
|
||||
|
||||
static void
|
||||
|
|
@ -1564,6 +1572,7 @@ static struct {
|
|||
{IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, unpack1R},
|
||||
{IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, unpack1IR},
|
||||
{IMAGING_MODE_1, IMAGING_RAWMODE_1_8, 8, unpack18},
|
||||
{IMAGING_MODE_1, IMAGING_RAWMODE_L, 8, unpack1L},
|
||||
|
||||
/* grayscale */
|
||||
{IMAGING_MODE_L, IMAGING_RAWMODE_L_2, 2, unpackL2},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user