diff --git a/RELENTLESS/include/api.h b/RELENTLESS/include/api.h index 7e92319..9ddcdac 100644 --- a/RELENTLESS/include/api.h +++ b/RELENTLESS/include/api.h @@ -77,4 +77,6 @@ #include "pros/link.hpp" #endif +#include "asset.h" + #endif // _PROS_API_H_ diff --git a/RELENTLESS/include/gif-pros/gifclass.hpp b/RELENTLESS/include/gif-pros/gifclass.hpp index 09ecf27..fd45aa7 100644 --- a/RELENTLESS/include/gif-pros/gifclass.hpp +++ b/RELENTLESS/include/gif-pros/gifclass.hpp @@ -17,13 +17,23 @@ public: * @param fname the gif filename on the SD card (prefixed with /usd/) * @param parent the LVGL parent object */ - Gif(const char* fname, lv_obj_t* parent); + Gif(const asset file, lv_obj_t* parent); /** * Destructs and cleans the Gif class */ ~Gif(); + /** + * Pauses the GIF task + */ + void pause(); + + /** + * Resumes the GIF task + */ + void resume(); + /** * Deletes GIF and frees all allocated memory */ diff --git a/RELENTLESS/include/gif-pros/gifdec.h b/RELENTLESS/include/gif-pros/gifdec.h index 293801e..ab03013 100644 --- a/RELENTLESS/include/gif-pros/gifdec.h +++ b/RELENTLESS/include/gif-pros/gifdec.h @@ -55,4 +55,4 @@ void gd_close_gif(gd_GIF *gif); } #endif -#endif /* GIFDEC_H */ +#endif /* GIFDEC_H */ \ No newline at end of file diff --git a/RELENTLESS/src/gif-pros/gifclass.cpp b/RELENTLESS/src/gif-pros/gifclass.cpp new file mode 100644 index 0000000..2bcd62d --- /dev/null +++ b/RELENTLESS/src/gif-pros/gifclass.cpp @@ -0,0 +1,132 @@ +#include "gifclass.hpp" + +/** + * MIT License + * Copyright (c) 2019 Theo Lemay + * https://github.com/theol0403/gif-pros + */ + +/** + * Construct the Gif class + * @param fname the gif filename on the SD card (prefixed with /usd/) + * @param parent the LVGL parent object + */ +Gif::Gif(const asset file, lv_obj_t* parent) { + size_t len = file.size; + + _gifmem = file.buf; + + if (_gifmem != NULL) { + // create a FILE from memory buffer + FILE* memfp = fmemopen(_gifmem, len, "rb"); + + // open gof file + // will allocate memory for background and one animation frame. + _gif = gd_open_gif(memfp); + if(_gif == NULL) { + std::cerr << "Gif::Gif - error opening gif" << std::endl; + _cleanup(); + return; + } + + // memory for rendering frame + _buffer = (uint8_t*)malloc(_gif->width * _gif->height * BYTES_PER_PIXEL); + if(_buffer == NULL) { + // out of memory + _cleanup(); + std::cerr << "Gif::Gif - not enough memory for frame buffer" << std::endl; + } else { + _cbuf = new lv_color_t[_gif->width * _gif->height]; + _canvas = lv_canvas_create(parent, NULL); + lv_canvas_set_buffer(_canvas, _cbuf, _gif->width, _gif->height, LV_IMG_CF_TRUE_COLOR_ALPHA); + _task = pros::c::task_create(_render_task, static_cast(this), TASK_PRIORITY_DEFAULT-2, TASK_STACK_DEPTH_DEFAULT, "GIF"); + } + } +}; + + +/** + * Destructs and cleans the Gif class + */ +Gif::~Gif() { + _cleanup(); +} + +/** + * Pauses the GIF task + */ +void Gif::pause(){ + pros::c::task_suspend(_task); +} + +/** + * Resumes the GIF task + */ +void Gif::resume(){ + pros::c::task_resume(_task); +} + +/** +* Deletes GIF and frees all allocated memory +*/ +void Gif::clean() { + _cleanup(); +} + +/** + * Cleans and frees all allocated memory + */ +void Gif::_cleanup() { + if(_canvas) { lv_obj_del(_canvas); _canvas = nullptr; } + if(_cbuf) { delete[] _cbuf; _cbuf = nullptr; } + if(_buffer) { free(_buffer); _buffer = nullptr; } + if(_gif) { gd_close_gif(_gif); _gif = nullptr; } + if(_gifmem) { free(_gifmem); _gifmem = nullptr; } + // deleting task kills this thread + if(_task) { pros::c::task_delete(_task); _task = nullptr; } +} + + +/** + * Render cycle, blocks until loop count exceeds gif loop count flag (if any) + */ +void Gif::_render() { + + for (size_t looped = 1;; looped++) { + while (gd_get_frame(_gif)) { + int32_t now = pros::millis(); + + gd_render_frame(_gif, _buffer); + + for (size_t i = 0; i < _gif->height * _gif->width; i++) { + _cbuf[i].red = _buffer[(i * BYTES_PER_PIXEL)]; + _cbuf[i].green = _buffer[(i * BYTES_PER_PIXEL) + 1]; + _cbuf[i].blue = _buffer[(i * BYTES_PER_PIXEL) + 2]; + _cbuf[i].alpha = _buffer[(i * BYTES_PER_PIXEL) + 3]; + }; + + lv_obj_invalidate(_canvas); // force canvas redraw + + int32_t delay = _gif->gce.delay * 10; + int32_t delta = pros::millis() - now; + delay -= delta; + + if(delay > 0) pros::delay(delay); + } + + if(looped == _gif->loop_count) break; + gd_rewind(_gif); + } + + _cleanup(); +} + + +/** + * Calls _render() + * @param arg Gif* + */ +void Gif::_render_task(void* arg) { + Gif* instance = static_cast(arg); + instance->_render(); +} \ No newline at end of file diff --git a/RELENTLESS/src/gif-pros/gifdec.c b/RELENTLESS/src/gif-pros/gifdec.c new file mode 100644 index 0000000..a6dfdd8 --- /dev/null +++ b/RELENTLESS/src/gif-pros/gifdec.c @@ -0,0 +1,498 @@ +#include "gifdec.h" + +#include +#include +#include + +#include +#include +#include +#include + +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) + +typedef struct Entry { + uint16_t length; + uint16_t prefix; + uint8_t suffix; +} Entry; + +typedef struct Table { + int bulk; + int nentries; + Entry *entries; +} Table; + +static uint16_t +read_num(FILE *fp) +{ + uint8_t bytes[2]; + + fread(bytes, 1, 2, fp); + return bytes[0] + (((uint16_t) bytes[1]) << 8); +} + +gd_GIF * +gd_open_gif( FILE *fp ) +{ + uint8_t sigver[3]; + uint16_t width, height, depth; + uint8_t fdsz, bgidx, aspect; + int gct_sz; + gd_GIF *gif = NULL; + + if (fp == NULL) return NULL; + /* Header */ + fread(sigver, 1, 3, fp); + if (memcmp(sigver, "GIF", 3) != 0) { + fprintf(stderr, "invalid signature\n"); + goto fail; + } + /* Version */ + fread(sigver, 1, 3, fp); + if (memcmp(sigver, "89a", 3) != 0) { + fprintf(stderr, "invalid version\n"); + goto fail; + } + /* Width x Height */ + width = read_num(fp); + height = read_num(fp); + /* FDSZ */ + fread(&fdsz, 1, 1, fp); + /* Presence of GCT */ + if (!(fdsz & 0x80)) { + fprintf(stderr, "no global color table\n"); + goto fail; + } + /* Color Space's Depth */ + depth = ((fdsz >> 4) & 7) + 1; + /* Ignore Sort Flag. */ + /* GCT Size */ + gct_sz = 1 << ((fdsz & 0x07) + 1); + /* Background Color Index */ + fread(&bgidx, 1, 1, fp); + /* Aspect Ratio */ + fread(&aspect, 1, 1, fp); + /* Create gd_GIF Structure. */ + gif = calloc(1, sizeof(*gif) + (BYTES_PER_PIXEL+1) * width * height); + if (!gif) goto fail; + gif->fp = fp; + gif->width = width; + gif->height = height; + gif->depth = depth; + /* Read GCT */ + gif->gct.size = gct_sz; + fread(gif->gct.colors, 1, 3 * gif->gct.size, fp); + gif->palette = &gif->gct; + gif->bgindex = bgidx; + gif->canvas = (uint8_t *) &gif[1]; + gif->frame = &gif->canvas[BYTES_PER_PIXEL * width * height]; + if (gif->bgindex) + memset(gif->frame, gif->bgindex, gif->width * gif->height); + gif->anim_start = ftell(fp); + goto ok; + fail: + fclose(fp); + ok: + return gif; +} + +static void +discard_sub_blocks(gd_GIF *gif) +{ + uint8_t size; + + do { + fread(&size, 1, 1, gif->fp); + fseek(gif->fp, size, SEEK_CUR); + } while (size); +} + +static void +read_plain_text_ext(gd_GIF *gif) +{ + if (gif->plain_text) { + uint16_t tx, ty, tw, th; + uint8_t cw, ch, fg, bg; + off_t sub_block; + fseek(gif->fp, 1, SEEK_CUR); /* block size = 12 */ + tx = read_num(gif->fp); + ty = read_num(gif->fp); + tw = read_num(gif->fp); + th = read_num(gif->fp); + fread( &cw, 1, 1, gif->fp); + fread( &ch, 1, 1, gif->fp); + fread( &fg, 1, 1, gif->fp); + fread( &bg, 1, 1, gif->fp); + sub_block = ftell(gif->fp); + gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); + fseek(gif->fp, sub_block, SEEK_SET); + } else { + /* Discard plain text metadata. */ + fseek(gif->fp, 13, SEEK_CUR); + } + /* Discard plain text sub-blocks. */ + discard_sub_blocks(gif); +} + +static void +read_graphic_control_ext(gd_GIF *gif) +{ + uint8_t rdit; + + /* Discard block size (always 0x04). */ + fseek(gif->fp, 1, SEEK_CUR); + fread( &rdit, 1, 1, gif->fp); + gif->gce.disposal = (rdit >> 2) & 3; + gif->gce.input = rdit & 2; + gif->gce.transparency = rdit & 1; + gif->gce.delay = read_num(gif->fp); + fread( &gif->gce.tindex, 1, 1, gif->fp); + /* Skip block terminator. */ + fseek(gif->fp, 1, SEEK_CUR); +} + +static void +read_comment_ext(gd_GIF *gif) +{ + if (gif->comment) { + off_t sub_block = ftell(gif->fp); + gif->comment(gif); + fseek(gif->fp, sub_block, SEEK_SET); + } + /* Discard comment sub-blocks. */ + discard_sub_blocks(gif); +} + +static void +read_application_ext(gd_GIF *gif) +{ + char app_id[8]; + char app_auth_code[3]; + + /* Discard block size (always 0x0B). */ + fseek(gif->fp, 1, SEEK_CUR); + /* Application Identifier. */ + fread( app_id, 1, 8, gif->fp); + /* Application Authentication Code. */ + fread( app_auth_code, 1, 3, gif->fp); + if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { + /* Discard block size (0x03) and constant byte (0x01). */ + fseek(gif->fp, 2, SEEK_CUR); + gif->loop_count = read_num(gif->fp); + /* Skip block terminator. */ + fseek(gif->fp, 1, SEEK_CUR); + } else if (gif->application) { + off_t sub_block = ftell(gif->fp); + gif->application(gif, app_id, app_auth_code); + fseek(gif->fp, sub_block, SEEK_SET); + discard_sub_blocks(gif); + } else { + discard_sub_blocks(gif); + } +} + +static void +read_ext(gd_GIF *gif) +{ + uint8_t label; + + fread( &label, 1, 1, gif->fp); + switch (label) { + case 0x01: + read_plain_text_ext(gif); + break; + case 0xF9: + read_graphic_control_ext(gif); + break; + case 0xFE: + read_comment_ext(gif); + break; + case 0xFF: + read_application_ext(gif); + break; + default: + fprintf(stderr, "unknown extension: %02X\n", label); + } +} + +static Table * +new_table(int key_size) +{ + int key; + int init_bulk = MAX(1 << (key_size + 1), 0x100); + Table *table = malloc(sizeof(*table) + sizeof(Entry) * init_bulk); + if (table) { + table->bulk = init_bulk; + table->nentries = (1 << key_size) + 2; + table->entries = (Entry *) &table[1]; + for (key = 0; key < (1 << key_size); key++) + table->entries[key] = (Entry) {1, 0xFFF, key}; + } + return table; +} + +/* Add table entry. Return value: + * 0 on success + * +1 if key size must be incremented after this addition + * -1 if could not realloc table */ + static int + add_entry(Table **tablep, uint16_t length, uint16_t prefix, uint8_t suffix) + { + Table *table = *tablep; + if (table->nentries == table->bulk) { + table->bulk *= 2; + table = realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); + if (!table) return -1; + table->entries = (Entry *) &table[1]; + *tablep = table; + } + table->entries[table->nentries] = (Entry) {length, prefix, suffix}; + table->nentries++; + if ((table->nentries & (table->nentries - 1)) == 0) + return 1; + return 0; +} + +static uint16_t +get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) +{ + int bits_read; + int rpad; + int frag_size; + uint16_t key; + + key = 0; + for (bits_read = 0; bits_read < key_size; bits_read += frag_size) { + rpad = (*shift + bits_read) % 8; + if (rpad == 0) { + /* Update byte. */ + if (*sub_len == 0) fread( sub_len, 1, 1, gif->fp); /* Must be nonzero! */ + fread( byte, 1, 1, gif->fp); + (*sub_len)--; + } + frag_size = MIN(key_size - bits_read, 8 - rpad); + key |= ((uint16_t) ((*byte) >> rpad)) << bits_read; + } + /* Clear extra bits to the left. */ + key &= (1 << key_size) - 1; + *shift = (*shift + key_size) % 8; + return key; +} + +/* Compute output index of y-th input line, in frame of height h. */ +static int +interlaced_line_index(int h, int y) +{ + int p; /* number of lines in current pass */ + + p = (h - 1) / 8 + 1; + if (y < p) /* pass 1 */ + return y * 8; + y -= p; + p = (h - 5) / 8 + 1; + if (y < p) /* pass 2 */ + return y * 8 + 4; + y -= p; + p = (h - 3) / 4 + 1; + if (y < p) /* pass 3 */ + return y * 4 + 2; + y -= p; + /* pass 4 */ + return y * 2 + 1; +} + +/* Decompress image pixels. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ +static int +read_image_data(gd_GIF *gif, int interlace) +{ + uint8_t sub_len, shift, byte; + int init_key_size, key_size, table_is_full; + int frm_off, str_len, p, x, y; + uint16_t key, clear, stop; + int ret; + Table *table; + Entry entry = (Entry) {0, 0, 0}; // prevent warning + off_t start, end; + + fread( &byte, 1, 1, gif->fp); + key_size = (int) byte; + start = ftell(gif->fp); + discard_sub_blocks(gif); + end = ftell(gif->fp); + fseek(gif->fp, start, SEEK_SET); + clear = 1 << key_size; + stop = clear + 1; + table = new_table(key_size); + key_size++; + init_key_size = key_size; + sub_len = shift = 0; + key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ + frm_off = 0; + ret = 0; + table_is_full = 0; + str_len = 0; + while (1) { + if (key == clear) { + key_size = init_key_size; + table->nentries = (1 << (key_size - 1)) + 2; + table_is_full = 0; + } else if (!table_is_full) { + ret = add_entry(&table, str_len + 1, key, entry.suffix); + if (ret == -1) { + free(table); + return -1; + } + if (table->nentries == 0x1000) { + ret = 0; + table_is_full = 1; + } + } + key = get_key(gif, key_size, &sub_len, &shift, &byte); + if (key == clear) continue; + if (key == stop) break; + if (ret == 1) key_size++; + entry = table->entries[key]; + str_len = entry.length; + while (1) { + p = frm_off + entry.length - 1; + x = p % gif->fw; + y = p / gif->fw; + if (interlace) + y = interlaced_line_index((int) gif->fh, y); + gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; + if (entry.prefix == 0xFFF) + break; + else + entry = table->entries[entry.prefix]; + } + frm_off += str_len; + if (key < table->nentries - 1 && !table_is_full) + table->entries[table->nentries - 1].suffix = entry.suffix; + } + free(table); + fread( &sub_len, 1, 1, gif->fp); /* Must be zero! */ + fseek(gif->fp, end, SEEK_SET); + return 0; +} + +/* Read image. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ +static int +read_image(gd_GIF *gif) +{ + uint8_t fisrz; + int interlace; + + /* Image Descriptor. */ + gif->fx = read_num(gif->fp); + gif->fy = read_num(gif->fp); + gif->fw = read_num(gif->fp); + gif->fh = read_num(gif->fp); + fread( &fisrz, 1, 1, gif->fp); + interlace = fisrz & 0x40; + /* Ignore Sort Flag. */ + /* Local Color Table? */ + if (fisrz & 0x80) { + /* Read LCT */ + gif->lct.size = 1 << ((fisrz & 0x07) + 1); + fread( gif->lct.colors, 1, 3 * gif->lct.size, gif->fp); + gif->palette = &gif->lct; + } else + gif->palette = &gif->gct; + /* Image Data. */ + return read_image_data(gif, interlace); +} + +static void +render_frame_rect(gd_GIF *gif, uint8_t *buffer) +{ + int i, j, k; + uint8_t index, *color; + i = gif->fy * gif->width + gif->fx; + for (j = 0; j < gif->fh; j++) { + for (k = 0; k < gif->fw; k++) { + index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k]; + color = &gif->palette->colors[index*3]; + if (!gif->gce.transparency || index != gif->gce.tindex) { + int offset = (i+k)*BYTES_PER_PIXEL; + buffer[offset+0] = *(color+0); + buffer[offset+1] = *(color+1); + buffer[offset+2] = *(color+2); + if(index == gif->bgindex && gif->gce.transparency) { + buffer[offset+3] = 0; + } else { + buffer[offset+3] = 255; + } + } + } + i += gif->width; + } +} + +static void +dispose(gd_GIF *gif) +{ + int i, j, k; + uint8_t *bgcolor; + switch (gif->gce.disposal) { + case 2: /* Restore to background color. */ + bgcolor = &gif->palette->colors[gif->bgindex*3]; + i = gif->fy * gif->width + gif->fx; + for (j = 0; j < gif->fh; j++) { + for (k = 0; k < gif->fw; k++) { + memcpy(&gif->canvas[(i+k)*BYTES_PER_PIXEL], bgcolor, BYTES_PER_PIXEL); + } + i += gif->width; + } + break; + case 3: /* Restore to previous, i.e., don't update canvas.*/ + break; + default: + /* Add frame non-transparent pixels to canvas. */ + render_frame_rect(gif, gif->canvas); + } +} + +/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ +int +gd_get_frame(gd_GIF *gif) +{ + char sep; + + dispose(gif); + fread( &sep, 1, 1, gif->fp); + while (sep != ',') { + if (sep == ';') + return 0; + if (sep == '!') + read_ext(gif); + else return -1; + fread( &sep, 1, 1, gif->fp); + } + if (read_image(gif) == -1) + return -1; + return 1; +} + +void +gd_render_frame(gd_GIF *gif, uint8_t *buffer) +{ + memcpy(buffer, gif->canvas, gif->width * gif->height * BYTES_PER_PIXEL); + render_frame_rect(gif, buffer); +} + +void +gd_rewind(gd_GIF *gif) +{ + fseek(gif->fp, gif->anim_start, SEEK_SET); +} + +void +gd_close_gif(gd_GIF *gif) +{ + fclose(gif->fp); + free(gif); +} \ No newline at end of file diff --git a/RELENTLESS/src/globals.cpp b/RELENTLESS/src/globals.cpp index e4c296e..d74e54d 100644 --- a/RELENTLESS/src/globals.cpp +++ b/RELENTLESS/src/globals.cpp @@ -12,6 +12,11 @@ namespace globals { // Chassis pros::Controller master(CONTROLLER_MASTER); + /* + {-2, -6, 12, 5}, -> left + {-16, 1, 4, -3}, -> right + */ + pros::Motor motor_tlf(12, MOTOR_GEARSET_06, false); pros::Motor motor_blb(6, MOTOR_GEARSET_06, true); pros::Motor motor_blf(2, MOTOR_GEARSET_06, true); @@ -25,8 +30,6 @@ namespace globals { pros::Motor_Group right_drive({ motor_trf, motor_trb, motor_brf, motor_brb }); // Electronics / Pneumatics / Sensors - pros::Distance intake_dist_sens(21); - pros::ADIDigitalOut pto_piston('A'); pros::ADIDigitalOut left_wing_piston('C'); diff --git a/RELENTLESS/src/main.cpp b/RELENTLESS/src/main.cpp index 7358299..094816b 100644 --- a/RELENTLESS/src/main.cpp +++ b/RELENTLESS/src/main.cpp @@ -7,6 +7,11 @@ #include "main.h" #include "wings.h" +#include "asset.h" +#include "gif-pros/gifclass.hpp" + +ASSET(griddy_gif) + using namespace globals; using namespace superstruct; @@ -49,7 +54,7 @@ void initialize() { chassis.initialize(); ary::autonselector::initialize(); - + //pros::lcd::register_btn1_cb(centerButtonCallback); pros::Task telemetryTask(telemetry); } @@ -62,16 +67,15 @@ void autonomous() { } void opcontrol() { + //Gif gif(griddy_gif, lv_scr_act()); opControlInit(); // Configure the chassis for driver control while (true) { - /* Handle controls for whoever is driving the bot at the moment, each available user a respective set of controls' Available Options: RENU, RIA, CHRIS */ - if (currentuser == RENU) { chassis.arcade_standard(ary::SPLIT, ary::DEFAULT); renu_control(); diff --git a/RELENTLESS/static/griddy.gif b/RELENTLESS/static/griddy.gif new file mode 100644 index 0000000..c35e1ad Binary files /dev/null and b/RELENTLESS/static/griddy.gif differ