Skip to content

Commit 65b5f42

Browse files
initial commit
1 parent e45aac5 commit 65b5f42

37 files changed

+6336
-2
lines changed

ALookCom/CohenSutherlandLc.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
## Cohen Sutherland line clipping algo
2+
3+
def lineClipping(x1, y1, x2, y2, xmin, ymin, xmax, ymax):
4+
## bit mask
5+
INSIDE, LEFT, RIGHT, LOWER, UPPER = 0, 1, 2, 4, 8
6+
7+
def _getclip(xa, ya):
8+
p = INSIDE
9+
10+
if xa < xmin:
11+
p |= LEFT
12+
elif xa > xmax:
13+
p |= RIGHT
14+
15+
if ya < ymin:
16+
p |= LOWER
17+
elif ya > ymax:
18+
p |= UPPER
19+
20+
return p
21+
22+
# check for trivially outside lines
23+
k1 = _getclip(x1, y1)
24+
k2 = _getclip(x2, y2)
25+
26+
# %% examine non-trivially outside points
27+
# bitwise OR |
28+
while (k1 | k2) != 0: # if both points are inside box (0000) , ACCEPT trivial whole line in box
29+
30+
# if line trivially outside window, REJECT
31+
if (k1 & k2) != 0: # bitwise AND &
32+
return None, None, None, None
33+
34+
# non-trivial case, at least one point outside window
35+
# this is not a bitwise or, it's the word "or"
36+
opt = k1 or k2 # take first non-zero point, short circuit logic
37+
if opt & UPPER: # these are bitwise ANDS
38+
x = x1 + (x2 - x1) * (ymax - y1) // (y2 - y1)
39+
y = ymax
40+
elif opt & LOWER:
41+
x = x1 + (x2 - x1) * (ymin - y1) // (y2 - y1)
42+
y = ymin
43+
elif opt & RIGHT:
44+
y = y1 + (y2 - y1) * (xmax - x1) // (x2 - x1)
45+
x = xmax
46+
elif opt & LEFT:
47+
y = y1 + (y2 - y1) * (xmin - x1) // (x2 - x1)
48+
x = xmin
49+
else:
50+
raise RuntimeError('Undefined clipping state')
51+
52+
if opt == k1:
53+
x1, y1 = x, y
54+
k1 = _getclip(x1, y1)
55+
elif opt == k2:
56+
x2, y2 = x, y
57+
k2 = _getclip(x2, y2)
58+
59+
return x1, y1, x2, y2

ALookCom/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# use this to import files that are inside this module
2+
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

