Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3Module for conversion of PIL images to lists for variable initialization.
4"""
5from typing import Callable
6from typing import List
7from typing import Tuple
9from mypy_extensions import TypedDict
11from .utils import bytes_to_int
12from .utils import rgb888_to_rgb565
13from .utils import sublists_from_iterable
15__all__ = ("pil_image_to_list",)
18# conditional imports
19try:
20 from PIL.Image import Image
21except ImportError as e:
22 raise ImportError(
23 "Package PIL or Pillow must be available to use this functionality."
24 ) from e
26ConversionItem = TypedDict(
27 "ConversionItem",
28 {
29 "formats": Tuple[str, str],
30 "conversion": Callable,
31 "bytes_per_pixel": int,
32 },
33)
34Conversion = List[ConversionItem]
36CONVERSIONS: Conversion = [
37 {
38 "formats": ("RGB", "RGB888"),
39 "conversion": bytes_to_int,
40 "bytes_per_pixel": 3,
41 },
42 {
43 "formats": ("RGB", "RGB565"),
44 "conversion": rgb888_to_rgb565,
45 "bytes_per_pixel": 3,
46 },
47 {
48 "formats": ("RGBA", "ARGB8888"),
49 "conversion": bytes_to_int,
50 "bytes_per_pixel": 4,
51 },
52 {"formats": ("L", "A8"), "conversion": bytes_to_int, "bytes_per_pixel": 1},
53]
56def pil_image_to_list(
57 image: Image, outformat: str = "ARGB8888"
58) -> List[List[int]]:
59 """Convert a PIL image into an a list of `int`\\ s.
61 Conversion is done from the :meth:`Image.tobytes` to `list` of `int`, based
62 on the format given by the argument `format`.
64 The main purpose of this is to turn :class:`Image`\\ s into nested
65 `list`\\ s, which can be used as array initialization values by :class:
66 csnake.variable.
68 image's format is extracted from the image itself and there is only a
69 limited number of conversions available.
70 While the input format is fixed, the output format is supplied by
71 `outformat`.
73 Here are the supported mappings of formats (PIL :attr:`Image.mode` → output
74 format):
76 * RGB → RGB888
77 * RGB → RGB565
78 * RGBA → ARGB888
79 * L → A8
81 Args:
82 image: `PIL` or `Pillow` :class:`Image` to be turned into array.
83 outformat: Format of `int`'s in the output array.
85 Returns:
86 :obj:`list` of :obj:`list` of :obj:`int` for pixels of the image
88 Raises:
89 TypeError: If the `image` arg passed isn't a `PIL` `Image`.
90 ValueError: If the conversion between the listed formats isn't
91 possible.
93 Examples:
94 >>> from csnake.pil_converter import pil_image_to_list
95 >>> from csnake import CodeWriter, Variable, FormattedLiteral
96 >>> from PIL import Image, ImageDraw
97 >>> im = Image.new('RGB', (3, 3))
98 >>> dr = ImageDraw.Draw(im)
99 >>> dr.line((0,0) + im.size, fill = '#8CAFAF')
100 >>> im_ls = pil_image_to_list(im, "RGB888")
101 >>> print(im_ls)
102 [[9220015, 0, 0], [0, 9220015, 0], [0, 0, 9220015]]
103 >>> var = Variable(
104 ... "pic",
105 ... primitive="int",
106 ... value=FormattedLiteral(im_ls, int_formatter=hex),
107 ... )
108 >>> cw = CodeWriter()
109 >>> cw.add_variable_initialization(var)
110 >>> print(cw)
111 int pic[3][3] = {
112 {0x8cafaf, 0x0, 0x0},
113 {0x0, 0x8cafaf, 0x0},
114 {0x0, 0x0, 0x8cafaf}
115 };
116 """
118 if not isinstance(image, Image):
119 raise TypeError("image must be an instance of PIL.Image")
121 input_format = image.mode
122 output_format = outformat
124 iomode = input_format, output_format
126 try:
127 conversion = next(i for i in CONVERSIONS if i["formats"] == iomode)
128 except StopIteration as e:
129 raise ValueError(
130 f"Cannot export to pixel format {output_format} from an image "
131 "with pixel format {input_format}. See documenation code for "
132 "valid combinations."
133 ) from e
135 conversion_function = conversion["conversion"]
137 bytes_per_pixel = conversion["bytes_per_pixel"]
139 rows = image.height
140 row_width = image.width
142 image_bytes = image.tobytes()
144 input_bytes = len(image_bytes)
146 if not input_bytes % bytes_per_pixel == 0:
147 raise ValueError(
148 f"Number of picture's bytes ({input_bytes}) is not "
149 "divisible by the pixel byte length for its supposed format "
150 "({self.bytes_per_pixel})."
151 )
153 image_bytes_to_int = (int(b) for b in image_bytes)
154 int_list_pixels = sublists_from_iterable(
155 image_bytes_to_int, bytes_per_pixel
156 )
158 intpixel_iter = iter(int_list_pixels)
160 output_list = []
161 for _ in range(rows):
162 curr_row = []
163 for _ in range(row_width):
164 current_pixel_ints = next(intpixel_iter)
165 converted_pixel = conversion_function(current_pixel_ints)
166 curr_row.append(converted_pixel)
167 output_list.append(curr_row)
169 return output_list