Skip to content

Commit 313ac76

Browse files
committed
do not convert "L" mode to "RGB" during save #26
1 parent 5268af7 commit 313ac76

File tree

3 files changed

+41
-12
lines changed

3 files changed

+41
-12
lines changed

pillow_heif/heif.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ def __init__(self, heif_ctx: Union[LibHeifCtx, HeifCtxAsDict], handle):
4848
else:
4949
self._handle = None
5050
self.size = heif_ctx.size
51+
if heif_ctx.mode == "L":
52+
self._colorspace = HeifColorspace.MONOCHROME
5153
if heif_ctx.data:
52-
_img = create_image(self.size, self.chroma, self.bit_depth, heif_ctx.data, stride=heif_ctx.stride)
54+
chroma_color = (self.chroma, self._colorspace)
55+
_img = create_image(self.size, chroma_color, self.bit_depth, heif_ctx.data, heif_ctx.stride)
5356
self._img_to_img_data_dict(_img)
5457

5558
@property
@@ -88,10 +91,14 @@ def has_alpha(self):
8891

8992
@property
9093
def mode(self):
91-
"""Returns “RGBA” for images with alpha channel, and “RGB” for images without.
94+
"""Returns “L“ for Greyscale images, “RGBA” for images with alpha channel and “RGB” for other cases.
9295
93-
:returns: "RGB" or "RGBA" """
96+
.. note:: Images with "L" mode currently only possible to get when adding from Pillow.
9497
98+
:returns: "RGB", "RGBA" or "L" """
99+
100+
if self.color == HeifColorspace.MONOCHROME:
101+
return "L"
95102
return "RGBA" if self.has_alpha else "RGB" # noqa
96103

97104
@property
@@ -123,6 +130,8 @@ def chroma(self):
123130
124131
:returns: Value from :py:class:`~pillow_heif.HeifChroma`"""
125132

133+
if self.mode == "L":
134+
return HeifChroma.MONOCHROME
126135
if self.bit_depth <= 8:
127136
return HeifChroma.INTERLEAVED_RGBA if self.has_alpha else HeifChroma.INTERLEAVED_RGB
128137
return HeifChroma.INTERLEAVED_RRGGBBAA_BE if self.has_alpha else HeifChroma.INTERLEAVED_RRGGBB_BE
@@ -186,7 +195,8 @@ def _load_if_not(self):
186195

187196
def _img_to_img_data_dict(self, heif_img):
188197
p_stride = ffi.new("int *")
189-
p_data = lib.heif_image_get_plane(heif_img, HeifChannel.INTERLEAVED, p_stride)
198+
channel = HeifChannel.Y if self.color == HeifColorspace.MONOCHROME else HeifChannel.INTERLEAVED
199+
p_data = lib.heif_image_get_plane(heif_img, channel, p_stride)
190200
stride = p_stride[0]
191201
data_length = self.size[1] * stride
192202
data_buffer = ffi.buffer(p_data, data_length)
@@ -575,8 +585,6 @@ def __add_frame_from_pillow(self, frame: Image.Image, ignore_primary: bool, **kw
575585
frame = frame.convert(mode=mode)
576586
elif frame.mode == "LA":
577587
frame = frame.convert(mode="RGBA")
578-
elif frame.mode == "L":
579-
frame = frame.convert(mode="RGB")
580588

581589
if original_orientation is not None and original_orientation != 1:
582590
frame = ImageOps.exif_transpose(frame)

pillow_heif/private.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22
Undocumented private functions for other code to look better.
33
"""
44

5-
from typing import Union
5+
from typing import Tuple, Union
66

77
from _pillow_heif_cffi import ffi, lib
88

99
from ._libheif_ctx import LibHeifCtxWrite
1010
from .constants import HeifChannel, HeifChroma, HeifColorProfileType, HeifColorspace
1111
from .error import check_libheif_error
1212

13+
MODE_CHANNELS = {
14+
"RGBA": 4,
15+
"RGB": 3,
16+
"L": 1,
17+
"": 0,
18+
}
19+
1320

1421
# from dataclasses import dataclass
1522
# @dataclass # Avalaible from Python 3.7
@@ -20,7 +27,7 @@ def __init__(self, bit_depth: int, mode: str, size: tuple, data, **kwargs):
2027
stride = kwargs.get("stride", None)
2128
if stride is None:
2229
factor = 1 if bit_depth == 8 else 2
23-
stride = size[0] * 3 * factor if mode == "RGB" else size[0] * 4 * factor
30+
stride = size[0] * MODE_CHANNELS[mode] * factor
2431
self.bit_depth = bit_depth
2532
self.mode = mode
2633
self.size = size
@@ -29,16 +36,18 @@ def __init__(self, bit_depth: int, mode: str, size: tuple, data, **kwargs):
2936
self.additional_info = kwargs.get("add_info", {})
3037

3138

32-
def create_image(size: tuple, chroma: HeifChroma, bit_depth: int, data, stride: int, **kwargs):
39+
def create_image(size: tuple, chroma_color: Tuple[HeifChroma, HeifColorspace], bit_depth: int, data, stride: int):
3340
width, height = size
41+
chroma, color = chroma_color
3442
p_new_img = ffi.new("struct heif_image **")
35-
error = lib.heif_image_create(width, height, kwargs.get("color", HeifColorspace.RGB), chroma, p_new_img)
43+
error = lib.heif_image_create(width, height, color, chroma, p_new_img)
3644
check_libheif_error(error)
3745
new_img = ffi.gc(p_new_img[0], lib.heif_image_release)
38-
error = lib.heif_image_add_plane(new_img, HeifChannel.INTERLEAVED, width, height, bit_depth)
46+
channel = HeifChannel.Y if color == HeifColorspace.MONOCHROME else HeifChannel.INTERLEAVED
47+
error = lib.heif_image_add_plane(new_img, channel, width, height, bit_depth)
3948
check_libheif_error(error)
4049
p_dest_stride = ffi.new("int *")
41-
p_data = lib.heif_image_get_plane(new_img, HeifChannel.INTERLEAVED, p_dest_stride)
50+
p_data = lib.heif_image_get_plane(new_img, channel, p_dest_stride)
4251
dest_stride = p_dest_stride[0]
4352
copy_image_data(p_data, data, dest_stride, stride, height)
4453
return new_img

tests/opener_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import pillow_heif.HeifImagePlugin # noqa
1515
from pillow_heif import (
16+
HeifChroma,
1617
HeifError,
1718
HeifFile,
1819
HeifImage,
@@ -56,6 +57,17 @@ def test_corrupted_open(img_path):
5657
Image.open(BytesIO(f.read()))
5758

5859

60+
def test_add_L_mode():
61+
img = Image.open(Path("images/jpeg_gif_png/L_color_mode_image.png"))
62+
img = img.convert(mode="L")
63+
heif_file = HeifFile().add_from_pillow(img)
64+
assert heif_file.mode == "L"
65+
assert heif_file.chroma == HeifChroma.MONOCHROME
66+
img2 = heif_file[0].to_pillow()
67+
assert img2.mode == "L"
68+
assert img.tobytes() == img2.tobytes()
69+
70+
5971
@pytest.mark.parametrize("img_path", dataset.MINIMAL_DATASET)
6072
def test_inputs(img_path):
6173
with builtins.open(img_path, "rb") as fh:

0 commit comments

Comments
 (0)