/* * This file is part of the Micro Python project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2015 Damien George, Mark Shannon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include "py/runtime.h" #include "microbitimage.h" #include "microbitconstimage.h" #include "py/runtime0.h" #include "microbitfont.h" #define min(a,b) (((a)<(b))?(a):(b)) #define max(a,b) (((a)>(b))?(a):(b)) const monochrome_5by5_t microbit_blank_image = { { µbit_image_type }, 1, 0, 0, 0, { 0, 0, 0 } }; STATIC void microbit_image_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; mp_printf(print, "Image("); if (kind == PRINT_STR) mp_printf(print, "\n "); mp_printf(print, "'"); for (int y = 0; y < imageHeight(self); ++y) { for (int x = 0; x < imageWidth(self); ++x) { mp_printf(print, "%c", "0123456789"[imageGetPixelValue(self, x, y)]); } mp_printf(print, ":"); if (kind == PRINT_STR && y < imageHeight(self)-1) mp_printf(print, "'\n '"); } mp_printf(print, "'"); if (kind == PRINT_STR) mp_printf(print, "\n"); mp_printf(print, ")"); } uint8_t monochromeGetPixelValue(monochrome_5by5_t * p_mono, mp_int_t x, mp_int_t y) { unsigned int index = y*5+x; if (index == 24) return p_mono->pixel44; return (p_mono->bits24[index>>3] >> (index&7))&1; } uint8_t greyscaleGetPixelValue(greyscale_t * p_greyscale, mp_int_t x, mp_int_t y) { unsigned int index = y*p_greyscale->width+x; unsigned int shift = ((index<<2)&4); return (p_greyscale->byte_data[index>>1] >> shift)&15; } void greyscaleSetPixelValue(greyscale_t * p_greyscale, mp_int_t x, mp_int_t y, mp_int_t val) { unsigned int index = y*p_greyscale->width+x; unsigned int shift = ((index<<2)&4); uint8_t mask = 240 >> shift; p_greyscale->byte_data[index>>1] = (p_greyscale->byte_data[index>>1] & mask) | (val << shift); } void greyscaleFill(greyscale_t * p_greyscale, mp_int_t val) { mp_int_t byte = (val<<4) | val; for (int i = 0; i < ((p_greyscale->width*p_greyscale->height+1)>>1); i++) { p_greyscale->byte_data[i] = byte; } } void greyscaleClear(greyscale_t * p_greyscale) { memset(&p_greyscale->byte_data, 0, (p_greyscale->width*p_greyscale->height+1)>>1); } uint8_t imageGetPixelValue(microbit_image_obj_t * p_image, mp_int_t x, mp_int_t y) { if (p_image->base.five) return monochromeGetPixelValue(&p_image->monochrome_5by5, x, y)*MAX_BRIGHTNESS; else return greyscaleGetPixelValue(&p_image->greyscale, x, y); } mp_int_t imageWidth(microbit_image_obj_t * p_image) { if (p_image->base.five) return 5; else return p_image->greyscale.width; } mp_int_t imageHeight(microbit_image_obj_t * p_image) { if (p_image->base.five) return 5; else return p_image->greyscale.height; } STATIC greyscale_t *greyscale_new(mp_int_t w, mp_int_t h) { greyscale_t *result = m_new_obj_var(greyscale_t, uint8_t, (w*h+1)>>1); result->base.type = µbit_image_type; result->five = 0; result->width = w; result->height = h; return result; } greyscale_t * imageCopy(microbit_image_obj_t * p_image) { mp_int_t w = imageWidth(p_image); mp_int_t h = imageHeight(p_image); greyscale_t *result = greyscale_new(w, h); for (mp_int_t y = 0; y < h; y++) { for (mp_int_t x = 0; x < w; ++x) { greyscaleSetPixelValue(result, x,y, imageGetPixelValue(p_image, x,y)); } } return result; } greyscale_t * imageInvert(microbit_image_obj_t * p_image) { mp_int_t w = imageWidth(p_image); mp_int_t h = imageHeight(p_image); greyscale_t *result = greyscale_new(w, h); for (mp_int_t y = 0; y < h; y++) { for (mp_int_t x = 0; x < w; ++x) { greyscaleSetPixelValue(result, x,y, MAX_BRIGHTNESS - imageGetPixelValue(p_image, x,y)); } } return result; } STATIC microbit_image_obj_t *image_from_parsed_str(const char *s, mp_int_t len) { mp_int_t w = 0; mp_int_t h = 0; mp_int_t line_len = 0; greyscale_t *result; /*First pass -- Establish metadata */ for (int i = 0; i < len; i++) { char c = s[i]; if (c == '\n' || c == ':') { w = max(line_len, w); line_len = 0; ++h; } else if (c == ' ') { ++line_len; } else if ('c' >= '0' && c <= '9') { ++line_len; } else { mp_raise_ValueError(MP_ERROR_TEXT("Unexpected character in Image definition.")); } } if (line_len) { // Omitted trailing terminator ++h; w = max(line_len, w); } result = greyscale_new(w, h); mp_int_t x = 0; mp_int_t y = 0; /* Second pass -- Fill in data */ for (int i = 0; i < len; i++) { char c = s[i]; if (c == '\n' || c == ':') { while (x < w) { greyscaleSetPixelValue(result, x, y, 0); x++; } ++y; x = 0; } else if (c == ' ') { /* Treat spaces as 0 */ greyscaleSetPixelValue(result, x, y, 0); ++x; } else if ('c' >= '0' && c <= '9') { greyscaleSetPixelValue(result, x, y, c - '0'); ++x; } } if (y < h) { while (x < w) { greyscaleSetPixelValue(result, x, y, 0); x++; } } return (microbit_image_obj_t *)result; } STATIC mp_obj_t microbit_image_make_new(const mp_obj_type_t *type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) { (void)type_in; mp_arg_check_num(n_args, n_kw, 0, 3, false); switch (n_args) { case 0: { greyscale_t *image = greyscale_new(5, 5); greyscaleClear(image); return image; } case 1: { if (mp_obj_is_str(args[0])) { // arg is a string object size_t len; const char *str = mp_obj_str_get_data(args[0], &len); // make image from string if (len == 1) { /* For a single charater, return the font glyph */ return microbit_image_for_char(str[0]); } else { /* Otherwise parse the image description string */ return image_from_parsed_str(str, len); } } else { mp_raise_TypeError(MP_ERROR_TEXT("Image(s) takes a string.")); } } case 2: case 3: { mp_int_t w = mp_obj_get_int(args[0]); mp_int_t h = mp_obj_get_int(args[1]); greyscale_t *image = greyscale_new(w, h); if (n_args == 2) { greyscaleClear(image); } else { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); if (w < 0 || h < 0 || (size_t)(w * h) != bufinfo.len) { mp_raise_ValueError(MP_ERROR_TEXT("image data is incorrect size")); } mp_int_t i = 0; for (mp_int_t y = 0; y < h; y++) { for (mp_int_t x = 0; x < w; ++x) { uint8_t val = min(((const uint8_t*)bufinfo.buf)[i], MAX_BRIGHTNESS); greyscaleSetPixelValue(image, x, y, val); ++i; } } } return image; } default: { mp_raise_TypeError(MP_ERROR_TEXT("Image() takes 0 to 3 arguments")); } } } static void clear_rect(greyscale_t *img, mp_int_t x0, mp_int_t y0,mp_int_t x1, mp_int_t y1) { for (int i = x0; i < x1; ++i) { for (int j = y0; j < y1; ++j) { greyscaleSetPixelValue(img, i, j, 0); } } } STATIC void image_blit(microbit_image_obj_t *src, greyscale_t *dest, mp_int_t x, mp_int_t y, mp_int_t w, mp_int_t h, mp_int_t xdest, mp_int_t ydest) { if (w < 0) w = 0; if (h < 0) h = 0; mp_int_t intersect_x0 = max(max(0, x), -xdest); mp_int_t intersect_y0 = max(max(0, y), -ydest); mp_int_t intersect_x1 = min(min(dest->width+x-xdest, imageWidth(src)), x+w); mp_int_t intersect_y1 = min(min(dest->height+y-ydest, imageHeight(src)), y+h); mp_int_t xstart, xend, ystart, yend, xdel, ydel; mp_int_t clear_x0 = max(0, xdest); mp_int_t clear_y0 = max(0, ydest); mp_int_t clear_x1 = min(dest->width, xdest+w); mp_int_t clear_y1 = min(dest->height, ydest+h); if (intersect_x0 >= intersect_x1 || intersect_y0 >= intersect_y1) { // Nothing to copy clear_rect(dest, clear_x0, clear_y0, clear_x1, clear_y1); return; } if (x > xdest) { xstart = intersect_x0; xend = intersect_x1; xdel = 1; } else { xstart = intersect_x1-1; xend = intersect_x0-1; xdel = -1; } if (y > ydest) { ystart = intersect_y0; yend = intersect_y1; ydel = 1; } else { ystart = intersect_y1-1; yend = intersect_y0-1; ydel = -1; } for (int i = xstart; i != xend; i += xdel) { for (int j = ystart; j != yend; j += ydel) { int val = imageGetPixelValue(src, i, j); greyscaleSetPixelValue(dest, i+xdest-x, j+ydest-y, val); } } // Adjust intersection rectange to dest intersect_x0 += xdest-x; intersect_y0 += ydest-y; intersect_x1 += xdest-x; intersect_y1 += ydest-y; // Clear four rectangles in the cleared area surrounding the copied area. clear_rect(dest, clear_x0, clear_y0, intersect_x0, intersect_y1); clear_rect(dest, clear_x0, intersect_y1, intersect_x1, clear_y1); clear_rect(dest, intersect_x1, intersect_y0, clear_x1, clear_y1); clear_rect(dest, intersect_x0, clear_y0, clear_x1, intersect_y0); } greyscale_t *image_shift(microbit_image_obj_t *self, mp_int_t x, mp_int_t y) { greyscale_t *result = greyscale_new(imageWidth(self), imageWidth(self)); image_blit(self, result, x, y, imageWidth(self), imageWidth(self), 0, 0); return result; } STATIC microbit_image_obj_t *image_crop(microbit_image_obj_t *img, mp_int_t x, mp_int_t y, mp_int_t w, mp_int_t h) { if (w < 0) w = 0; if (h < 0) h = 0; greyscale_t *result = greyscale_new(w, h); image_blit(img, result, x, y, w, h, 0, 0); return (microbit_image_obj_t *)result; } mp_obj_t microbit_image_width(mp_obj_t self_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; return MP_OBJ_NEW_SMALL_INT(imageWidth(self)); } MP_DEFINE_CONST_FUN_OBJ_1(microbit_image_width_obj, microbit_image_width); mp_obj_t microbit_image_height(mp_obj_t self_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; return MP_OBJ_NEW_SMALL_INT(imageHeight(self)); } MP_DEFINE_CONST_FUN_OBJ_1(microbit_image_height_obj, microbit_image_height); mp_obj_t microbit_image_get_pixel(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; mp_int_t x = mp_obj_get_int(x_in); mp_int_t y = mp_obj_get_int(y_in); if (x < 0 || y < 0) { mp_raise_ValueError(MP_ERROR_TEXT("index cannot be negative")); } if (x < imageWidth(self) && y < imageHeight(self)) { return MP_OBJ_NEW_SMALL_INT(imageGetPixelValue(self, x, y)); } mp_raise_ValueError(MP_ERROR_TEXT("index too large")); } MP_DEFINE_CONST_FUN_OBJ_3(microbit_image_get_pixel_obj, microbit_image_get_pixel); /* Raise an exception if not mutable */ static void check_mutability(microbit_image_obj_t *self) { if (self->base.five) { mp_raise_TypeError(MP_ERROR_TEXT("image cannot be modified (try copying first)")); } } mp_obj_t microbit_image_set_pixel(mp_uint_t n_args, const mp_obj_t *args) { (void)n_args; microbit_image_obj_t *self = (microbit_image_obj_t*)args[0]; check_mutability(self); mp_int_t x = mp_obj_get_int(args[1]); mp_int_t y = mp_obj_get_int(args[2]); if (x < 0 || y < 0) { mp_raise_ValueError(MP_ERROR_TEXT("index cannot be negative")); } mp_int_t bright = mp_obj_get_int(args[3]); if (bright < 0 || bright > MAX_BRIGHTNESS) mp_raise_ValueError(MP_ERROR_TEXT("brightness out of bounds.")); if (x < imageWidth(self) && y < imageHeight(self)) { greyscaleSetPixelValue(&(self->greyscale), x, y, bright); return mp_const_none; } mp_raise_ValueError(MP_ERROR_TEXT("index too large")); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(microbit_image_set_pixel_obj, 4, 4, microbit_image_set_pixel); mp_obj_t microbit_image_fill(mp_obj_t self_in, mp_obj_t n_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; check_mutability(self); mp_int_t n = mp_obj_get_int(n_in); if (n < 0 || n > MAX_BRIGHTNESS) { mp_raise_ValueError(MP_ERROR_TEXT("brightness out of bounds.")); } greyscaleFill(&self->greyscale, n); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(microbit_image_fill_obj, microbit_image_fill); mp_obj_t microbit_image_blit(mp_uint_t n_args, const mp_obj_t *args) { microbit_image_obj_t *self = (microbit_image_obj_t*)args[0]; check_mutability(self); mp_obj_t src = args[1]; if (mp_obj_get_type(src) != µbit_image_type) { mp_raise_TypeError(MP_ERROR_TEXT("expecting an image")); } if (n_args == 7) { mp_raise_TypeError(MP_ERROR_TEXT("must specify both offsets")); } mp_int_t x = mp_obj_get_int(args[2]); mp_int_t y = mp_obj_get_int(args[3]); mp_int_t w = mp_obj_get_int(args[4]); mp_int_t h = mp_obj_get_int(args[5]); if (w < 0 || h < 0) { mp_raise_ValueError(MP_ERROR_TEXT("size cannot be negative")); } mp_int_t xdest; mp_int_t ydest; if (n_args == 6) { xdest = 0; ydest = 0; } else { xdest = mp_obj_get_int(args[6]); ydest = mp_obj_get_int(args[7]); } image_blit((microbit_image_obj_t *)src, &(self->greyscale), x, y, w, h, xdest, ydest); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(microbit_image_blit_obj, 6, 8, microbit_image_blit); mp_obj_t microbit_image_crop(mp_uint_t n_args, const mp_obj_t *args) { (void)n_args; microbit_image_obj_t *self = (microbit_image_obj_t*)args[0]; mp_int_t x0 = mp_obj_get_int(args[1]); mp_int_t y0 = mp_obj_get_int(args[2]); mp_int_t x1 = mp_obj_get_int(args[3]); mp_int_t y1 = mp_obj_get_int(args[4]); return image_crop(self, x0, y0, x1, y1); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(microbit_image_crop_obj, 5, 5, microbit_image_crop); mp_obj_t microbit_image_shift_left(mp_obj_t self_in, mp_obj_t n_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; mp_int_t n = mp_obj_get_int(n_in); return image_shift(self, n, 0); } MP_DEFINE_CONST_FUN_OBJ_2(microbit_image_shift_left_obj, microbit_image_shift_left); mp_obj_t microbit_image_shift_right(mp_obj_t self_in, mp_obj_t n_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; mp_int_t n = mp_obj_get_int(n_in); return image_shift(self, -n, 0); } MP_DEFINE_CONST_FUN_OBJ_2(microbit_image_shift_right_obj, microbit_image_shift_right); mp_obj_t microbit_image_shift_up(mp_obj_t self_in, mp_obj_t n_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; mp_int_t n = mp_obj_get_int(n_in); return image_shift(self, 0, n); } MP_DEFINE_CONST_FUN_OBJ_2(microbit_image_shift_up_obj, microbit_image_shift_up); mp_obj_t microbit_image_shift_down(mp_obj_t self_in, mp_obj_t n_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; mp_int_t n = mp_obj_get_int(n_in); return image_shift(self, 0, -n); } MP_DEFINE_CONST_FUN_OBJ_2(microbit_image_shift_down_obj, microbit_image_shift_down); mp_obj_t microbit_image_copy(mp_obj_t self_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; return imageCopy(self); } MP_DEFINE_CONST_FUN_OBJ_1(microbit_image_copy_obj, microbit_image_copy); mp_obj_t microbit_image_invert(mp_obj_t self_in) { microbit_image_obj_t *self = (microbit_image_obj_t*)self_in; return imageInvert(self); } MP_DEFINE_CONST_FUN_OBJ_1(microbit_image_invert_obj, microbit_image_invert); STATIC const mp_rom_map_elem_t microbit_image_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(µbit_image_width_obj) }, { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(µbit_image_height_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel), MP_ROM_PTR(µbit_image_get_pixel_obj) }, { MP_ROM_QSTR(MP_QSTR_set_pixel), MP_ROM_PTR(µbit_image_set_pixel_obj) }, { MP_ROM_QSTR(MP_QSTR_shift_left), MP_ROM_PTR(µbit_image_shift_left_obj) }, { MP_ROM_QSTR(MP_QSTR_shift_right), MP_ROM_PTR(µbit_image_shift_right_obj) }, { MP_ROM_QSTR(MP_QSTR_shift_up), MP_ROM_PTR(µbit_image_shift_up_obj) }, { MP_ROM_QSTR(MP_QSTR_shift_down), MP_ROM_PTR(µbit_image_shift_down_obj) }, { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(µbit_image_copy_obj) }, { MP_ROM_QSTR(MP_QSTR_crop), MP_ROM_PTR(µbit_image_crop_obj) }, { MP_ROM_QSTR(MP_QSTR_invert), MP_ROM_PTR(µbit_image_invert_obj) }, { MP_ROM_QSTR(MP_QSTR_fill), MP_ROM_PTR(µbit_image_fill_obj) }, { MP_ROM_QSTR(MP_QSTR_blit), MP_ROM_PTR(µbit_image_blit_obj) }, { MP_ROM_QSTR(MP_QSTR_HEART), MP_ROM_PTR(µbit_const_image_heart_obj) }, { MP_ROM_QSTR(MP_QSTR_HEART_SMALL), MP_ROM_PTR(µbit_const_image_heart_small_obj) }, { MP_ROM_QSTR(MP_QSTR_HAPPY), MP_ROM_PTR(µbit_const_image_happy_obj) }, { MP_ROM_QSTR(MP_QSTR_SMILE), MP_ROM_PTR(µbit_const_image_smile_obj) }, { MP_ROM_QSTR(MP_QSTR_SAD), MP_ROM_PTR(µbit_const_image_sad_obj) }, { MP_ROM_QSTR(MP_QSTR_CONFUSED), MP_ROM_PTR(µbit_const_image_confused_obj) }, { MP_ROM_QSTR(MP_QSTR_ANGRY), MP_ROM_PTR(µbit_const_image_angry_obj) }, { MP_ROM_QSTR(MP_QSTR_ASLEEP), MP_ROM_PTR(µbit_const_image_asleep_obj) }, { MP_ROM_QSTR(MP_QSTR_SURPRISED), MP_ROM_PTR(µbit_const_image_surprised_obj) }, { MP_ROM_QSTR(MP_QSTR_SILLY), MP_ROM_PTR(µbit_const_image_silly_obj) }, { MP_ROM_QSTR(MP_QSTR_FABULOUS), MP_ROM_PTR(µbit_const_image_fabulous_obj) }, { MP_ROM_QSTR(MP_QSTR_MEH), MP_ROM_PTR(µbit_const_image_meh_obj) }, { MP_ROM_QSTR(MP_QSTR_YES), MP_ROM_PTR(µbit_const_image_yes_obj) }, { MP_ROM_QSTR(MP_QSTR_NO), MP_ROM_PTR(µbit_const_image_no_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK12), MP_ROM_PTR(µbit_const_image_clock12_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK1), MP_ROM_PTR(µbit_const_image_clock1_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK2), MP_ROM_PTR(µbit_const_image_clock2_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK3), MP_ROM_PTR(µbit_const_image_clock3_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK4), MP_ROM_PTR(µbit_const_image_clock4_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK5), MP_ROM_PTR(µbit_const_image_clock5_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK6), MP_ROM_PTR(µbit_const_image_clock6_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK7), MP_ROM_PTR(µbit_const_image_clock7_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK8), MP_ROM_PTR(µbit_const_image_clock8_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK9), MP_ROM_PTR(µbit_const_image_clock9_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK10), MP_ROM_PTR(µbit_const_image_clock10_obj) }, { MP_ROM_QSTR(MP_QSTR_CLOCK11), MP_ROM_PTR(µbit_const_image_clock11_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_N), MP_ROM_PTR(µbit_const_image_arrow_n_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_NE), MP_ROM_PTR(µbit_const_image_arrow_ne_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_E), MP_ROM_PTR(µbit_const_image_arrow_e_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_SE), MP_ROM_PTR(µbit_const_image_arrow_se_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_S), MP_ROM_PTR(µbit_const_image_arrow_s_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_SW), MP_ROM_PTR(µbit_const_image_arrow_sw_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_W), MP_ROM_PTR(µbit_const_image_arrow_w_obj) }, { MP_ROM_QSTR(MP_QSTR_ARROW_NW), MP_ROM_PTR(µbit_const_image_arrow_nw_obj) }, { MP_ROM_QSTR(MP_QSTR_TRIANGLE), MP_ROM_PTR(µbit_const_image_triangle_obj) }, { MP_ROM_QSTR(MP_QSTR_TRIANGLE_LEFT), MP_ROM_PTR(µbit_const_image_triangle_left_obj) }, { MP_ROM_QSTR(MP_QSTR_CHESSBOARD), MP_ROM_PTR(µbit_const_image_chessboard_obj) }, { MP_ROM_QSTR(MP_QSTR_DIAMOND), MP_ROM_PTR(µbit_const_image_diamond_obj) }, { MP_ROM_QSTR(MP_QSTR_DIAMOND_SMALL), MP_ROM_PTR(µbit_const_image_diamond_small_obj) }, { MP_ROM_QSTR(MP_QSTR_SQUARE), MP_ROM_PTR(µbit_const_image_square_obj) }, { MP_ROM_QSTR(MP_QSTR_SQUARE_SMALL), MP_ROM_PTR(µbit_const_image_square_small_obj) }, { MP_ROM_QSTR(MP_QSTR_RABBIT), MP_ROM_PTR(µbit_const_image_rabbit) }, { MP_ROM_QSTR(MP_QSTR_COW), MP_ROM_PTR(µbit_const_image_cow) }, { MP_ROM_QSTR(MP_QSTR_MUSIC_CROTCHET), MP_ROM_PTR(µbit_const_image_music_crotchet_obj) }, { MP_ROM_QSTR(MP_QSTR_MUSIC_QUAVER), MP_ROM_PTR(µbit_const_image_music_quaver_obj) }, { MP_ROM_QSTR(MP_QSTR_MUSIC_QUAVERS), MP_ROM_PTR(µbit_const_image_music_quavers_obj) }, { MP_ROM_QSTR(MP_QSTR_PITCHFORK), MP_ROM_PTR(µbit_const_image_pitchfork_obj) }, { MP_ROM_QSTR(MP_QSTR_XMAS), MP_ROM_PTR(µbit_const_image_xmas_obj) }, { MP_ROM_QSTR(MP_QSTR_PACMAN), MP_ROM_PTR(µbit_const_image_pacman_obj) }, { MP_ROM_QSTR(MP_QSTR_TARGET), MP_ROM_PTR(µbit_const_image_target_obj) }, { MP_ROM_QSTR(MP_QSTR_ALL_CLOCKS), MP_ROM_PTR(µbit_const_image_all_clocks_tuple_obj) }, { MP_ROM_QSTR(MP_QSTR_ALL_ARROWS), MP_ROM_PTR(µbit_const_image_all_arrows_tuple_obj) }, { MP_ROM_QSTR(MP_QSTR_TSHIRT), MP_ROM_PTR(µbit_const_image_tshirt_obj) }, { MP_ROM_QSTR(MP_QSTR_ROLLERSKATE), MP_ROM_PTR(µbit_const_image_rollerskate_obj) }, { MP_ROM_QSTR(MP_QSTR_DUCK), MP_ROM_PTR(µbit_const_image_duck_obj) }, { MP_ROM_QSTR(MP_QSTR_HOUSE), MP_ROM_PTR(µbit_const_image_house_obj) }, { MP_ROM_QSTR(MP_QSTR_TORTOISE), MP_ROM_PTR(µbit_const_image_tortoise_obj) }, { MP_ROM_QSTR(MP_QSTR_BUTTERFLY), MP_ROM_PTR(µbit_const_image_butterfly_obj) }, { MP_ROM_QSTR(MP_QSTR_STICKFIGURE), MP_ROM_PTR(µbit_const_image_stickfigure_obj) }, { MP_ROM_QSTR(MP_QSTR_GHOST), MP_ROM_PTR(µbit_const_image_ghost_obj) }, { MP_ROM_QSTR(MP_QSTR_SWORD), MP_ROM_PTR(µbit_const_image_sword_obj) }, { MP_ROM_QSTR(MP_QSTR_GIRAFFE), MP_ROM_PTR(µbit_const_image_giraffe_obj) }, { MP_ROM_QSTR(MP_QSTR_SKULL), MP_ROM_PTR(µbit_const_image_skull_obj) }, { MP_ROM_QSTR(MP_QSTR_UMBRELLA), MP_ROM_PTR(µbit_const_image_umbrella_obj) }, { MP_ROM_QSTR(MP_QSTR_SNAKE), MP_ROM_PTR(µbit_const_image_snake_obj) }, }; STATIC MP_DEFINE_CONST_DICT(microbit_image_locals_dict, microbit_image_locals_dict_table); #define THE_FONT font_pendolino3_5x5_pad3msb #define ASCII_START 32 #define ASCII_END 126 STATIC const unsigned char *get_font_data_from_char(char c) { if (c < ASCII_START || c > ASCII_END) { c = '?'; } int offset = (c-ASCII_START) * 5; return THE_FONT + offset; } STATIC mp_int_t get_pixel_from_font_data(const unsigned char *data, int x, int y) { /* The following logic belongs in MicroBitFont */ return ((data[y]>>(4-x))&1); } void microbit_image_set_from_char(greyscale_t *img, char c) { const unsigned char *data = get_font_data_from_char(c); for (int x = 0; x < 5; ++x) { for (int y = 0; y < 5; ++y) { greyscaleSetPixelValue(img, x, y, get_pixel_from_font_data(data, x, y)*MAX_BRIGHTNESS); } } } microbit_image_obj_t *microbit_image_for_char(char c) { greyscale_t *result = greyscale_new(5,5); microbit_image_set_from_char(result, c); return (microbit_image_obj_t *)result; } #if MICROPY_PY_BUILTINS_FLOAT microbit_image_obj_t *microbit_image_dim(microbit_image_obj_t *lhs, mp_float_t fval) { #else microbit_image_obj_t *microbit_image_dim(microbit_image_obj_t *lhs, mp_int_t fval) { #endif if (fval < 0) mp_raise_ValueError(MP_ERROR_TEXT("Brightness multiplier must not be negative.")); greyscale_t *result = greyscale_new(imageWidth(lhs), imageHeight(lhs)); for (int x = 0; x < imageWidth(lhs); ++x) { for (int y = 0; y < imageWidth(lhs); ++y) { #if MICROPY_PY_BUILTINS_FLOAT int val = min((int)imageGetPixelValue(lhs, x,y)*fval+0.5, MAX_BRIGHTNESS); #else int val = min((int)imageGetPixelValue(lhs, x,y)*fval, MAX_BRIGHTNESS); #endif greyscaleSetPixelValue(result, x, y, val); } } return (microbit_image_obj_t *)result; } microbit_image_obj_t *microbit_image_sum(microbit_image_obj_t *lhs, microbit_image_obj_t *rhs, bool add) { mp_int_t h = imageHeight(lhs); mp_int_t w = imageWidth(lhs); if (imageHeight(rhs) != h || imageWidth(lhs) != w) { // TODO: verify that image width in test above should really test (lhs != w) mp_raise_ValueError(MP_ERROR_TEXT("Images must be the same size.")); } greyscale_t *result = greyscale_new(w, h); for (int x = 0; x < w; ++x) { for (int y = 0; y < h; ++y) { int val; int lval = imageGetPixelValue(lhs, x,y); int rval = imageGetPixelValue(rhs, x,y); if (add) val = min(lval + rval, MAX_BRIGHTNESS); else val = max(0, lval - rval); greyscaleSetPixelValue(result, x, y, val); } } return (microbit_image_obj_t *)result; } STATIC mp_obj_t image_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { if (mp_obj_get_type(lhs_in) != µbit_image_type) { return MP_OBJ_NULL; // op not supported } microbit_image_obj_t *lhs = (microbit_image_obj_t *)lhs_in; switch(op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_SUBTRACT: break; case MP_BINARY_OP_MULTIPLY: #if MICROPY_PY_BUILTINS_FLOAT return microbit_image_dim(lhs, mp_obj_get_float(rhs_in)); #else return microbit_image_dim(lhs, mp_obj_get_int(rhs_in) * 10); #endif case MP_BINARY_OP_TRUE_DIVIDE: #if MICROPY_PY_BUILTINS_FLOAT return microbit_image_dim(lhs, 1.0/mp_obj_get_float(rhs_in)); #else break; case MP_BINARY_OP_FLOOR_DIVIDE: return microbit_image_dim(lhs, (100/mp_obj_get_int(rhs_in) + 5) / 10); #endif default: return MP_OBJ_NULL; // op not supported } if (mp_obj_get_type(rhs_in) != µbit_image_type) { return MP_OBJ_NULL; // op not supported } return microbit_image_sum(lhs, (microbit_image_obj_t *)rhs_in, op == MP_BINARY_OP_ADD); } const mp_obj_type_t microbit_image_type = { { &mp_type_type }, .name = MP_QSTR_MicroBitImage, .print = microbit_image_print, .make_new = microbit_image_make_new, .call = NULL, .unary_op = NULL, .binary_op = image_binary_op, .attr = NULL, .subscr = NULL, .getiter = NULL, .iternext = NULL, .buffer_p = {NULL}, .locals_dict = (mp_obj_dict_t*)µbit_image_locals_dict, }; typedef struct _scrolling_string_t { mp_obj_base_t base; char const *str; mp_uint_t len; mp_obj_t ref; bool monospace; bool repeat; } scrolling_string_t; typedef struct _scrolling_string_iterator_t { mp_obj_base_t base; mp_obj_t ref; greyscale_t *img; char const *next_char; char const *start; char const *end; uint8_t offset; uint8_t offset_limit; bool monospace; bool repeat; char right; } scrolling_string_iterator_t; extern const mp_obj_type_t microbit_scrolling_string_type; extern const mp_obj_type_t microbit_scrolling_string_iterator_type; mp_obj_t scrolling_string_image_iterable(const char* str, mp_uint_t len, mp_obj_t ref, bool monospace, bool repeat) { scrolling_string_t *result = m_new_obj(scrolling_string_t); result->base.type = µbit_scrolling_string_type; result->str = str; result->len = len; result->ref = ref; result->monospace = monospace; result->repeat = repeat; return result; } STATIC int font_column_non_blank(const unsigned char *font_data, unsigned int col) { for (int y = 0; y < 5; ++y) { if (get_pixel_from_font_data(font_data, col, y)) { return 1; } } return 0; } /* Not strictly the rightmost non-blank column, but the rightmost in columns 2,3 or 4. */ STATIC unsigned int rightmost_non_blank_column(const unsigned char *font_data) { if (font_column_non_blank(font_data, 4)) { return 4; } if (font_column_non_blank(font_data, 3)) { return 3; } return 2; } static void restart(scrolling_string_iterator_t *iter) { iter->next_char = iter->start; iter->offset = 0; if (iter->start < iter->end) { iter->right = *iter->next_char; if (iter->monospace) { iter->offset_limit = 5; } else { iter->offset_limit = rightmost_non_blank_column(get_font_data_from_char(iter->right)) + 1; } } else { iter->right = ' '; iter->offset_limit = 5; } } STATIC mp_obj_t get_microbit_scrolling_string_iter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { (void)iter_buf; scrolling_string_t *str = (scrolling_string_t *)o_in; scrolling_string_iterator_t *result = m_new_obj(scrolling_string_iterator_t); result->base.type = µbit_scrolling_string_iterator_type; result->img = greyscale_new(5,5); result->start = str->str; result->ref = str->ref; result->monospace = str->monospace; result->end = result->start + str->len; result->repeat = str->repeat; restart(result); return result; } STATIC mp_obj_t microbit_scrolling_string_iter_next(mp_obj_t o_in) { scrolling_string_iterator_t *iter = (scrolling_string_iterator_t *)o_in; if (iter->next_char == iter->end && iter->offset == 5) { if (iter->repeat) { restart(iter); greyscaleClear(iter->img); } else { return MP_OBJ_STOP_ITERATION; } } for (int x = 0; x < 4; x++) { for (int y = 0; y < 5; y++) { greyscaleSetPixelValue(iter->img, x, y, greyscaleGetPixelValue(iter->img, x+1, y)); } } for (int y = 0; y < 5; y++) { greyscaleSetPixelValue(iter->img, 4, y, 0); } const unsigned char *font_data; if (iter->offset < iter->offset_limit) { font_data = get_font_data_from_char(iter->right); for (int y = 0; y < 5; ++y) { int pix = get_pixel_from_font_data(font_data, iter->offset, y)*MAX_BRIGHTNESS; greyscaleSetPixelValue(iter->img, 4, y, pix); } } else if (iter->offset == iter->offset_limit) { ++iter->next_char; if (iter->next_char == iter->end) { iter->right = ' '; iter->offset_limit = 5; iter->offset = 0; } else { iter->right = *iter->next_char; font_data = get_font_data_from_char(iter->right); if (iter->monospace) { iter->offset = -1; iter->offset_limit = 5; } else { iter->offset = -font_column_non_blank(font_data, 0); iter->offset_limit = rightmost_non_blank_column(font_data)+1; } } } ++iter->offset; return iter->img; } const mp_obj_type_t microbit_scrolling_string_type = { { &mp_type_type }, .name = MP_QSTR_ScrollingString, .print = NULL, .make_new = NULL, .call = NULL, .unary_op = NULL, .binary_op = NULL, .attr = NULL, .subscr = NULL, .getiter = get_microbit_scrolling_string_iter, .iternext = NULL, .buffer_p = {NULL}, .locals_dict = NULL, }; const mp_obj_type_t microbit_scrolling_string_iterator_type = { { &mp_type_type }, .name = MP_QSTR_iterator, .print = NULL, .make_new = NULL, .call = NULL, .unary_op = NULL, .binary_op = NULL, .attr = NULL, .subscr = NULL, .getiter = mp_identity_getiter, .iternext = microbit_scrolling_string_iter_next, .buffer_p = {NULL}, .locals_dict = NULL, }; /** Facade types to present a string as a sequence of images. * These are necessary to avoid allocation during iteration, * which may happen in interrupt handlers. */ typedef struct _string_image_facade_t { mp_obj_base_t base; mp_obj_t string; greyscale_t *image; } string_image_facade_t; static mp_obj_t string_image_facade_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { if (value == MP_OBJ_SENTINEL) { // Fill in image string_image_facade_t *self = (string_image_facade_t *)self_in; size_t len; const char *text = mp_obj_str_get_data(self->string, &len); mp_uint_t index = mp_get_index(self->base.type, len, index_in, false); microbit_image_set_from_char(self->image, text[index]); return self->image; } else { return MP_OBJ_NULL; // op not supported } } static mp_obj_t facade_unary_op(mp_unary_op_t op, mp_obj_t self_in) { string_image_facade_t *self = (string_image_facade_t *)self_in; switch (op) { case MP_UNARY_OP_LEN: return mp_obj_len(self->string); default: return MP_OBJ_NULL; // op not supported } } static mp_obj_t microbit_facade_iterator(mp_obj_t iterable_in, mp_obj_iter_buf_t *iter_buf); const mp_obj_type_t string_image_facade_type = { { &mp_type_type }, .name = MP_QSTR_Facade, .print = NULL, .make_new = NULL, .call = NULL, .unary_op = facade_unary_op, .binary_op = NULL, .attr = NULL, .subscr = string_image_facade_subscr, .getiter = microbit_facade_iterator, .iternext = NULL, .buffer_p = {NULL}, NULL }; typedef struct _facade_iterator_t { mp_obj_base_t base; mp_obj_t string; mp_uint_t index; greyscale_t *image; } facade_iterator_t; mp_obj_t microbit_string_facade(mp_obj_t string) { string_image_facade_t *result = m_new_obj(string_image_facade_t); result->base.type = &string_image_facade_type; result->string = string; result->image = greyscale_new(5,5); return result; } static mp_obj_t microbit_facade_iter_next(mp_obj_t iter_in) { facade_iterator_t *iter = (facade_iterator_t *)iter_in; size_t len; const char *text = mp_obj_str_get_data(iter->string, &len); if (iter->index >= len) { return MP_OBJ_STOP_ITERATION; } microbit_image_set_from_char(iter->image, text[iter->index]); iter->index++; return iter->image; } const mp_obj_type_t microbit_facade_iterator_type = { { &mp_type_type }, .name = MP_QSTR_iterator, .print = NULL, .make_new = NULL, .call = NULL, .unary_op = NULL, .binary_op = NULL, .attr = NULL, .subscr = NULL, .getiter = mp_identity_getiter, .iternext = microbit_facade_iter_next, .buffer_p = {NULL}, NULL }; mp_obj_t microbit_facade_iterator(mp_obj_t iterable_in, mp_obj_iter_buf_t *iter_buf) { (void)iter_buf; facade_iterator_t *result = m_new_obj(facade_iterator_t); string_image_facade_t *iterable = (string_image_facade_t *)iterable_in; result->base.type = µbit_facade_iterator_type; result->string = iterable->string; result->image = iterable->image; result->index = 0; return result; }