Add support for color gif.

Color gifs look very good for how bad you'd expect them to be with just
7 bits of color (rgb232) - quite amazing. Also, I  hardened the gif
module to make it "user ready".
This commit is contained in:
Kwabena W. Agyeman 2016-03-04 23:29:42 -05:00
parent 03a06a409d
commit 9325029675
4 changed files with 122 additions and 80 deletions

View File

@ -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; y<blocks; y++) {
buf[buf_ofs++] = BLOCK_SIZE+1;
buf[buf_ofs++] = 0x80;
int block_size = IM_MIN(BLOCK_SIZE, bytes - (y * BLOCK_SIZE));
buf[buf_ofs++] = 1 + block_size;
buf[buf_ofs++] = 0x80; // clear code
for (int x=0; x<BLOCK_SIZE; x++) {
buf[buf_ofs++] = img->pixels[y*BLOCK_SIZE+x]>>1;
if (IM_IS_GS(img)) {
for (int x=0; x<block_size; x++) {
buf[buf_ofs++] = img->pixels[(y*BLOCK_SIZE)+x] >> 1;
}
} else {
for (int x=0; x<block_size; x++) {
int pixel = ((uint16_t *) img->pixels)[(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();
}

View File

@ -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__

View File

@ -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)));
}
gif_open(&gif->fp, gif->width, gif->height, gif->loop);
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;
}
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, "<gif width:%d height:%d loop:%d>", self->width, self->height, self->loop);
mp_printf(print, "<gif width:%d height:%d color:%d loop:%d>", 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___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,
};

View File

@ -16,7 +16,7 @@ for i in range(30):
# 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()