Hide keyboard shortcuts

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 

8 

9from mypy_extensions import TypedDict 

10 

11from .utils import bytes_to_int 

12from .utils import rgb888_to_rgb565 

13from .utils import sublists_from_iterable 

14 

15__all__ = ("pil_image_to_list",) 

16 

17 

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 

25 

26ConversionItem = TypedDict( 

27 "ConversionItem", 

28 { 

29 "formats": Tuple[str, str], 

30 "conversion": Callable, 

31 "bytes_per_pixel": int, 

32 }, 

33) 

34Conversion = List[ConversionItem] 

35 

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] 

54 

55 

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. 

60 

61 Conversion is done from the :meth:`Image.tobytes` to `list` of `int`, based 

62 on the format given by the argument `format`. 

63 

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. 

67 

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`. 

72 

73 Here are the supported mappings of formats (PIL :attr:`Image.mode` → output 

74 format): 

75 

76 * RGB → RGB888 

77 * RGB → RGB565 

78 * RGBA → ARGB888 

79 * L → A8 

80 

81 Args: 

82 image: `PIL` or `Pillow` :class:`Image` to be turned into array. 

83 outformat: Format of `int`'s in the output array. 

84 

85 Returns: 

86 :obj:`list` of :obj:`list` of :obj:`int` for pixels of the image 

87 

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. 

92 

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 """ 

117 

118 if not isinstance(image, Image): 

119 raise TypeError("image must be an instance of PIL.Image") 

120 

121 input_format = image.mode 

122 output_format = outformat 

123 

124 iomode = input_format, output_format 

125 

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 

134 

135 conversion_function = conversion["conversion"] 

136 

137 bytes_per_pixel = conversion["bytes_per_pixel"] 

138 

139 rows = image.height 

140 row_width = image.width 

141 

142 image_bytes = image.tobytes() 

143 

144 input_bytes = len(image_bytes) 

145 

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 ) 

152 

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 ) 

157 

158 intpixel_iter = iter(int_list_pixels) 

159 

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) 

168 

169 return output_list