• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricks-micropython / 19806274214

30 Nov 2025 11:10PM UTC coverage: 56.56% (-0.1%) from 56.677%
19806274214

Pull #426

github

web-flow
Merge 38f684caf into 7a0eadb24
Pull Request #426: Fonts support and terminal like text output

80 of 159 new or added lines in 4 files covered. (50.31%)

2 existing lines in 1 file now uncovered.

4600 of 8133 relevant lines covered (56.56%)

16547299.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pybricks/parameters/pb_type_image.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2025 The Pybricks Authors
3

4
#include "py/mpconfig.h"
5

6
#if PYBRICKS_PY_PARAMETERS_IMAGE
7

8
#if !MICROPY_ENABLE_FINALISER
9
#error "MICROPY_ENABLE_FINALISER must be enabled."
10
#endif
11

12
#include <umm_malloc.h>
13

14
#include "py/mphal.h"
15
#include "py/obj.h"
16

17
#include <pybricks/parameters.h>
18
#include <pybricks/util_mp/pb_kwarg_helper.h>
19
#include <pybricks/util_mp/pb_obj_helper.h>
20

21
#include <pbio/image.h>
22
#include <pbio/int_math.h>
23

24
#include <pbdrv/display.h>
25

26
extern const mp_obj_type_t pb_type_Image;
27

28
// pybricks.parameters.Image class object
29
typedef struct _pb_type_Image_obj_t {
30
    mp_obj_base_t base;
31
    // For images with shared memory, we need to keep a reference to the object
32
    // that owns the memory.
33
    mp_obj_t owner;
34
    pbio_image_t image;
35
    bool is_display;
36
} pb_type_Image_obj_t;
37

38
static int get_color(mp_obj_t obj) {
×
39
    uint8_t max = pbdrv_display_get_max_value();
×
40
    if (obj == mp_const_none) {
×
41
        return max;
×
42
    }
43
    if (mp_obj_is_int(obj)) {
×
44
        return mp_obj_get_int(obj);
×
45
    }
46
    const pbio_color_hsv_t *hsv = pb_type_Color_get_hsv(obj);
×
47
    int32_t v = pbio_int_math_bind(hsv->v, 0, 100);
×
48
    return max - v * max / 100;
×
49
}
50

51
mp_obj_t pb_type_Image_display_obj_new(void) {
×
52
    pb_type_Image_obj_t *self = mp_obj_malloc(pb_type_Image_obj_t, &pb_type_Image);
×
53
    self->owner = MP_OBJ_NULL;
×
54
    self->is_display = true;
×
55
    self->image = *pbdrv_display_get_image();
×
56

57
    return MP_OBJ_FROM_PTR(self);
×
58
}
59

60
static mp_obj_t pb_type_Image_make_new(const mp_obj_type_t *type,
×
61
    size_t n_args, size_t n_kw, const mp_obj_t *args) {
62
    PB_PARSE_ARGS_CLASS(n_args, n_kw, args,
×
63
        PB_ARG_REQUIRED(source),
64
        PB_ARG_DEFAULT_FALSE(sub),
65
        PB_ARG_DEFAULT_INT(x1, 0),
66
        PB_ARG_DEFAULT_INT(y1, 0),
67
        PB_ARG_DEFAULT_NONE(x2),
68
        PB_ARG_DEFAULT_NONE(y2));
69

70
    pb_type_Image_obj_t *self;
71

72
    pb_assert_type(source_in, &pb_type_Image);
×
73
    pb_type_Image_obj_t *source = MP_OBJ_TO_PTR(source_in);
×
74
    if (!mp_obj_is_true(sub_in)) {
×
75
        // Copy.
76
        int width = source->image.width;
×
77
        int height = source->image.height;
×
78

79
        void *buf = umm_malloc(width * height * sizeof(uint8_t));
×
80
        if (!buf) {
×
81
            mp_raise_type(&mp_type_MemoryError);
×
82
        }
83

84
        self = mp_obj_malloc_with_finaliser(pb_type_Image_obj_t, &pb_type_Image);
×
85
        self->owner = MP_OBJ_NULL;
×
86
        self->is_display = false;
×
87
        pbio_image_init(&self->image, buf, width, height, width);
×
NEW
88
        self->image.print_font = source->image.print_font;
×
NEW
89
        self->image.print_value = source->image.print_value;
×
UNCOV
90
        pbio_image_draw_image(&self->image, &source->image, 0, 0);
×
91
    } else {
92
        // Sub-image.
93
        mp_int_t x1 = pb_obj_get_int(x1_in);
×
94
        mp_int_t y1 = pb_obj_get_int(y1_in);
×
95
        mp_int_t x2 = x2_in == mp_const_none ? source->image.width - 1 : pb_obj_get_int(x2_in);
×
96
        mp_int_t y2 = y2_in == mp_const_none ? source->image.height - 1 : pb_obj_get_int(y2_in);
×
97
        self = mp_obj_malloc(pb_type_Image_obj_t, &pb_type_Image);
×
98
        self->owner = source_in;
×
99
        self->is_display = false;
×
100
        int width = x2 - x1 + 1;
×
101
        int height = y2 - y1 + 1;
×
102
        pbio_image_init_sub(&self->image, &source->image, x1, y1, width, height);
×
103
    }
104

105
    return MP_OBJ_FROM_PTR(self);
×
106
}
107

