GifWriter: improve efficiency

* Increase colorspace conversion efficiency.
   This not only avoids a function call, it avoids the time-consuming
   switch statement in conver_pixel (replacing it with a single
   conditional on the byteswap flag + accounting for BGR/RGB during
   palette creation)

 * Buffer all the bytes of a single frame together.  By reducing
   low level write calls we get a decent speed increase even though
   it increases data-shuffling a bit.

Together with some other changes that enable "double buffered" camera
capture, this gets me to 8.8fps capturing QVGA (320x240) gifs and
11fps capturing 240x240 square gifs.
This commit is contained in:
Jeff Epler 2021-10-26 11:11:28 -05:00
parent 3e020a73a8
commit 7d6ac96001
2 changed files with 70 additions and 29 deletions

View File

@ -26,6 +26,9 @@
* THE SOFTWARE.
*/
#include <string.h>
#include "py/gc.h"
#include "py/runtime.h"
#include "shared-module/gifio/GifWriter.h"
@ -35,16 +38,30 @@
#define BLOCK_SIZE (126) // (2^7) - 2 // (DO NOT CHANGE!)
static void handle_error(const char *what, int error) {
if (error != 0) {
mp_raise_OSError(error);
static void handle_error(gifio_gifwriter_t *self) {
if (self->error != 0) {
mp_raise_OSError(self->error);
}
}
static void write_data(gifio_gifwriter_t *self, const void *data, size_t size) {
static void flush_data(gifio_gifwriter_t *self) {
if (self->cur == 0) {
return;
}
int error = 0;
self->file_proto->write(self->file, data, size, &error);
handle_error("write_data", error);
self->file_proto->write(self->file, self->data, self->cur, &error);
self->cur = 0;
if (error != 0) {
self->error = error;
}
}
// These "write" calls _MUST_ have enough buffer space available! This is
// ensured by allocating the proper buffer size in construct.
static void write_data(gifio_gifwriter_t *self, const void *data, size_t size) {
assert(self->cur + size <= self->size);
memcpy(self->data + self->cur, data, size);
self->cur += size;
}
static void write_byte(gifio_gifwriter_t *self, uint8_t value) {
@ -70,23 +87,44 @@ void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *
self->colorspace = colorspace;
self->own_file = own_file;
size_t nblocks = (width * height + 125) / 126;
self->size = nblocks * 128 + 4;
self->data = gc_alloc(self->size, 0, false);
self->cur = 0;
self->error = 0;
write_data(self, "GIF89a", 6);
write_word(self, width);
write_word(self, height);
write_data(self, (uint8_t []) {0xF6, 0x00, 0x00}, 3);
if (colorspace == DISPLAYIO_COLORSPACE_RGB888) {
mp_raise_TypeError(translate("unsupported colorspace for GifWriter"));
switch (colorspace) {
case DISPLAYIO_COLORSPACE_RGB565:
case DISPLAYIO_COLORSPACE_RGB565_SWAPPED:
case DISPLAYIO_COLORSPACE_BGR565:
case DISPLAYIO_COLORSPACE_BGR565_SWAPPED:
case DISPLAYIO_COLORSPACE_L8:
break;
default:
mp_raise_TypeError(translate("unsupported colorspace for GifWriter"));
}
bool color = (colorspace != DISPLAYIO_COLORSPACE_L8);
bool bgr = (colorspace == DISPLAYIO_COLORSPACE_BGR565 || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED);
self->byteswap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED);
if (color) {
for (int i = 0; i < 128; i++) {
int red = (int)(((((i & 0x60) >> 5) * 255) + 1.5) / 3);
int green = (int)(((((i & 0x1C) >> 2) * 255) + 3.5) / 7);
int blue = (int)((((i & 0x3) * 255) + 1.5) / 3);
write_data(self, (uint8_t []) {red, green, blue}, 3);
if (bgr) {
write_data(self, (uint8_t []) {blue, red, green}, 3);
} else {
write_data(self, (uint8_t []) {red, green, blue}, 3);
}
}
} else {
for (int i = 0; i < 128; i++) {
@ -101,7 +139,8 @@ void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *
write_data(self, (uint8_t []) {0x03, 0x01, 0x00, 0x00, 0x00}, 5);
}
flush_data(self);
handle_error(self);
}
bool shared_module_gifio_gifwriter_deinited(gifio_gifwriter_t *self) {
@ -163,10 +202,13 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b
block_data[0] = 1 + block_size;
for (int j = 0; j < block_size; j++) {
int pixel = displayio_colorconverter_convert_pixel(self->colorspace, (*pixels++));
int red = (pixel >> (16 + 6)) & 0x3;
int green = (pixel >> (8 + 5)) & 0x7;
int blue = (pixel >> 6) & 0x3;
int pixel = *pixels++;
if (self->byteswap) {
pixel = __builtin_bswap16(pixel);
}
int red = (pixel >> (11 + (5 - 2))) & 0x3;
int green = (pixel >> (5 + (6 - 3))) & 0x7;
int blue = (pixel >> (0 + (5 - 2))) & 0x3;
block_data[j + 2] = (red << 5) | (green << 2) | blue;
}
write_data(self, block_data, 2 + block_size);
@ -174,25 +216,20 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b
}
write_data(self, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code
int error = 0;
self->file_proto->ioctl(self->file, MP_STREAM_FLUSH, 0, &error);
handle_error("flush", error);
flush_data(self);
handle_error(self);
}
void shared_module_gifio_gifwriter_close(gifio_gifwriter_t *self) {
// we want to ensure the stream is closed even if the first write failed, so we don't use write_data
int error1 = 0;
self->file_proto->write(self->file, ";", 1, &error1);
write_byte(self, ';');
flush_data(self);
int error2 = 0;
if (self->own_file) {
self->file_proto->ioctl(self->file, MP_STREAM_CLOSE, 0, &error2);
} else {
self->file_proto->ioctl(self->file, MP_STREAM_FLUSH, 0, &error2);
}
int error = 0;
self->file_proto->ioctl(self->file, self->own_file ? MP_STREAM_CLOSE : MP_STREAM_FLUSH, 0, &error);
self->file = NULL;
handle_error("write", error1);
handle_error("close", error2);
if (error != 0) {
self->error = error;
}
handle_error(self);
}

View File

@ -36,5 +36,9 @@ typedef struct gifio_gifwriter {
const mp_stream_p_t *file_proto;
displayio_colorspace_t colorspace;
int width, height;
int error;
uint8_t *data;
size_t cur, size;
bool own_file;
bool byteswap;
} gifio_gifwriter_t;