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

5 

6from typing import Callable 

7from typing import List 

8from typing import Tuple 

9 

10from mypy_extensions import TypedDict 

11 

12from .utils import bytes_to_int 

13from .utils import rgb888_to_rgb565 

14from .utils import sublists_from_iterable 

15 

16__all__ = ("pil_image_to_list",) 

17 

18 

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 

26 

27ConversionItem = TypedDict( 

28 "ConversionItem", 

29 { 

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

31 "conversion": Callable, 

32 "bytes_per_pixel": int, 

33 }, 

34) 

35Conversion = List[ConversionItem] 

36 

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] 

55 

56 

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. 

61 

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

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

64 

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. 

68 

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

73 

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

75 format): 

76 

77 * RGB → RGB888 

78 * RGB → RGB565 

79 * RGBA → ARGB888 

80 * L → A8 

81 

82 Args: 

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

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

85 

86 Returns: 

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

88 

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. 

93 

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

118 

119 if not isinstance(image, Image): 

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

121 

122 input_format = image.mode 

123 output_format = outformat 

124 

125 iomode = input_format, output_format 

126 

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 

135 

136 conversion_function = conversion["conversion"] 

137 

138 bytes_per_pixel = conversion["bytes_per_pixel"] 

139 

140 rows = image.height 

141 row_width = image.width 

142 

143 image_bytes = image.tobytes() 

144 

145 input_bytes = len(image_bytes) 

146 

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 ) 

153 

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 ) 

158 

159 intpixel_iter = iter(int_list_pixels) 

160 

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) 

169 

170 return output_list