108
static mp_obj_t pb_type_Image_close(mp_obj_t self_in) {
×
109
    pb_type_Image_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
110
    // If we own the memory, free it.
111
    if (self->owner == MP_OBJ_NULL && !self->is_display && self->image.pixels) {
×
112
        umm_free(self->image.pixels);
×
113
        self->image.pixels = NULL;
×
114
    }
115
    return mp_const_none;
×
116
}
117
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Image_close_obj, pb_type_Image_close);
118

119
static mp_obj_t pb_type_Image_empty(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
120
    PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
×
121
        PB_ARG_DEFAULT_NONE(width),
122
        PB_ARG_DEFAULT_NONE(height));
123

124
    pbio_image_t *display = pbdrv_display_get_image();
×
125

126
    mp_int_t width = width_in == mp_const_none ? display->width : mp_obj_get_int(width_in);
×
127
    mp_int_t height = height_in == mp_const_none ? display->height : mp_obj_get_int(height_in);
×
128

129
    if (width < 1 || height < 1) {
×
130
        mp_raise_ValueError(MP_ERROR_TEXT("Image width or height is less than 1"));
×
131
    }
132

133
    void *buf = umm_malloc(width * height * sizeof(uint8_t));
×
134
    if (!buf) {
×
135
        mp_raise_type(&mp_type_MemoryError);
×
136
    }
137

138
    pb_type_Image_obj_t *self = mp_obj_malloc_with_finaliser(pb_type_Image_obj_t, &pb_type_Image);
×
139
    self->owner = MP_OBJ_NULL;
×
140
    self->is_display = false;
×
141
    pbio_image_init(&self->image, buf, width, height, width);
×
NEW
142
    self->image.print_font = display->print_font;
×
NEW
143
    self->image.print_value = display->print_value;
×
UNCOV
144
    pbio_image_fill(&self->image, 0);
×
145

146
    return MP_OBJ_FROM_PTR(self);
×
147
}
148
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_empty_fun_obj, 0, pb_type_Image_empty);
149
static MP_DEFINE_CONST_STATICMETHOD_OBJ(pb_type_Image_empty_obj, MP_ROM_PTR(&pb_type_Image_empty_fun_obj));
150

151
static void pb_type_Image_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
×
152
    // Read only
153
    if (dest[0] == MP_OBJ_NULL) {
×
154
        pb_type_Image_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
155
        if (attr == MP_QSTR_width) {
×
156
            dest[0] = mp_obj_new_int(self->image.width);
×
157
            return;
×
158
        }
159
        if (attr == MP_QSTR_height) {
×
160
            dest[0] = mp_obj_new_int(self->image.height);
×
161
            return;
×
162
        }
163
    }
164
    // Attribute not found, continue lookup in locals dict.
165
    dest[1] = MP_OBJ_SENTINEL;
×
166
}
167

168
static mp_obj_t pb_type_Image_clear(mp_obj_t self_in) {
×
169
    pb_type_Image_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
170

171
    pbio_image_fill(&self->image, 0);
×
172

173
    if (self->is_display) {
×
174
        pbdrv_display_update();
×
175
    }
176

177
    return mp_const_none;
×
178
}
179
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Image_clear_obj, pb_type_Image_clear);
180

