31
31
import gc
32
32
import struct
33
33
import board
34
- from displayio import Bitmap , Palette , Display
34
+ from displayio import Bitmap , Palette , Display , ColorConverter
35
35
36
36
try :
37
37
from typing import Tuple , Optional , Union
@@ -81,11 +81,88 @@ def _rgb565_to_bgr_tuple(color: int) -> Tuple[int, int, int]:
81
81
return blue , green , red
82
82
83
83
84
+ def rgb565_unpack (packed_rgb : int ) -> tuple [int , int , int ]:
85
+ """
86
+ Convert an int representing a hex rgb565 color into a tuple
87
+ of it's r, g, and b values.
88
+ :param packed_rgb: rgb565 color value as an int
89
+ :return: Tuple with r, g, and b values
90
+ """
91
+ r = (packed_rgb >> 11 ) & 0x1F
92
+ g = (packed_rgb >> 5 ) & 0x3F
93
+ b = packed_rgb & 0x1F
94
+ return (r , g , b )
95
+
96
+
97
+ def rgb565_pack (r : int , g : int , b : int ) -> int :
98
+ """
99
+ Convert a tuple with r, g, and b values into an rgb565
100
+ color value represented as an integer
101
+ :param r: red value
102
+ :param g: green value
103
+ :param b: blue value
104
+ :return int: rgb565 value
105
+ """
106
+ return ((r & 0x1F ) << 11 ) | ((g & 0x3F ) << 5 ) | (b & 0x1F )
107
+
108
+
109
+ def rgb888_unpack (packed_rgb : int ) -> Tuple [int , int , int ]:
110
+ """
111
+ Convert an int representing an rgb888 color value into a
112
+ tuple containing it's r, g, and b values.
113
+ :param packed_rgb: rgb888 integer color value
114
+ :return: Tuple containing r, g, and b values
115
+ """
116
+ r = (packed_rgb >> 16 ) & 0xFF
117
+ g = (packed_rgb >> 8 ) & 0xFF
118
+ b = packed_rgb & 0xFF
119
+ return (r , g , b )
120
+
121
+
122
+ def rgb888_pack (r , g , b ):
123
+ """
124
+ Convert a tuple with r, g, and b values into an rgb888
125
+ color value represented as an integer
126
+ :param r: red value
127
+ :param g: green value
128
+ :param b: blue value
129
+ :return int: rgb888 value
130
+ """
131
+ return ((r & 0xFF ) << 16 ) | ((g & 0xFF ) << 8 ) | (b & 0xFF )
132
+
133
+
134
+ def rgb565_to_rgb888 (rgb565 ):
135
+ """
136
+ Convert from an integer representing rgb565 color into an integer
137
+ representing rgb888 color.
138
+ :param rgb565: Color to convert
139
+ :return int: rgb888 color value
140
+ """
141
+ # Shift the red value to the right by 11 bits.
142
+ red5 = rgb565 >> 11
143
+ # Shift the green value to the right by 5 bits and extract the lower 6 bits.
144
+ green6 = (rgb565 >> 5 ) & 0b111111
145
+ # Extract the lower 5 bits for blue.
146
+ blue5 = rgb565 & 0b11111
147
+
148
+ # Convert 5-bit red to 8-bit red.
149
+ red8 = round (red5 / 31 * 255 )
150
+ # Convert 6-bit green to 8-bit green.
151
+ green8 = round (green6 / 63 * 255 )
152
+ # Convert 5-bit blue to 8-bit blue.
153
+ blue8 = round (blue5 / 31 * 255 )
154
+
155
+ # Combine the RGB888 values into a single integer
156
+ rgb888_value = (red8 << 16 ) | (green8 << 8 ) | blue8
157
+
158
+ return rgb888_value
159
+
160
+
84
161
# pylint:disable=too-many-locals
85
162
def _write_pixels (
86
163
output_file : BufferedWriter ,
87
164
pixel_source : Union [Bitmap , Display ],
88
- palette : Optional [Palette ],
165
+ palette : Optional [Union [ Palette , ColorConverter ] ],
89
166
) -> None :
90
167
saving_bitmap = isinstance (pixel_source , Bitmap )
91
168
width , height = _rotated_height_and_width (pixel_source )
@@ -97,7 +174,13 @@ def _write_pixels(
97
174
# pixel_source: Bitmap
98
175
for x in range (width ):
99
176
pixel = pixel_source [x , y - 1 ]
100
- color = palette [pixel ] # handled by save_pixel's guardians
177
+ if isinstance (palette , Palette ):
178
+ color = palette [pixel ] # handled by save_pixel's guardians
179
+ elif isinstance (palette , ColorConverter ):
180
+ converted = palette .convert (pixel )
181
+ converted_888 = rgb565_to_rgb888 (converted )
182
+ color = converted_888
183
+
101
184
for _ in range (3 ):
102
185
row_buffer [buffer_index ] = color & 0xFF
103
186
color >>= 8
@@ -124,7 +207,7 @@ def _write_pixels(
124
207
def save_pixels (
125
208
file_or_filename : Union [str , BufferedWriter ],
126
209
pixel_source : Union [Display , Bitmap ] = None ,
127
- palette : Optional [Palette ] = None ,
210
+ palette : Optional [Union [ Palette , ColorConverter ] ] = None ,
128
211
) -> None :
129
212
"""Save pixels to a 24 bit per pixel BMP file.
130
213
If pixel_source if a displayio.Bitmap, save it's pixels through palette.
@@ -140,8 +223,10 @@ def save_pixels(
140
223
pixel_source = board .DISPLAY
141
224
142
225
if isinstance (pixel_source , Bitmap ):
143
- if not isinstance (palette , Palette ):
144
- raise ValueError ("Third argument must be a Palette for a Bitmap save" )
226
+ if not isinstance (palette , Palette ) and not isinstance (palette , ColorConverter ):
227
+ raise ValueError (
228
+ "Third argument must be a Palette or ColorConverter for a Bitmap save"
229
+ )
145
230
elif not isinstance (pixel_source , Display ):
146
231
raise ValueError ("Second argument must be a Bitmap or Display" )
147
232
try :
0 commit comments