diff --git a/external/solanaceae_tox b/external/solanaceae_tox index ec926dc..178f08e 160000 --- a/external/solanaceae_tox +++ b/external/solanaceae_tox @@ -1 +1 @@ -Subproject commit ec926dcb95be38e22a4b2ddc0c5cd5d14eb3a6b7 +Subproject commit 178f08ee9617bd89e12bb16163a56ec4cafcf012 diff --git a/src/image_loader_qoi.cpp b/src/image_loader_qoi.cpp index cd8f6e7..9382548 100644 --- a/src/image_loader_qoi.cpp +++ b/src/image_loader_qoi.cpp @@ -1,10 +1,72 @@ #include "./image_loader_qoi.hpp" -#include #include +#include +#include + #include +#define QOI_HEADER_SIZE 14 + +// true if data looks like qoi +static bool probe_qoi(const uint8_t* data, int64_t data_size) { + // smallest possible qoi + // magic + // dims (w h) + // chan + // col + if (data_size < QOI_HEADER_SIZE+8) { + return false; + } + + if ( + data[0] != 'q' || + data[1] != 'o' || + data[2] != 'i' || + data[3] != 'f' + ) { + return false; + } + + return true; +} + +// every qoi ends with 0,0,0,0,0,0,0,1 +// returns the index for the first byte after padding (aka the size of the qoi) +static int64_t probe_qoi_padding(const uint8_t* data, int64_t data_size) { + assert(data); + assert(data_size >= 8); + + // loop checks if 0x00 or 0x01, otherwise skips 2 + // we start at the last possible first position + for (int64_t pos = 7; pos < data_size;) { + if (data[pos] == 0x00) { + pos += 1; // not last char in padding, but we might be in the padding + } else if (data[pos] == 0x01) { + // last char from padding + if ( + data[pos-7] == 0x00 && + data[pos-6] == 0x00 && + data[pos-5] == 0x00 && + data[pos-4] == 0x00 && + data[pos-3] == 0x00 && + data[pos-2] == 0x00 && + data[pos-1] == 0x00 + ) { + //return pos - 7; // first byte of padding + return pos+1; + } else { + pos += 8; // cant be last byte for another 7 + } + } else { + pos += 2; + } + } + + return -1; // not found +} + ImageLoaderQOI::ImageInfo ImageLoaderQOI::loadInfoFromMemory(const uint8_t* data, uint64_t data_size) { ImageInfo res; @@ -26,24 +88,46 @@ ImageLoaderQOI::ImageInfo ImageLoaderQOI::loadInfoFromMemory(const uint8_t* data ImageLoaderQOI::ImageResult ImageLoaderQOI::loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) { ImageResult res; - qoi_desc desc; - - uint8_t* img_data = static_cast( - qoi_decode(data, data_size, &desc, 4) - ); - if (img_data == nullptr) { - // not readable + if (!probe_qoi(data, data_size)) { return res; } - res.width = desc.width; - res.height = desc.height; + for (size_t data_pos = 0; data_pos < data_size;) { + const auto qoi_size = probe_qoi_padding(data+data_pos, data_size-data_pos); + if (qoi_size < QOI_HEADER_SIZE+8) { + // too small + break; + } - auto& new_frame = res.frames.emplace_back(); - new_frame.ms = 0; - new_frame.data = {img_data, img_data+(desc.width*desc.height*4)}; + qoi_desc desc; + + uint8_t* img_data = static_cast( + qoi_decode(data + data_pos, qoi_size, &desc, 4) + ); + if (img_data == nullptr) { + // not readable + return res; + } + + if (res.width == 0 || res.height == 0) { + res.width = desc.width; + res.height = desc.height; + } + + if (res.width == desc.width && res.height == desc.height) { + auto& new_frame = res.frames.emplace_back(); + new_frame.ms = 25; // ffmpeg default + new_frame.data = {img_data, img_data+(desc.width*desc.height*4)}; + } else { + // dim mismatch, what do we do? abort, continue? + data_pos = data_size; // force exit loop + } + + free(img_data); + + data_pos += qoi_size; + } - free(img_data); return res; }