181
static mp_obj_t pb_type_Image_load_image(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
182
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
183
        pb_type_Image_obj_t, self,
184
        PB_ARG_REQUIRED(source));
185

186
    pb_assert_type(source_in, &pb_type_Image);
×
187
    pb_type_Image_obj_t *source = MP_OBJ_TO_PTR(source_in);
×
188

189
    int x = (self->image.width - source->image.width) / 2;
×
190
    int y = (self->image.height - source->image.height) / 2;
×
191

192
    pbio_image_fill(&self->image, 0);
×
193
    pbio_image_draw_image(&self->image, &source->image, x, y);
×
194

195
    if (self->is_display) {
×
196
        pbdrv_display_update();
×
197
    }
198

199
    return mp_const_none;
×
200
}
201
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_load_image_obj, 1, pb_type_Image_load_image);
202

203
static mp_obj_t pb_type_Image_draw_image(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
204
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
205
        pb_type_Image_obj_t, self,
206
        PB_ARG_REQUIRED(x),
207
        PB_ARG_REQUIRED(y),
208
        PB_ARG_REQUIRED(source),
209
        PB_ARG_DEFAULT_NONE(transparent));
210

211
    mp_int_t x = mp_obj_get_int(x_in);
×
212
    mp_int_t y = mp_obj_get_int(y_in);
×
213
    pb_assert_type(source_in, &pb_type_Image);
×
214
    pb_type_Image_obj_t *source = MP_OBJ_TO_PTR(source_in);
×
215

216
    if (transparent_in == mp_const_none) {
×
217
        pbio_image_draw_image(&self->image, &source->image, x, y);
×
218
    } else {
219
        int transparent_value = get_color(transparent_in);
×
220

221
        pbio_image_draw_image_transparent(&self->image, &source->image, x, y, transparent_value);
×
222
    }
223

224
    if (self->is_display) {
×
225
        pbdrv_display_update();
×
226
    }
227

228
    return mp_const_none;
×
229
}
230
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_draw_image_obj, 1, pb_type_Image_draw_image);
231

232
static mp_obj_t pb_type_Image_draw_pixel(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
233
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
234
        pb_type_Image_obj_t, self,
235
        PB_ARG_REQUIRED(x),
236
        PB_ARG_REQUIRED(y),
237
        PB_ARG_DEFAULT_NONE(color));
238

239
    mp_int_t x = pb_obj_get_int(x_in);
×
240
    mp_int_t y = pb_obj_get_int(y_in);
×
241
    int color = get_color(color_in);
×
242

243
    pbio_image_draw_pixel(&self->image, x, y, color);
×
244

245
    if (self->is_display) {
×
246
        pbdrv_display_update();
×
247
    }
248

249
    return mp_const_none;
×
250
}
251
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_draw_pixel_obj, 1, pb_type_Image_draw_pixel);
252

253
static mp_obj_t pb_type_Image_draw_line(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
254
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
255
        pb_type_Image_obj_t, self,
256
        PB_ARG_REQUIRED(x1),
257
        PB_ARG_REQUIRED(y1),
258
        PB_ARG_REQUIRED(x2),
259
        PB_ARG_REQUIRED(y2),
260
        PB_ARG_DEFAULT_INT(width, 1),
261
        PB_ARG_DEFAULT_NONE(color));
262

263
    mp_int_t x1 = pb_obj_get_int(x1_in);
×
264
    mp_int_t y1 = pb_obj_get_int(y1_in);
×
265
    mp_int_t x2 = pb_obj_get_int(x2_in);
×
266
    mp_int_t y2 = pb_obj_get_int(y2_in);
×
267
    mp_int_t width = pb_obj_get_int(width_in);
×
268
    int color = get_color(color_in);
×
269

270
    pbio_image_draw_thick_line(&self->image, x1, y1, x2, y2, width, color);
×
271

272
    if (self->is_display) {
×
273
        pbdrv_display_update();
×
274
    }
275

276
    return mp_const_none;
×
277
}
278
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_draw_line_obj, 1, pb_type_Image_draw_line);
279

