Skip to content

Commit f8e804b

Browse files
committed
Move to learning guide repo
1 parent 0a031e7 commit f8e804b

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed

CircuitPython_PyPaint/code.py

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
"""
2+
Paint for PyPortal, PyBadge, PyGamer, and the like.
3+
4+
Adafruit invests time and resources providing this open source code.
5+
Please support Adafruit and open source hardware by purchasing
6+
products from Adafruit!
7+
8+
Written by Dave Astels for Adafruit Industries
9+
Copyright (c) 2019 Adafruit Industries
10+
Licensed under the MIT license.
11+
12+
All text above must be included in any redistribution.
13+
"""
14+
15+
#pylint:disable=invalid-name, no-self-use
16+
17+
import gc
18+
import time
19+
import board
20+
import displayio
21+
import adafruit_logging as logging
22+
try:
23+
import adafruit_touchscreen
24+
except ImportError:
25+
pass
26+
try:
27+
from adafruit_cursorcontrol.cursorcontrol import Cursor
28+
from adafruit_cursorcontrol.cursorcontrol_cursormanager import DebouncedCursorManager
29+
except ImportError:
30+
pass
31+
32+
class Color(object):
33+
"""Standard colors"""
34+
WHITE = 0xFFFFFF
35+
BLACK = 0x000000
36+
RED = 0xFF0000
37+
ORANGE = 0xFFA500
38+
YELLOW = 0xFFFF00
39+
GREEN = 0x00FF00
40+
BLUE = 0x0000FF
41+
PURPLE = 0x800080
42+
PINK = 0xFFC0CB
43+
44+
colors = (BLACK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, WHITE)
45+
46+
def __init__(self):
47+
pass
48+
49+
################################################################################
50+
51+
class TouchscreenPoller(object):
52+
"""Get 'pressed' and location updates from a touch screen device."""
53+
54+
def __init__(self, splash, cursor_bmp):
55+
logging.getLogger('Paint').debug('Creating a TouchscreenPoller')
56+
self._display_grp = splash
57+
self._touchscreen = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
58+
board.TOUCH_YD, board.TOUCH_YU,
59+
calibration=((9000, 59000),
60+
(8000, 57000)),
61+
size=(320, 240))
62+
self._cursor_grp = displayio.Group(max_size=1)
63+
self._cur_palette = displayio.Palette(3)
64+
self._cur_palette.make_transparent(0)
65+
self._cur_palette[1] = 0xFFFFFF
66+
self._cur_palette[2] = 0x0000
67+
self._cur_sprite = displayio.TileGrid(cursor_bmp,
68+
pixel_shader=self._cur_palette)
69+
self._cursor_grp.append(self._cur_sprite)
70+
self._display_grp.append(self._cursor_grp)
71+
self._x_offset = cursor_bmp.width // 2
72+
self._y_offset = cursor_bmp.height // 2
73+
74+
75+
76+
def poll(self):
77+
"""Check for input. Returns contact (a bool) and it's location ((x,y) or None)"""
78+
79+
p = self._touchscreen.touch_point
80+
if p is not None:
81+
self._cursor_grp.x = p[0] - self._x_offset
82+
self._cursor_grp.y = p[1] - self._y_offset
83+
return True, p
84+
else:
85+
return False, None
86+
87+
def poke(self, location=None):
88+
"""Force a bitmap refresh."""
89+
self._display_grp.remove(self._cursor_grp)
90+
if location is not None:
91+
self._cursor_grp.x = location[0] - self._x_offset
92+
self._cursor_grp.y = location[1] - self._y_offset
93+
self._display_grp.append(self._cursor_grp)
94+
95+
################################################################################
96+
97+
class CursorPoller(object):
98+
"""Get 'pressed' and location updates from a D-Pad/joystick device."""
99+
100+
def __init__(self, splash, cursor_bmp):
101+
logging.getLogger('Paint').debug('Creating a CursorPoller')
102+
self._mouse_cursor = Cursor(board.DISPLAY, display_group=splash, bmp=cursor_bmp, cursor_speed=2)
103+
self._x_offset = cursor_bmp.width // 2
104+
self._y_offset = cursor_bmp.height // 2
105+
self._cursor = DebouncedCursorManager(self._mouse_cursor)
106+
self._logger = logging.getLogger('Paint')
107+
108+
def poll(self):
109+
"""Check for input. Returns press (a bool) and it's location ((x,y) or None)"""
110+
location = None
111+
self._cursor.update()
112+
button = self._cursor.held
113+
if button:
114+
location = (self._mouse_cursor.x + self._x_offset, self._mouse_cursor.y + self._y_offset)
115+
return button, location
116+
117+
def poke(self, x=None, y=None):
118+
"""Force a bitmap refresh."""
119+
self._mouse_cursor.hide()
120+
self._mouse_cursor.show()
121+
122+
################################################################################
123+
124+
class Paint(object):
125+
126+
def __init__(self, display=board.DISPLAY):
127+
self._logger = logging.getLogger("Paint")
128+
self._logger.setLevel(logging.DEBUG)
129+
self._display = display
130+
self._w = self._display.width
131+
self._h = self._display.height
132+
self._x = self._w // 2
133+
self._y = self._h // 2
134+
135+
self._splash = displayio.Group(max_size=5)
136+
137+
self._bg_bitmap = displayio.Bitmap(self._w, self._h, 1)
138+
self._bg_palette = displayio.Palette(1)
139+
self._bg_palette[0] = Color.BLACK
140+
self._bg_sprite = displayio.TileGrid(self._bg_bitmap,
141+
pixel_shader=self._bg_palette,
142+
x=0, y=0)
143+
self._splash.append(self._bg_sprite)
144+
145+
self._palette_bitmap = displayio.Bitmap(self._w, self._h, 5)
146+
self._palette_palette = displayio.Palette(len(Color.colors))
147+
for i, c in enumerate(Color.colors):
148+
self._palette_palette[i] = c
149+
self._palette_sprite = displayio.TileGrid(self._palette_bitmap,
150+
pixel_shader=self._palette_palette,
151+
x=0, y=0)
152+
self._splash.append(self._palette_sprite)
153+
154+
self._fg_bitmap = displayio.Bitmap(self._w, self._h, 5)
155+
self._fg_palette = displayio.Palette(len(Color.colors))
156+
for i, c in enumerate(Color.colors):
157+
self._fg_palette[i] = c
158+
self._fg_sprite = displayio.TileGrid(self._fg_bitmap,
159+
pixel_shader=self._fg_palette,
160+
x=0, y=0)
161+
self._splash.append(self._fg_sprite)
162+
163+
self._color_palette = self._make_color_palette()
164+
self._splash.append(self._color_palette)
165+
166+
self._display.show(self._splash)
167+
self._display.refresh_soon()
168+
gc.collect()
169+
self._display.wait_for_frame()
170+
171+
if hasattr(board, 'TOUCH_XL'):
172+
self._poller = TouchscreenPoller(self._splash, self._cursor_bitmap())
173+
elif hasattr(board, 'BUTTON_CLOCK'):
174+
self._poller = CursorPoller(self._splash, self._cursor_bitmap())
175+
else:
176+
raise AttributeError('PYOA requires a touchscreen or cursor.')
177+
178+
self._pressed = False
179+
self._last_pressed = False
180+
self._location = None
181+
self._last_location = None
182+
183+
self._pencolor = 7
184+
185+
def _make_color_palette(self):
186+
self._palette_bitmap = displayio.Bitmap(self._w // 10, self._h, 5)
187+
self._palette_palette = displayio.Palette(len(Color.colors))
188+
swatch_height = self._h // len(Color.colors)
189+
for i, c in enumerate(Color.colors):
190+
self._palette_palette[i] = c
191+
for y in range(swatch_height):
192+
for x in range(self._w // 10):
193+
self._palette_bitmap[x, swatch_height * i + y] = i
194+
self._palette_bitmap[self._w // 10 - 1, swatch_height * i + y] = 7
195+
196+
197+
return displayio.TileGrid(self._palette_bitmap,
198+
pixel_shader=self._palette_palette,
199+
x=0, y=0)
200+
201+
def _cursor_bitmap(self):
202+
bmp = displayio.Bitmap(9, 9, 3)
203+
for i in range(9):
204+
bmp[4, i] = 1
205+
bmp[i, 4] = 1
206+
bmp[4, 4] = 0
207+
return bmp
208+
209+
def _plot(self, x, y, c):
210+
try:
211+
self._fg_bitmap[int(x), int(y)] = c
212+
except IndexError:
213+
pass
214+
215+
#pylint:disable=too-many-branches,too-many-statements
216+
217+
def _goto(self, start, end):
218+
"""Draw a line from the previous position to the current one.
219+
220+
:param start: a tuple of (x, y) coordinatess to fram from
221+
:param end: a tuple of (x, y) coordinates to draw to
222+
"""
223+
x0 = start[0]
224+
y0 = start[1]
225+
x1 = end[0]
226+
y1 = end[1]
227+
self._logger.debug("* GoTo from (%d, %d) to (%d, %d)", x0, y0, x1, y1)
228+
steep = abs(y1 - y0) > abs(x1 - x0)
229+
rev = False
230+
dx = x1 - x0
231+
232+
if steep:
233+
x0, y0 = y0, x0
234+
x1, y1 = y1, x1
235+
dx = x1 - x0
236+
237+
if x0 > x1:
238+
rev = True
239+
dx = x0 - x1
240+
241+
dy = abs(y1 - y0)
242+
err = dx / 2
243+
ystep = -1
244+
if y0 < y1:
245+
ystep = 1
246+
247+
while (not rev and x0 <= x1) or (rev and x1 <= x0):
248+
if steep:
249+
try:
250+
self._plot(int(y0), int(x0), self._pencolor)
251+
except IndexError:
252+
pass
253+
self._x = y0
254+
self._y = x0
255+
self._poller.poke((int(y0), int(x0)))
256+
time.sleep(0.003)
257+
else:
258+
try:
259+
self._plot(int(x0), int(y0), self._pencolor)
260+
except IndexError:
261+
pass
262+
self._x = x0
263+
self._y = y0
264+
self._poller.poke((int(x0), int(y0)))
265+
time.sleep(0.003)
266+
err -= dy
267+
if err < 0:
268+
y0 += ystep
269+
err += dx
270+
if rev:
271+
x0 -= 1
272+
else:
273+
x0 += 1
274+
275+
#pylint:enable=too-many-branches,too-many-statements
276+
277+
278+
def _pick_color(self, location):
279+
swatch_height = self._h // len(Color.colors)
280+
picked = location[1] // swatch_height
281+
self._pencolor = picked
282+
283+
def _handle_motion(self, start, end):
284+
self._logger.debug('Moved: (%d, %d) -> (%d, %d)', start[0], start[1], end[0], end[1])
285+
self._goto(start, end)
286+
287+
def _handle_press(self, location):
288+
self._logger.debug('Pressed!')
289+
if location[0] < self._w // 10: # in color picker
290+
self._pick_color(location)
291+
else:
292+
self._plot(location[0], location[1], self._pencolor)
293+
self._poller.poke()
294+
295+
def _handle_release(self, location):
296+
self._logger.debug('Released!')
297+
298+
@property
299+
def _was_just_pressed(self):
300+
return self._pressed and not self._last_pressed
301+
302+
@property
303+
def _was_just_released(self):
304+
return not self._pressed and self._last_pressed
305+
306+
@property
307+
def _did_move(self):
308+
if self._location is not None and self._last_location is not None:
309+
x_changed = self._location[0] != self._last_location[0]
310+
y_changed = self._location[1] != self._last_location[1]
311+
return x_changed or y_changed
312+
313+
def _update(self):
314+
self._last_pressed, self._last_location = self._pressed, self._location
315+
self._pressed, self._location = self._poller.poll()
316+
317+
318+
def run(self):
319+
"""Run the painting program."""
320+
while True:
321+
self._update()
322+
if self._was_just_pressed:
323+
self._handle_press(self._location)
324+
elif self._was_just_released:
325+
self._handle_release(self._location)
326+
if self._did_move and self._pressed:
327+
self._handle_motion(self._last_location, self._location)
328+
time.sleep(0.1)
329+
330+
painter = Paint()
331+
painter.run()

0 commit comments

Comments
 (0)