diff --git a/src/omv/img/gif.c b/src/omv/img/gif.c index 9db0c08be..a48267585 100644 --- a/src/omv/img/gif.c +++ b/src/omv/img/gif.c @@ -4,35 +4,39 @@ * This work is licensed under the MIT license, see the file LICENSE for details. * * A super simple GIF encoder. - * Only support Grayscale/128 colors/No compression. * */ -#include "imlib.h" -#include "xalloc.h" -#include "fb_alloc.h" #include "ff_wrapper.h" +#include "imlib.h" +#include "fb_alloc.h" +#define BLOCK_SIZE (126) // (2^(7)) - 2 +#define BUFF_SIZE ((BLOCK_SIZE+2)*128) // 128 blocks at once (don't increase) -#define BLOCK_SIZE (100) -#define BUFF_SIZE ((BLOCK_SIZE+2)*500) - -void gif_open(FIL *fp, int width, int height, bool loop) +void gif_open(FIL *fp, int width, int height, bool color, bool loop) { write_data(fp, "GIF89a", 6); write_word(fp, width); write_word(fp, height); write_data(fp, (uint8_t []) {0xF6, 0x00, 0x00}, 3); - // Grayscale - for (int i=0; i<256; i+=2) { - write_data(fp, (uint8_t []) {i, i, i}, 3); + if (color) { + for (int i=0; i<128; i++) { + int red = ((((i & 0x60) >> 5) * 255) + 1.5) / 3; + int green = ((((i & 0x1C) >> 2) * 255) + 3.5) / 7; + int blue = (((i & 0x3) * 255) + 1.5) / 3; + write_data(fp, (uint8_t []) {red, green, blue}, 3); + } + } else { + for (int i=0; i<128; i++) { + int gray = ((i * 255) + 63.5) / 127; + write_data(fp, (uint8_t []) {gray, gray, gray}, 3); + } } - if (loop == true) { + if (loop) { write_data(fp, (uint8_t []) {'!', 0xFF, 0x0B}, 3); write_data(fp, "NETSCAPE2.0", 11); - write_data(fp, (uint8_t []) {0x03, 0x01}, 2); - write_word(fp, 0); // 0 == loop forever - write_byte(fp, 0); + write_data(fp, (uint8_t []) {0x03, 0x01, 0x00, 0x00, 0x00}, 5); } } @@ -41,42 +45,50 @@ void gif_add_frame(FIL *fp, image_t *img, uint16_t delay) if (delay) { write_data(fp, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); write_word(fp, delay); - write_word(fp, 0); + write_word(fp, 0); // end } write_byte(fp, 0x2C); write_long(fp, 0); write_word(fp, img->w); write_word(fp, img->h); - write_data(fp, (uint8_t []) {0x00, 0x07}, 2); // DEPTH + write_data(fp, (uint8_t []) {0x00, 0x07}, 2); // 7-bits - int bytes = (img->h * img->w); - int blocks = bytes / BLOCK_SIZE; - - int buf_ofs=0; - uint8_t *buf = fb_alloc(BUFF_SIZE * sizeof(*buf)); + int buf_ofs = 0; + uint8_t *buf = fb_alloc(BUFF_SIZE); + int bytes = img->h * img->w; + int blocks = (bytes + BLOCK_SIZE - 1) / BLOCK_SIZE; for (int y=0; ypixels[y*BLOCK_SIZE+x]>>1; + if (IM_IS_GS(img)) { + for (int x=0; xpixels[(y*BLOCK_SIZE)+x] >> 1; + } + } else { + for (int x=0; xpixels)[(y*BLOCK_SIZE)+x]; + int red = IM_R565(pixel) >> 3; + int green = IM_G565(pixel) >> 3; + int blue = IM_B565(pixel) >> 3; + buf[buf_ofs++] = (red << 5) | (green << 2) | blue; + } } - if (buf_ofs==BUFF_SIZE) { - buf_ofs = 0; + if (buf_ofs>=BUFF_SIZE) { // flush data + buf_ofs-=BUFF_SIZE; write_data(fp, buf, BUFF_SIZE); } } - if (buf_ofs) { + if (buf_ofs) { // flush remaining write_data(fp, buf, buf_ofs); } - write_data(fp, (uint8_t []) {0x01, 0x81}, 2); // STOP code - write_byte(fp, 0x00); - + write_data(fp, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code fb_free(); } diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 2415981b6..f9204fa37 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -429,6 +429,11 @@ void imlib_image_operation(image_t *img, const char *path, image_t *other, line_ void imlib_load_image(image_t *img, const char *path); void imlib_save_image(image_t *img, const char *path, rectangle_t *roi); +/* GIF functions */ +void gif_open(FIL *fp, int width, int height, bool color, bool loop); +void gif_add_frame(FIL *fp, image_t *img, uint16_t delay); +void gif_close(FIL *fp); + /* Basic image functions */ int imlib_get_pixel(image_t *img, int x, int y); void imlib_set_pixel(image_t *img, int x, int y, int p); @@ -544,9 +549,4 @@ void jpeg_compress(image_t *src, image_t *dst, int quality); void im_filter_bw(uint8_t *src, uint8_t *dst, int size, int bpp, void *args); void im_filter_skin(uint8_t *src, uint8_t *dst, int size, int bpp, void *args); - -// GIF functions -void gif_open(FIL *fp, int width, int height, bool loop); -void gif_add_frame(FIL *fp, image_t *img, uint16_t delay); -void gif_close(FIL *fp); #endif //__IMLIB_H__ diff --git a/src/omv/py/py_gif.c b/src/omv/py/py_gif.c index 6b4b2d76d..0eb7c9b5e 100644 --- a/src/omv/py/py_gif.c +++ b/src/omv/py/py_gif.c @@ -7,78 +7,106 @@ * */ #include "mp.h" -#include "imlib.h" -#include "ff.h" -#include "py_image.h" +#include "ff_wrapper.h" +#include "framebuffer.h" +#include "sensor.h" #include "py_assert.h" #include "py_helper.h" -#include "omv_boardconfig.h" - -static const mp_obj_type_t py_gif_type; -extern const char *ffs_strerror(FRESULT res); +#include "py_image.h" +static const mp_obj_type_t py_gif_type; // forward declare // Gif class -typedef struct _py_gif_obj_t { +typedef struct py_gif_obj { mp_obj_base_t base; int width; int height; + bool color; bool loop; FIL fp; } py_gif_obj_t; static mp_obj_t py_gif_open(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - py_gif_obj_t *gif; - gif = m_new_obj(py_gif_obj_t); - gif->width = mp_obj_get_int(args[0]); - gif->height = mp_obj_get_int(args[1]); - gif->loop = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_loop), 1); + py_gif_obj_t *gif = m_new_obj(py_gif_obj_t); + gif->width = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_width), fb->w); + gif->height = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_height), fb->h); + gif->color = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), fb->bpp>=2); + gif->loop = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_loop), true); gif->base.type = &py_gif_type; - FRESULT res; - const char *path = mp_obj_str_get_str(args[2]); - if ((res = f_open(&gif->fp, path, FA_WRITE|FA_CREATE_ALWAYS)) != FR_OK) { - nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, ffs_strerror(res))); - } + file_write_open(&gif->fp, mp_obj_str_get_str(args[0])); + gif_open(&gif->fp, gif->width, gif->height, gif->color, gif->loop); + return gif; +} - gif_open(&gif->fp, gif->width, gif->height, gif->loop); - return gif; +static mp_obj_t py_gif_width(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->width); +} + +static mp_obj_t py_gif_height(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->height); +} + +static mp_obj_t py_gif_format(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->color ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE); +} + +static mp_obj_t py_gif_size(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(f_size(&arg_gif->fp)); } static mp_obj_t py_gif_add_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - py_gif_obj_t *gif = args[0]; - image_t *image = py_image_cobj(args[1]); - int delay = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_delay), 0); + py_gif_obj_t *arg_gif = args[0]; + image_t *arg_img = py_image_cobj(args[1]); + PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), + "Operation not supported on JPEG"); + PY_ASSERT_FALSE_MSG((arg_gif->width != arg_img->w) + || (arg_gif->height != arg_img->h) + || (arg_gif->color != IM_IS_RGB565(arg_img)), + "Unexpected image geometry"); + int delay = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_delay), 10); - gif_add_frame(&gif->fp, image, delay); + gif_add_frame(&arg_gif->fp, arg_img, delay); return mp_const_none; } -static mp_obj_t py_gif_close(mp_obj_t self) +static mp_obj_t py_gif_close(mp_obj_t gif_obj) { - py_gif_obj_t *gif = self; - gif_close(&gif->fp); + py_gif_obj_t *arg_gif = gif_obj; + gif_close(&arg_gif->fp); return mp_const_none; } static void py_gif_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { py_gif_obj_t *self = self_in; - mp_printf(print, "", self->width, self->height, self->loop); + mp_printf(print, "", self->width, self->height, self->color, self->loop); } - -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_close_obj, py_gif_close); -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_open_obj, 3, py_gif_open); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_width_obj, py_gif_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_height_obj, py_gif_height); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_format_obj, py_gif_format); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_size_obj, py_gif_size); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_add_frame_obj, 2, py_gif_add_frame); - +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_close_obj, py_gif_close); static const mp_map_elem_t locals_dict_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&py_gif_close_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_width), (mp_obj_t)&py_gif_width_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_height), (mp_obj_t)&py_gif_height_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_format), (mp_obj_t)&py_gif_format_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_size), (mp_obj_t)&py_gif_size_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_add_frame), (mp_obj_t)&py_gif_add_frame_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&py_gif_close_obj }, { NULL, NULL }, }; - STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); static const mp_obj_type_t py_gif_type = { @@ -88,14 +116,16 @@ static const mp_obj_type_t py_gif_type = { .locals_dict = (mp_obj_t)&locals_dict, }; +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_open_obj, 1, py_gif_open); static const mp_map_elem_t globals_dict_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_gif)}, - { MP_OBJ_NEW_QSTR(MP_QSTR_Gif), (mp_obj_t)&py_gif_open_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_gif) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Gif), (mp_obj_t)&py_gif_open_obj }, + { NULL, NULL }, }; STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); const mp_obj_module_t gif_module = { .base = { &mp_type_module }, - .name = MP_QSTR_image, + .name = MP_QSTR_gif, .globals = (mp_obj_t)&globals_dict, }; diff --git a/usr/examples/gif.py b/usr/examples/gif.py index e35d6cccf..a61e8d5c4 100644 --- a/usr/examples/gif.py +++ b/usr/examples/gif.py @@ -12,19 +12,19 @@ sensor.set_framesize(sensor.QQVGA) sensor.set_pixformat(sensor.GRAYSCALE) for i in range(30): - img = sensor.snapshot() + img = sensor.snapshot() # FPS clock clock = time.clock() -gif = gif.Gif(img.width(), img.height(), "/test.gif", loop=True) +gif = gif.Gif("/test.gif", loop=True) for i in range(30): - clock.tick() - img = sensor.snapshot() - gif.add_frame(img, delay=10) + clock.tick() + img = sensor.snapshot() + gif.add_frame(img, delay=10) # Print FPS. # Note: Actual FPS is higher, the IDE slows down streaming. - print(clock.fps()) + print(clock.fps()) gif.close() print("done")