280
static mp_obj_t pb_type_Image_draw_box(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
281
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
282
        pb_type_Image_obj_t, self,
283
        PB_ARG_REQUIRED(x1),
284
        PB_ARG_REQUIRED(y1),
285
        PB_ARG_REQUIRED(x2),
286
        PB_ARG_REQUIRED(y2),
287
        PB_ARG_DEFAULT_INT(r, 0),
288
        PB_ARG_DEFAULT_FALSE(fill),
289
        PB_ARG_DEFAULT_NONE(color));
290

291
    mp_int_t x1 = pb_obj_get_int(x1_in);
×
292
    mp_int_t y1 = pb_obj_get_int(y1_in);
×
293
    mp_int_t x2 = pb_obj_get_int(x2_in);
×
294
    mp_int_t y2 = pb_obj_get_int(y2_in);
×
295
    mp_int_t r = pb_obj_get_int(r_in);
×
296
    bool fill = mp_obj_is_true(fill_in);
×
297
    int color = get_color(color_in);
×
298

299
    int width = x2 - x1 + 1;
×
300
    int height = y2 - y1 + 1;
×
301
    if (fill) {
×
302
        pbio_image_fill_rounded_rect(&self->image, x1, y1, width, height, r, color);
×
303
    } else {
304
        pbio_image_draw_rounded_rect(&self->image, x1, y1, width, height, r, color);
×
305
    }
306

307
    if (self->is_display) {
×
308
        pbdrv_display_update();
×
309
    }
310

311
    return mp_const_none;
×
312
}
313
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_draw_box_obj, 1, pb_type_Image_draw_box);
314

315
static mp_obj_t pb_type_Image_draw_circle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
316
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
317
        pb_type_Image_obj_t, self,
318
        PB_ARG_REQUIRED(x),
319
        PB_ARG_REQUIRED(y),
320
        PB_ARG_REQUIRED(r),
321
        PB_ARG_DEFAULT_FALSE(fill),
322
        PB_ARG_DEFAULT_NONE(color));
323

324
    mp_int_t x = pb_obj_get_int(x_in);
×
325
    mp_int_t y = pb_obj_get_int(y_in);
×
326
    mp_int_t r = pb_obj_get_int(r_in);
×
327
    bool fill = mp_obj_is_true(fill_in);
×
328
    int color = get_color(color_in);
×
329

330
    if (fill) {
×
331
        pbio_image_fill_circle(&self->image, x, y, r, color);
×
332
    } else {
333
        pbio_image_draw_circle(&self->image, x, y, r, color);
×
334
    }
335

336
    if (self->is_display) {
×
337
        pbdrv_display_update();
×
338
    }
339

340
    return mp_const_none;
×
341
}
342
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_draw_circle_obj, 1, pb_type_Image_draw_circle);
343

344
static mp_obj_t pb_type_Image_draw_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
345
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
346
        pb_type_Image_obj_t, self,
347
        PB_ARG_REQUIRED(x),
348
        PB_ARG_REQUIRED(y),
349
        PB_ARG_REQUIRED(text),
350
        PB_ARG_DEFAULT_NONE(text_color),
351
        PB_ARG_DEFAULT_NONE(background_color));
352

353
    mp_int_t x = pb_obj_get_int(x_in);
×
354
    mp_int_t y = pb_obj_get_int(y_in);
×
355
    size_t text_len;
356
    const char *text = mp_obj_str_get_data(text_in, &text_len);
×
357
    int text_color = get_color(text_color_in);
×
358

NEW
359
    const pbio_font_t *font = self->image.print_font;
×
360

361
    if (background_color_in != mp_const_none) {
×
362
        int background_color = get_color(background_color_in);
×
363
        pbio_image_rect_t rect;
364

365
        pbio_image_bbox_text(font, text, text_len, &rect);
×
366
        pbio_image_fill_rect(&self->image, x + rect.x - 1, y + rect.y - 1 + font->top_max,
×
367
            rect.width + 2, rect.height + 2, background_color);
×
368
    }
369

370
    pbio_image_draw_text(&self->image, font, x, y + font->top_max, text, text_len, text_color);
×
371

372
    if (self->is_display) {
×
373
        pbdrv_display_update();
×
374
    }
375

376
    return mp_const_none;
×
377
}
378
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_draw_text_obj, 1, pb_type_Image_draw_text);
379

NEW
380
static mp_obj_t pb_type_Image_print(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
381
    static const mp_arg_t allowed_args[] = {
382
        { MP_QSTR_sep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__space_)} },
