-
Notifications
You must be signed in to change notification settings - Fork 158
Expand file tree
/
Copy pathmock.py
More file actions
274 lines (209 loc) · 8.58 KB
/
mock.py
File metadata and controls
274 lines (209 loc) · 8.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""PIL/Tkinter based simulator for InkyWHAT and InkyWHAT."""
import numpy
from . import inky, inky_uc8159
class InkyMock(inky.Inky):
"""Base simulator class for Inky."""
def __init__(self, colour, h_flip=False, v_flip=False, resolution=None):
"""Initialise an Inky pHAT Display.
:param colour: one of red, black or yellow, default: black
:param resolution: (width, height) in pixels
"""
global tkinter, ImageTk, Image
try:
import tkinter
except ImportError:
raise ImportError("Simulation requires tkinter")
try:
from PIL import Image, ImageTk
except ImportError:
raise ImportError("Simulation requires PIL ImageTk and Image")
if resolution is None:
resolution = (self.WIDTH, self.HEIGHT)
if resolution not in inky._RESOLUTION.keys():
raise ValueError("Resolution {}x{} not supported!".format(*resolution))
self.resolution = resolution
self.width, self.height = resolution
self.cols, self.rows, self.rotation = inky._RESOLUTION[resolution]
self.buf = numpy.zeros((self.height, self.width), dtype=numpy.uint8)
if colour not in ("red", "black", "yellow", "multi"):
raise ValueError("Colour {} is not supported!".format(colour))
self.colour = colour
self.h_flip = h_flip
self.v_flip = v_flip
impression_palette = [57, 48, 57, # black
255, 255, 255, # white
58, 91, 70, # green
61, 59, 94, # blue
156, 72, 75, # red
208, 190, 71, # yellow
177, 106, 73, # orange
255, 255, 255] # clear
bw_inky_palette = [255, 255, 255, # 0 = white
0, 0, 0] # 1 = black
red_inky_palette = [255, 255, 255, # 0 = white
0, 0, 0, # 1 = black
255, 0, 0] # index 2 is red
ylw_inky_palette = [255, 255, 255, # 0 = white
0, 0, 0, # 1 = black
223, 204, 16] # index 2 is yellow
# yellow color value: screen capture from
# https://www.thoughtsmakethings.com/Pimoroni-Inky-pHAT
self.c_palette = {"black": bw_inky_palette,
"red": red_inky_palette,
"yellow": ylw_inky_palette,
"multi": impression_palette}
self._tk_done = False
self.tk_root = tkinter.Tk()
self.tk_root.title("Inky Preview")
self.tk_root.geometry("{}x{}".format(self.width, self.height))
self.tk_root.aspect(self.width, self.height, self.width, self.height)
self.tk_root.protocol("WM_DELETE_WINDOW", self._close_window)
self.cv = None
self.cvh = self.height
self.cvw = self.width
def wait_for_window_close(self):
"""Wait until the Tkinter window has closed."""
while not self._tk_done:
self.tk_root.update_idletasks()
self.tk_root.update()
def _close_window(self):
self._tk_done = True
self.tk_root.destroy()
def resize(self, event):
"""Resize background image to window size."""
# adapted from:
# https://stackoverflow.com/questions/24061099/tkinter-resize-background-image-to-window-size
# https://stackoverflow.com/questions/19838972/how-to-update-an-image-on-a-canvas
self.cvw = event.width
self.cvh = event.height
self.cv.config(width=self.cvw, height=self.cvh)
image = self.disp_img_copy.resize([self.cvw, self.cvh])
self.photo = ImageTk.PhotoImage(image)
self.cv.itemconfig(self.cvhandle, image=self.photo, anchor="nw")
self.tk_root.update()
def _send_command(self, command, data=None):
pass
def _simulate(self, region):
pass
def _display(self, region):
im = Image.fromarray(region, "P")
im.putpalette(self.c_palette[self.colour])
self.disp_img_copy = im.copy() # can be changed due to window resizing, so copy
image = self.disp_img_copy.resize([self.cvw, self.cvh])
self.photo = ImageTk.PhotoImage(image)
if self.cv is None:
self.cv = tkinter.Canvas(self.tk_root, width=self.width, height=self.height)
self.cv.pack(side="top", fill="both", expand="yes")
self.cvhandle = self.cv.create_image(0, 0, image=self.photo, anchor="nw")
self.cv.bind("<Configure>", self.resize)
self.tk_root.update()
def show(self, busy_wait=True):
"""Show buffer on display.
:param busy_wait: Ignored. Updates are simulated and instant.
"""
print(">> Simulating {} {}x{}...".format(self.colour, self.width, self.height))
region = self.buf
if self.v_flip:
region = numpy.fliplr(region)
if self.h_flip:
region = numpy.flipud(region)
if self.rotation:
region = numpy.rot90(region, self.rotation // 90)
self._simulate(region)
class InkyMockPHAT(InkyMock):
"""Inky PHAT (212x104) e-Ink Display Simulator."""
WIDTH = 212
HEIGHT = 104
WHITE = 0
BLACK = 1
RED = 2
YELLOW = 2
def _simulate(self, region):
region = numpy.rot90(region, self.rotation // 90)
region = numpy.flipud(region) # spec: phat rotated -90
region = numpy.fliplr(region) # spec: phat rotated -90
self._display(region)
class InkyMockPHATSSD1608(InkyMock):
"""Inky PHAT SSD1608 (250x122) e-Ink Display Simulator."""
WIDTH = 250
HEIGHT = 122
WHITE = 0
BLACK = 1
RED = 2
YELLOW = 2
def _simulate(self, region):
region = numpy.rot90(region, self.rotation // 90)
region = numpy.flipud(region) # spec: phat rotated -90
region = numpy.fliplr(region) # spec: phat rotated -90
self._display(region)
class InkyMockWHAT(InkyMock):
"""Inky wHAT e-Ink Display Simulator."""
WIDTH = 400
HEIGHT = 300
WHITE = 0
BLACK = 1
RED = 2
YELLOW = 2
def _simulate(self, region):
region = numpy.rot90(region, self.rotation // 90)
region = region.reshape(300, 400) # for display
self._display(region)
class InkyMockImpression(InkyMock):
"""Inky Impression e-Ink Display Simulator."""
BLACK = 0
WHITE = 1
GREEN = 2
BLUE = 3
RED = 4
YELLOW = 5
ORANGE = 6
CLEAN = 7
WIDTH = 600
HEIGHT = 448
DESATURATED_PALETTE = [
[0, 0, 0],
[255, 255, 255],
[0, 255, 0],
[0, 0, 255],
[255, 0, 0],
[255, 255, 0],
[255, 140, 0],
[255, 255, 255]]
SATURATED_PALETTE = [
[57, 48, 57],
[255, 255, 255],
[58, 91, 70],
[61, 59, 94],
[156, 72, 75],
[208, 190, 71],
[177, 106, 73],
[255, 255, 255]]
def __init__(self, resolution=None):
"""Initialize a new mock Inky Impression.
:param resolution: (width, height) in pixels, default: (600, 448)
"""
InkyMock.__init__(self, "multi", resolution=resolution)
def _simulate(self, region):
self._display(region)
def set_pixel(self, x, y, v):
"""Set a single pixel on the display."""
self.buf[y][x] = v & 0xF
def set_image(self, image, saturation=0.5):
"""Copy an image to the display.
:param image: PIL image to copy, must be 600x448
:param saturation: Saturation for quantization palette - higher value results in a more saturated image
"""
if not image.size == (self.width, self.height):
raise ValueError("Image must be ({}x{}) pixels!".format(self.width, self.height))
if not image.mode == "P":
if Image is None:
raise RuntimeError("PIL is required for converting images: sudo apt install python-pil python3-pil")
palette = inky_uc8159.Inky._palette_blend(self, saturation)
# Image size doesn't matter since it's just the palette we're using
palette_image = Image.new("P", (1, 1))
# Set our 7 colour palette (+ clear) and zero out the other 247 colours
palette_image.putpalette(palette + [0, 0, 0] * 248)
# Force source image data to be loaded for `.im` to work
image.load()
image = image.im.convert("P", True, palette_image.im)
self.buf = numpy.array(image, dtype=numpy.uint8).reshape((self.rows, self.cols))