ALookCom/anim.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import os
2+
3+
import cv2
4+
5+
import utils
6+
from img import Img
7+
from commandPub import CommandPub
8+
9+
10+
class Anim:
11+
def __init__(self, com):
12+
self.cmd = CommandPub(com)
13+
self.img = img = Img(com)
14+
self.com = com
15+
16+
## return the number of elements before the fist difference
17+
def _sameValueLen(self, la, lb):
18+
assert(len(la) == len(lb)), "different length is not handled"
19+
20+
cnt = 0
21+
for a, b in zip(la, lb):
22+
if a == b:
23+
cnt += 1
24+
else:
25+
break
26+
27+
return cnt
28+
29+
## return the number of elements before the fist difference, start counting from the end
30+
def _sameValueLenReverse(self, la, lb):
31+
assert(len(la) == len(lb)), "different length is not handled"
32+
33+
cnt = 0
34+
i = len(la)
35+
while i > 0:
36+
i -= 1
37+
if la[i] == lb[i]:
38+
cnt += 1
39+
else:
40+
break
41+
42+
return cnt
43+
44+
## encode full image for animation
45+
def _encodFullImgCmd(self, img):
46+
height = img.shape[0]
47+
width = img.shape[1]
48+
49+
## compress img 4 bit per pixel
50+
encodedImg = []
51+
for i in range(height):
52+
byte = 0
53+
shift = 0
54+
for j in range(width):
55+
## convert gray8bit to gray4bit
56+
pxl = round(img[i, j] / 17)
57+
58+
## compress 4 bit per pixel
59+
byte += pxl << shift
60+
shift += 4
61+
if shift == 8:
62+
encodedImg.append(byte)
63+
byte = 0
64+
shift = 0
65+
if shift != 0:
66+
encodedImg.append(byte)
67+
68+
return encodedImg
69+
70+
### reduce pixel range to 16 level of grey but keep it on 1 byte
71+
def _reducePxlRange(self, img):
72+
for i, line in enumerate(img):
73+
for j, plx in enumerate(line):
74+
pxl = round(plx / 17)
75+
img[i][j] = pxl
76+
77+
return img
78+
79+
## prepare command for animation saving
80+
def formatCmd(self, id, imgs):
81+
## first image encoded as complete image
82+
rawAnim = self._encodFullImgCmd(imgs[0])
83+
imgSize = len(rawAnim)
84+
85+
prev = self._reducePxlRange(imgs[0])
86+
87+
width = imgs[0].shape[1]
88+
89+
for img in imgs[1:]:
90+
img = self._reducePxlRange(img)
91+
92+
## crop width
93+
lines = []
94+
for i, line in enumerate(img):
95+
## crop the line
96+
crop = []
97+
xOffset = self._sameValueLen(line, prev[i])
98+
if xOffset != len(line):
99+
end = self._sameValueLenReverse(line, prev[i])
100+
crop = line[xOffset : len(line) - end]
101+
102+
## transform to 4bpp compression
103+
byte = 0
104+
shift = 0
105+
encCrop = []
106+
for pxl in crop:
107+
## compress 4 bit per pixel
108+
byte += pxl << shift
109+
shift += 4
110+
if shift == 8:
111+
encCrop.append(byte)
112+
byte = 0
113+
shift = 0
114+
if shift != 0:
115+
encCrop.append(byte)
116+
117+
lines.append({'offset': xOffset, 'widthPxl': len(crop), 'encodedData': encCrop})
118+
119+
## crop height
120+
class BreakIt(Exception): pass
121+
yOffset = 0
122+
try:
123+
for line in lines:
124+
if line['widthPxl'] == 0:
125+
yOffset += 1
126+
else:
127+
## break only the for loop
128+
raise BreakIt
129+
except BreakIt:
130+
pass
131+
132+
height = len(lines) - yOffset
133+
i = yOffset + height
134+
try:
135+
while (i > 0) and (height > 0):
136+
i -= 1
137+
if lines[i]['widthPxl'] == 0:
138+
height -= 1
139+
else:
140+
## break only the while loop
141+
raise BreakIt
142+
except BreakIt:
143+
pass
144+
145+
## restitue data
146+
frame = []
147+
lHeight = utils.uShortToList(height)
148+
lYOffset = utils.sShortToList(yOffset)
149+
frame += lHeight + lYOffset
150+
for line in lines[yOffset : yOffset + height]:
151+
widthPxl = line['widthPxl']
152+
lwidthPxl = utils.uShortToList(widthPxl)
153+
lXOffset = utils.sShortToList(line['offset'])
154+
frame += lwidthPxl + lXOffset + line['encodedData']
155+
156+
rawAnim += frame
157+
prev = img
158+
159+
## start save animation command
160+
data = [id]
161+
data += utils.intToList(len(rawAnim))
162+
data += utils.intToList(imgSize)
163+
data += utils.uShortToList(width)
164+
cmds = [self.com.formatFrame(0x95, data)]
165+
166+
## pack data in commands
167+
nbDataMax = self.com.getDataSizeMax()
168+
i = 0
169+
while i < len(rawAnim):
170+
if nbDataMax > (len(rawAnim) - i):
171+
nbDataMax = (len(rawAnim) - i)
172+
173+
cmds += [self.com.formatFrame(0x95, rawAnim[i:(nbDataMax + i)])]
174+
i += nbDataMax
175+
176+
return cmds
177+
178+
## prepare command for animation saving
179+
def formatCmdFolder(self, id, folder):
180+
## list file in folder
181+
imgs = []
182+
for f in os.listdir(folder):
183+
path = os.path.join(folder, f)
184+
if os.path.isfile(path):
185+
img = cv2.imread(path)
186+
img = cv2.rotate(img, cv2.ROTATE_180)
187+
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
188+
imgs.append(img)
189+
190+
return self.formatCmd(id, imgs)
191+
192+
## prepare command for animation saving
193+
def formatCmdGif(self, id, gif, invertColor):
194+
195+
cap = cv2.VideoCapture(gif)
196+
197+
imgs = []
198+
while True:
199+
ret, frame = cap.read()
200+
if not ret:
201+
break
202+
203+
frame = cv2.rotate(frame, cv2.ROTATE_180)
204+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
205+
206+
if invertColor:
207+
## invert color
208+
frame = 255 - frame
209+
210+
imgs.append(frame)
211+
212+
213+
cap.release()
214+
215+
return self.formatCmd(id, imgs)
216+
217+
## save an animation, takes all images of a folder
218+
def saveAnimation(self, id, folder = "anim/cube-302x256"):
219+
## convert image
220+
cmds = self.formatCmdFolder(id, folder)
221+
222+
## send commands
223+
for c in cmds:
224+
self.com.sendRawData(c)
225+
if not self.com.receiveAck():
226+
return False
227+
228+
return True
229+
230+
## save an animation from a gif
231+
def saveAnimationGif(self, id, gif = "anim/monkey-228x228.gif", invertColor = True):
232+
## convert image
233+
cmds = self.formatCmdGif(id, gif, invertColor)
234+
235+
## send commands
236+
for c in cmds:
237+
self.com.sendRawData(c)
238+
if not self.com.receiveAck():
239+
return False
240+
241+
return True

ALookCom/ble.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## BLE abstraction Layer
2+
3+
import asyncio
4+
from bleak import BleakScanner
5+
6+
class Ble:
7+
## get device rssi
8+
def __getRssi(self, dev):
9+
return dev.rssi
10+
11+
## runner to scan devices
12+
async def __runScanDevices(self):
13+
devices = await BleakScanner.discover(timeout = 2.5)
14+
for d in devices:
15+
self.__scannedDev.append(d)
16+
17+
## scan for devices
18+
def scanDevices(self):
19+
self.__scannedDev = []
20+
21+
loop = asyncio.get_event_loop()
22+
loop.run_until_complete(self.__runScanDevices())
23+
24+
## sort by RSSI
25+
self.__scannedDev.sort(key=self.__getRssi, reverse=True)
26+
27+
return self.__scannedDev
28+
29+
def getAdvtManufacturerData(self, dev):
30+
return dev.metadata['manufacturer_data']

0 commit comments

Comments
 (0)