diff --git a/qoi.h b/qoi.h index d473df24..1a954421 100644 --- a/qoi.h +++ b/qoi.h @@ -83,8 +83,8 @@ implementation you can define QOI_ZEROARR before including this library. -- Data Format -A QOI file has a 14 byte header, followed by any number of data "chunks" and 8 -zero-bytes to mark the end of the data stream. +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. struct qoi_header_t { char magic[4]; // magic bytes "qoif" @@ -120,7 +120,7 @@ values encoded in these data bits have the most significant bit on the left. The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the presence of an 8-bit tag first. -The byte stream is padded with 8 zero-bytes at the end. +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. The possible chunks are: @@ -135,6 +135,9 @@ The possible chunks are: 2-bit tag b00 6-bit index into the color index array: 0..63 +A valid encoder must not issue 7 or more consecutive QOI_OP_INDEX chunks to the +index 0, to avoid confusion with the 8 byte end marker. + .- QOI_OP_DIFF -----------. | Byte[0] | @@ -168,8 +171,8 @@ Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as The green channel is used to indicate the general direction of change and is encoded in 6 bits. The red and green channels (dr and db) base their diffs off of the green channel difference and are encoded in 4 bits. I.e.: - dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g) - db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g) + dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g) + db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g) The difference to the current channel values are using a wraparound operation, so "10 - 13" will result in 253, while "250 + 7" will result in 1. @@ -342,13 +345,20 @@ Implementation */ (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ ((unsigned int)'i') << 8 | ((unsigned int)'f')) #define QOI_HEADER_SIZE 14 -#define QOI_PADDING 8 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) typedef union { struct { unsigned char r, g, b, a; } rgba; unsigned int v; } qoi_rgba_t; +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { bytes[(*p)++] = (0xff000000 & v) >> 24; bytes[(*p)++] = (0x00ff0000 & v) >> 16; @@ -376,14 +386,15 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { data == NULL || out_len == NULL || desc == NULL || desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || - desc->colorspace > 2 + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } max_size = desc->width * desc->height * (desc->channels + 1) + - QOI_HEADER_SIZE + QOI_PADDING; + QOI_HEADER_SIZE + sizeof(qoi_padding); p = 0; bytes = (unsigned char *) QOI_MALLOC(max_size); @@ -488,8 +499,8 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { px_prev = px; } - for (i = 0; i < QOI_PADDING; i++) { - bytes[p++] = 0; + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; } *out_len = p; @@ -502,13 +513,13 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { unsigned char *pixels; qoi_rgba_t index[64]; qoi_rgba_t px; - int px_len, chunks_len, px_pos; + int px_len, chunks_len, px_pos; int p = 0, run = 0; if ( data == NULL || desc == NULL || (channels != 0 && channels != 3 && channels != 4) || - size < QOI_HEADER_SIZE + QOI_PADDING + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) ) { return NULL; } @@ -524,8 +535,9 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { if ( desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || - desc->colorspace > 2 || - header_magic != QOI_MAGIC + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } @@ -546,7 +558,7 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { px.rgba.b = 0; px.rgba.a = 255; - chunks_len = size - QOI_PADDING; + chunks_len = size - (int)sizeof(qoi_padding); for (px_pos = 0; px_pos < px_len; px_pos += channels) { if (run > 0) { run--; @@ -636,6 +648,10 @@ void *qoi_read(const char *filename, qoi_desc *desc, int channels) { fseek(f, 0, SEEK_END); size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } fseek(f, 0, SEEK_SET); data = QOI_MALLOC(size);