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"""
6from typing import Callable
7from typing import List
8from typing import Tuple
10from mypy_extensions import TypedDict
12from .utils import bytes_to_int
13from .utils import rgb888_to_rgb565
14from .utils import sublists_from_iterable
16__all__ = ("pil_image_to_list",)
19# conditional imports
20try:
21 from PIL.Image import Image
22except ImportError as e:
23 raise ImportError(
24 "Package PIL or Pillow must be available to use this functionality."
25 ) from e
27ConversionItem = TypedDict(
28 "ConversionItem",
29 {
30 "formats": Tuple[str, str],
31 "conversion": Callable,
32 "bytes_per_pixel": int,
33 },
34)
35Conversion = List[ConversionItem]
37CONVERSIONS: Conversion = [
38 {
39 "formats": ("RGB", "RGB888"),
40 "conversion": bytes_to_int,
41 "bytes_per_pixel": 3,
42 },
43 {
44 "formats": ("RGB", "RGB565"),
45 "conversion": rgb888_to_rgb565,
46 "bytes_per_pixel": 3,
47 },
48 {
49 "formats": ("RGBA", "ARGB8888"),
50 "conversion": bytes_to_int,
51 "bytes_per_pixel": 4,
52 },
53 {"formats": ("L", "A8"), "conversion": bytes_to_int, "bytes_per_pixel": 1},
54]
57def pil_image_to_list(
58 image: Image, outformat: str = "ARGB8888"
59) -> List[List[int]]:
60 """Convert a PIL image into an a list of `int`\\ s.
62 Conversion is done from the :meth:`Image.tobytes` to `list` of `int`, based
63 on the format given by the argument `format`.
65 The main purpose of this is to turn :class:`Image`\\ s into nested
66 `list`\\ s, which can be used as array initialization values by :class:
67 csnake.variable.
69 image's format is extracted from the image itself and there is only a
70 limited number of conversions available.
71 While the input format is fixed, the output format is supplied by
72 `outformat`.
74 Here are the supported mappings of formats (PIL :attr:`Image.mode` → output
75 format):
77 * RGB → RGB888
78 * RGB → RGB565
79 * RGBA → ARGB888
80 * L → A8
82 Args:
83 image: `PIL` or `Pillow` :class:`Image` to be turned into array.
84 outformat: Format of `int`'s in the output array.
86 Returns:
87 :obj:`list` of :obj:`list` of :obj:`int` for pixels of the image
89 Raises:
90 TypeError: If the `image` arg passed isn't a `PIL` `Image`.
91 ValueError: If the conversion between the listed formats isn't
92 possible.
94 Examples:
95 >>> from csnake.pil_converter import pil_image_to_list
96 >>> from csnake import CodeWriter, Variable, FormattedLiteral
97 >>> from PIL import Image, ImageDraw
98 >>> im = Image.new('RGB', (3, 3))
99 >>> dr = ImageDraw.Draw(im)
100 >>> dr.line((0,0) + im.size, fill = '#8CAFAF')
101 >>> im_ls = pil_image_to_list(im, "RGB888")
102 >>> print(im_ls)
103 [[9220015, 0, 0], [0, 9220015, 0], [0, 0, 9220015]]
104 >>> var = Variable(
105 ... "pic",
106 ... primitive="int",
107 ... value=FormattedLiteral(im_ls, int_formatter=hex),
108 ... )
109 >>> cw = CodeWriter()
110 >>> cw.add_variable_initialization(var)
111 >>> print(cw)
112 int pic[3][3] = {
113 {0x8cafaf, 0x0, 0x0},
114 {0x0, 0x8cafaf, 0x0},
115 {0x0, 0x0, 0x8cafaf}
116 };
117 """
119 if not isinstance(image, Image):
120 raise TypeError("image must be an instance of PIL.Image")
122 input_format = image.mode
123 output_format = outformat
125 iomode = input_format, output_format
127 try:
128 conversion = next(i for i in CONVERSIONS if i["formats"] == iomode)
129 except StopIteration as e:
130 raise ValueError(
131 f"Cannot export to pixel format {output_format} from an image "
132 "with pixel format {input_format}. See documenation code for "
133 "valid combinations."
134 ) from e
136 conversion_function = conversion["conversion"]
138 bytes_per_pixel = conversion["bytes_per_pixel"]
140 rows = image.height
141 row_width = image.width
143 image_bytes = image.tobytes()
145 input_bytes = len(image_bytes)
147 if not input_bytes % bytes_per_pixel == 0:
148 raise ValueError(
149 f"Number of picture's bytes ({input_bytes}) is not "
150 "divisible by the pixel byte length for its supposed format "
151 "({self.bytes_per_pixel})."
152 )
154 image_bytes_to_int = (int(b) for b in image_bytes)
155 int_list_pixels = sublists_from_iterable(
156 image_bytes_to_int, bytes_per_pixel
157 )
159 intpixel_iter = iter(int_list_pixels)
161 output_list = []
162 for _ in range(rows):
163 curr_row = []
164 for _ in range(row_width):
165 current_pixel_ints = next(intpixel_iter)
166 converted_pixel = conversion_function(current_pixel_ints)
167 curr_row.append(converted_pixel)
168 output_list.append(curr_row)
170 return output_list