383
        { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__0x0a_)} },
384
    };
385
    mp_arg_val_t parsed_args[MP_ARRAY_SIZE(allowed_args)];
NEW
386
    mp_arg_parse_all(0, NULL, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, parsed_args);
×
NEW
387
    pb_type_Image_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
×
388
    size_t sep_len, end_len;
NEW
389
    const char *sep = mp_obj_str_get_data(parsed_args[0].u_obj, &sep_len);
×
NEW
390
    const char *end = mp_obj_str_get_data(parsed_args[1].u_obj, &end_len);
×
391

392
    vstr_t vstr;
393
    mp_print_t print;
NEW
394
    vstr_init_print(&vstr, 16, &print);
×
395

NEW
396
    for (size_t i = 1; i < n_args; i++) {
×
NEW
397
        if (i > 1) {
×
NEW
398
            mp_print_strn(&print, sep, sep_len, 0, 0, 0);
×
399
        }
NEW
400
        mp_obj_print_helper(&print, pos_args[i], PRINT_STR);
×
401
    }
NEW
402
    mp_print_strn(&print, end, end_len, 0, 0, 0);
×
403

NEW
404
    size_t text_len = vstr_len(&vstr);
×
NEW
405
    const char *text = vstr_str(&vstr);
×
406

NEW
407
    pbio_image_print(&self->image, text, text_len);
×
408

NEW
409
    vstr_clear(&vstr);
×
410

NEW
411
    if (self->is_display) {
×
NEW
412
        pbdrv_display_update();
×
413
    }
414

NEW
415
    return mp_const_none;
×
416
}
417
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Image_print_obj, 1, pb_type_Image_print);
418

NEW
419
static mp_obj_t pb_type_Image_set_font(mp_obj_t self_in, mp_obj_t font_in) {
×
NEW
420
    pb_type_Image_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
421

NEW
422
    self->image.print_font = pb_type_Font_get_font(font_in);
×
423

NEW
424
    return mp_const_none;
×
425
}
426
static MP_DEFINE_CONST_FUN_OBJ_2(pb_type_Image_set_font_obj, pb_type_Image_set_font);
427

428
// dir(pybricks.parameters.Image)
429
static const mp_rom_map_elem_t pb_type_Image_locals_dict_table[] = {
430
    // REVISIT: consider close() method and __enter__/__exit__ for context manager
431
    // to deterministically free memory if needed.
432
    { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&pb_type_Image_close_obj) },
433
    { MP_ROM_QSTR(MP_QSTR_empty), MP_ROM_PTR(&pb_type_Image_empty_obj) },
434
    { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&pb_type_Image_clear_obj) },
435
    { MP_ROM_QSTR(MP_QSTR_load_image), MP_ROM_PTR(&pb_type_Image_load_image_obj) },
436
    { MP_ROM_QSTR(MP_QSTR_draw_image), MP_ROM_PTR(&pb_type_Image_draw_image_obj) },
437
    { MP_ROM_QSTR(MP_QSTR_draw_pixel), MP_ROM_PTR(&pb_type_Image_draw_pixel_obj) },
438
    { MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&pb_type_Image_draw_line_obj) },
439
    { MP_ROM_QSTR(MP_QSTR_draw_box), MP_ROM_PTR(&pb_type_Image_draw_box_obj) },
440
    { MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&pb_type_Image_draw_circle_obj) },
441
    { MP_ROM_QSTR(MP_QSTR_draw_text), MP_ROM_PTR(&pb_type_Image_draw_text_obj) },
442
    { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&pb_type_Image_print_obj) },
443
    { MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&pb_type_Image_set_font_obj) },
444
};
445
static MP_DEFINE_CONST_DICT(pb_type_Image_locals_dict, pb_type_Image_locals_dict_table);
446

447
// type(pybricks.parameters.Image)
448
MP_DEFINE_CONST_OBJ_TYPE(pb_type_Image,
449
    MP_QSTR_Image,
450
    MP_TYPE_FLAG_NONE,
451
    make_new, pb_type_Image_make_new,
452
    attr, pb_type_Image_attr,
453
    locals_dict, &pb_type_Image_locals_dict);
454

455
#endif // PYBRICKS_PY_PARAMETERS_IMAGE
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc