Skip to content

Commit 9e4649f

Browse files
committed
Add waitKeyEx()
Fixes #15
1 parent 3ca9229 commit 9e4649f

File tree

3 files changed

+90
-25
lines changed

3 files changed

+90
-25
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ If you need help understanding how to use these functions, see the documentation
263263
| --- | --- |
264264
| `cv.imshow(winname, mat) -> None`<br>Displays an image in the specified window.<br>[Documentation](https://docs.opencv.org/4.11.0/d7/dfc/group__highgui.html#ga453d42fe4cb60e5723281a89973ee563) | `winname` must actually be a display driver object that implements an `imshow()` method that takes a NumPy array as input. |
265265
| `cv.waitKey([, delay]) -> retval`<br>Waits for a pressed key.<br>[Documentation](https://docs.opencv.org/4.11.0/d7/dfc/group__highgui.html#ga5628525ad33f52eab17feebcfba38bd7) | Input is taken from `sys.stdin`, which is typically the REPL. |
266+
| `cv.waitKeyEx([, delay]) -> retval`<br>Similar to waitKey, but returns full key code.<br>[Documentation](https://docs.opencv.org/4.11.0/d7/dfc/group__highgui.html#ga5628525ad33f52eab17feebcfba38bd7) | Input is taken from `sys.stdin`, which is typically the REPL.<br>Full key code is implementation specific, so special key codes in MicroPython will not match other Python environments. |
266267

267268
# Building
268269

src/highgui.cpp

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,23 @@ mp_obj_t cv2_highgui_imshow(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k
4646
}
4747

4848
mp_obj_t cv2_highgui_waitKey(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
49+
// Call waitKeyEx to do the heavy lifting
50+
mp_obj_t key = cv2_highgui_waitKeyEx(n_args, pos_args, kw_args);
51+
52+
// Get the key code as an integer
53+
int32_t key_code = mp_obj_get_int(key);
54+
55+
// If the key code is -1, it means no key was pressed
56+
if (key_code == -1) {
57+
// Return the original key object
58+
return key;
59+
} else {
60+
// Return the last byte of the key code
61+
return MP_OBJ_NEW_SMALL_INT(key_code & 0xFF);
62+
}
63+
}
64+
65+
mp_obj_t cv2_highgui_waitKeyEx(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
4966
// Define the arguments
5067
enum { Arg_delay };
5168
static const mp_arg_t allowed_args[] = {
@@ -88,35 +105,79 @@ mp_obj_t cv2_highgui_waitKey(size_t n_args, const mp_obj_t *pos_args, mp_map_t *
88105
// of 0 to wait indefinitely, whereas `select.poll` uses -1
89106
mp_obj_t timeout = MP_OBJ_NEW_SMALL_INT(delay <= 0 ? -1 : delay);
90107

91-
// TODO: Some key presses return multiple characters (eg. up arrow key
92-
// returns 3 characters: "\x1b[A"). Need to handle this case properly.
93-
// Should also look into implementing waitKeyEx() for these extra cases
94-
95-
// Call `poll.poll(timeout)`
108+
// Load the `poll.poll()` method to check for key presses
96109
mp_obj_t poll_poll_method[3];
97110
mp_load_method(poll_obj, MP_QSTR_poll, poll_poll_method);
98-
poll_poll_method[2] = timeout;
99-
mp_obj_t result = mp_call_method_n_kw(1, 0, poll_poll_method);
100-
101-
// Extract the items from the result list
102-
mp_obj_t *items;
103-
size_t len;
104-
mp_obj_list_get(result, &len, &items);
105-
106-
// Check if any items were returned
107-
if(len == 0) {
108-
// If no items were returned, return -1 to indicate no key was pressed
109-
return MP_OBJ_NEW_SMALL_INT(-1);
110-
}
111111

112-
// Since something was returned, a key was pressed. We need to extract it
113-
// with `sys.stdin.read(1)`
112+
// Load the `sys.stdin.read(1)` method to read a single character
114113
mp_obj_t read_method[3];
115114
mp_load_method(stdin_obj, MP_QSTR_read, read_method);
116115
read_method[2] = MP_OBJ_NEW_SMALL_INT(1);
117-
mp_obj_t key_str = mp_call_method_n_kw(1, 0, read_method);
118116

119-
// Convert the key character to an integer and return it
120-
const char *key_chars = mp_obj_str_get_str(key_str);
121-
return MP_OBJ_NEW_SMALL_INT(key_chars[0]);
117+
// Initialize key code to -1, which indicates no key was pressed
118+
int32_t key_code = -1;
119+
120+
// Some key presses return multiple bytes (eg. up arrow key returns 3 bytes:
121+
// `\x1b[A`). To handle this, we will loop until no more bytes are available
122+
for (int i = 0; true; i++) {
123+
// Call `poll.poll(timeout)` if this is the first iteration, otherwise
124+
// call `poll.poll(1)` to quickly check for any remaining bytes. Can't
125+
// wait 0ms, because it takes a moment for all bytes to arrive
126+
poll_poll_method[2] = i == 0 ? timeout : MP_OBJ_NEW_SMALL_INT(1);
127+
mp_obj_t result = mp_call_method_n_kw(1, 0, poll_poll_method);
128+
129+
// Extract the items from the result list
130+
mp_obj_t *items;
131+
size_t len;
132+
mp_obj_list_get(result, &len, &items);
133+
134+
// Check if any items were returned
135+
if(len == 0) {
136+
// No more bytes available, so we're done. If multiple bytes were
137+
// read, we want the last byte to be 0 so it doesn't get confused
138+
// in `waitKey()` with a normal key press. So we can simply shift
139+
// the key code left by 8 bits again
140+
if (i > 1) {
141+
key_code <<= 8;
142+
}
143+
break;
144+
}
145+
146+
// Since something was returned, a byte is available. We need to
147+
// extract it by calling `sys.stdin.read(1)`
148+
mp_obj_t byte_str = mp_call_method_n_kw(1, 0, read_method);
149+
150+
// Convert the byte object to an actual byte
151+
uint8_t byte_val = mp_obj_str_get_str(byte_str)[0];
152+
153+
// Check which iteration this is
154+
if(i == 0) {
155+
// This is the first iteration, set the key code to this byte
156+
key_code = byte_val;
157+
158+
// Special keys always start with an escape character (0x1b). If
159+
// this is not the escape character, we can assume it's a normal key
160+
// press and break immediately. This helps mitigate the problem of
161+
// interpreting 2 key simultaneous key presses as 1 special key
162+
if (byte_val != 0x1b) {
163+
break;
164+
}
165+
} else if (i == 1) {
166+
// This is the second iteration, meaning the first byte was the
167+
// escape character. We don't want that to be part of the key code
168+
// (special keys will be indicated by having multiple bytes, and the
169+
// last byte being zero), so we'll just overwrite the key code with
170+
// the second byte
171+
key_code = byte_val;
172+
} else {
173+
// This is a subsequent iteration, meaning we have already read the
174+
// escape character and the second byte. For all subsequent bytes,
175+
// we will shift the key code left by 8 bits and add the new byte to
176+
// it to create a multi-byte key
177+
key_code = (key_code << 8) | byte_val;
178+
}
179+
}
180+
181+
// Return the final key code
182+
return MP_OBJ_NEW_SMALL_INT(key_code);
122183
}

src/highgui.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
// Function declarations
55
extern mp_obj_t cv2_highgui_imshow(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
66
extern mp_obj_t cv2_highgui_waitKey(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
7+
extern mp_obj_t cv2_highgui_waitKeyEx(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
78

89
// Python references to the functions
910
static MP_DEFINE_CONST_FUN_OBJ_KW(cv2_highgui_imshow_obj, 2, cv2_highgui_imshow);
1011
static MP_DEFINE_CONST_FUN_OBJ_KW(cv2_highgui_waitKey_obj, 0, cv2_highgui_waitKey);
12+
static MP_DEFINE_CONST_FUN_OBJ_KW(cv2_highgui_waitKeyEx_obj, 0, cv2_highgui_waitKeyEx);
1113

1214
// Global definitions for functions and constants
1315
#define OPENCV_HIGHGUI_GLOBALS \
1416
/* Functions */ \
1517
{ MP_ROM_QSTR(MP_QSTR_imshow), MP_ROM_PTR(&cv2_highgui_imshow_obj) }, \
16-
{ MP_ROM_QSTR(MP_QSTR_waitKey), MP_ROM_PTR(&cv2_highgui_waitKey_obj) }
18+
{ MP_ROM_QSTR(MP_QSTR_waitKey), MP_ROM_PTR(&cv2_highgui_waitKey_obj) }, \
19+
{ MP_ROM_QSTR(MP_QSTR_waitKeyEx), MP_ROM_PTR(&cv2_highgui_waitKeyEx_obj) }

0 commit comments

Comments
 (0)