2626#include <stdlib.h>
2727#include <string.h>
2828
29+ // TODO: remove the dependency.
30+ #include <lz4.h>
31+
2932#ifdef __cplusplus
3033extern "C" {
3134#endif
@@ -127,6 +130,9 @@ qoir_pixel_format__bytes_per_pixel(qoir_pixel_format pixfmt) {
127130#define QOIR_TILE_SIZE 0x80
128131#define QOIR_TILE_SHIFT 7
129132
133+ // QOIR_TS2 is the maximum (inclusive) number of pixels in a tile.
134+ #define QOIR_TS2 (QOIR_TILE_SIZE * QOIR_TILE_SIZE)
135+
130136// -------- QOIR Decode
131137
132138typedef struct qoir_decode_pixel_configuration_result_struct {
@@ -141,7 +147,10 @@ qoir_decode_pixel_configuration( //
141147
142148typedef struct qoir_decode_buffer_struct {
143149 struct {
144- uint8_t rgba [4 * QOIR_TILE_SIZE * QOIR_TILE_SIZE ];
150+ // opcodes has to be before literals, so that (worst case) we can read (and
151+ // ignore) 8 bytes past the end of the opcodes array. See §
152+ uint8_t opcodes [4 * QOIR_TS2 ];
153+ uint8_t literals [4 * QOIR_TS2 ];
145154 } private_impl ;
146155} qoir_decode_buffer ;
147156
@@ -173,7 +182,11 @@ qoir_decode( //
173182
174183typedef struct qoir_encode_buffer_struct {
175184 struct {
176- uint8_t rgba [4 * QOIR_TILE_SIZE * QOIR_TILE_SIZE ];
185+ // opcodes' size is (5 * QOIR_TS2), not (4 * QOIR_TS2), because in the
186+ // worst case (during encoding, before discarding the too-long opcodes in
187+ // favor of literals), each pixel uses QOI_OP_RGBA, 5 bytes each.
188+ uint8_t opcodes [5 * QOIR_TS2 ];
189+ uint8_t literals [4 * QOIR_TS2 ];
177190 } private_impl ;
178191} qoir_encode_buffer ;
179192
@@ -558,27 +571,57 @@ qoir_private_decode_qpix_payload( //
558571 src_ptr += 4 ;
559572 src_len -= 4 ;
560573 size_t tile_len = prefix & 0xFFFFFF ;
561- if (src_len < (tile_len + 8 )) {
574+ if ((src_len < (tile_len + 8 )) || //
575+ (((4 * QOIR_TS2 ) < tile_len ) && ((prefix >> 31 ) != 0 ))) {
562576 return qoir_status_message__error_invalid_data ;
563577 }
564578
565- const uint8_t * rgba = NULL ;
579+ const uint8_t * literals = NULL ;
566580 switch (prefix >> 24 ) {
567581 case 0 : { // Literals tile format.
568582 if (tile_len != (4 * tw * th )) {
569583 return qoir_status_message__error_invalid_data ;
570584 }
571- rgba = src_ptr ;
585+ literals = src_ptr ;
572586 break ;
573587 }
574588 case 1 : { // Opcodes tile format.
575589 const char * status_message = qoir_private_decode_tile_opcodes (
576- decbuf -> private_impl .rgba , (uint32_t )tw , (uint32_t )th , //
590+ decbuf -> private_impl .literals , (uint32_t )tw , (uint32_t )th , //
577591 src_ptr , tile_len + 8 ); // See § for +8.
578592 if (status_message ) {
579593 return status_message ;
580594 }
581- rgba = decbuf -> private_impl .rgba ;
595+ literals = decbuf -> private_impl .literals ;
596+ break ;
597+ }
598+ case 2 : { // LZ4-Literals tile format.
599+ int n = LZ4_decompress_safe ((const char * )src_ptr , //
600+ (char * )decbuf -> private_impl .literals , //
601+ tile_len , //
602+ sizeof (decbuf -> private_impl .literals ));
603+ if (n < 0 ) {
604+ return qoir_status_message__error_invalid_data ;
605+ }
606+ literals = decbuf -> private_impl .literals ;
607+ break ;
608+ }
609+ case 3 : { // LZ4-Opcodes tile format.
610+ int n = LZ4_decompress_safe ((const char * )src_ptr , //
611+ (char * )decbuf -> private_impl .opcodes , //
612+ tile_len , //
613+ sizeof (decbuf -> private_impl .opcodes ));
614+ if (n < 0 ) {
615+ return qoir_status_message__error_invalid_data ;
616+ }
617+ const char * status_message = qoir_private_decode_tile_opcodes (
618+ decbuf -> private_impl .literals , //
619+ (uint32_t )tw , (uint32_t )th , //
620+ decbuf -> private_impl .opcodes , n + 8 ); // See § for +8.
621+ if (status_message ) {
622+ return status_message ;
623+ }
624+ literals = decbuf -> private_impl .literals ;
582625 break ;
583626 }
584627 default :
@@ -590,7 +633,7 @@ qoir_private_decode_qpix_payload( //
590633
591634 uint8_t * dp =
592635 dst_data + (dst_stride_in_bytes * ty ) + (num_dst_channels * tx );
593- (* swizzle )(dp , dst_stride_in_bytes , rgba , 4 * tw , tw , th );
636+ (* swizzle )(dp , dst_stride_in_bytes , literals , 4 * tw , tw , th );
594637 }
595638 }
596639
@@ -879,30 +922,44 @@ qoir_private_encode_qpix_payload( //
879922 const uint8_t * sp = src_pixbuf -> data +
880923 (src_pixbuf -> stride_in_bytes * ty ) +
881924 (num_src_channels * tx );
882- (* swizzle )(encbuf -> private_impl .rgba , 4 * tw , //
883- sp , src_pixbuf -> stride_in_bytes , //
925+ (* swizzle )(encbuf -> private_impl .literals , 4 * tw , //
926+ sp , src_pixbuf -> stride_in_bytes , //
884927 tw , th );
885928
886- // qoir_private_encode_tile_opcodes can (temporarily) write up to (5 *
887- // QOIR_TILE_SIZE * QOIR_TILE_SIZE) bytes, since QOI_OP_RGBA is 5 bytes,
888- // but we use the Literals tile format if its shorter, worst case is (4 *
889- // QTS * QTS). The difference, ((5 - 4) * QTS * QTS), is pre-allocated by
890- // the caller as extra 'scratch space'. Reference: †
891929 qoir_private_size_t_result r = qoir_private_encode_tile_opcodes (
892- dp + 4 , encbuf -> private_impl .rgba , tw , th );
930+ encbuf -> private_impl .opcodes , encbuf -> private_impl .literals , tw , th );
931+ size_t literals_len = 4 * tw * th ;
893932 if (r .status_message ) {
894933 result .status_message = r .status_message ;
895934 return r ;
896- } else if (r .value >= (4 * tw * th )) {
897- // Use the Literals tile format.
898- size_t n = 4 * tw * th ;
899- memcpy (dp + 4 , encbuf -> private_impl .rgba , n );
900- qoir_private_poke_u32le (dp , (uint32_t )n );
901- dp += 4 + n ;
935+
936+ } else if (r .value >= literals_len ) {
937+ // Use the Literals or LZ4-Literals tile format.
938+ int n = LZ4_compress_default (
939+ ((const char * )(encbuf -> private_impl .literals )), ((char * )(dp + 4 )),
940+ (int )literals_len , 4 * QOIR_TS2 );
941+ if ((0 < n ) && (n < literals_len )) {
942+ qoir_private_poke_u32le (dp , 0x02000000 | (uint32_t )n );
943+ dp += 4 + n ;
944+ } else {
945+ memcpy (dp + 4 , encbuf -> private_impl .literals , literals_len );
946+ qoir_private_poke_u32le (dp , 0x00000000 | (uint32_t )literals_len );
947+ dp += 4 + literals_len ;
948+ }
949+
902950 } else {
903- // Use the Opcodes tile format.
904- qoir_private_poke_u32le (dp , ((uint32_t )(r .value | 0x01000000 )));
905- dp += 4 + r .value ;
951+ // Use the Opcodes or LZ4-Opcodes tile format.
952+ int n =
953+ LZ4_compress_default (((const char * )(encbuf -> private_impl .opcodes )),
954+ ((char * )(dp + 4 )), (int )r .value , 4 * QOIR_TS2 );
955+ if ((0 < n ) && (n < r .value )) {
956+ qoir_private_poke_u32le (dp , 0x03000000 | (uint32_t )n );
957+ dp += 4 + n ;
958+ } else {
959+ memcpy (dp + 4 , encbuf -> private_impl .opcodes , r .value );
960+ qoir_private_poke_u32le (dp , 0x01000000 | (uint32_t )r .value );
961+ dp += 4 + r .value ;
962+ }
906963 }
907964 }
908965 }
@@ -950,12 +1007,11 @@ qoir_encode( //
9501007 uint64_t height_in_tiles =
9511008 (src_pixbuf -> pixcfg .height_in_pixels + QOIR_TILE_MASK ) >> QOIR_TILE_SHIFT ;
9521009 uint64_t tile_len_worst_case =
953- 4 + (4 * QOIR_TILE_SIZE * QOIR_TILE_SIZE ); // Prefix + literal format.
1010+ 4 + (4 * QOIR_TS2 ); // Prefix + literal format.
9541011 uint64_t dst_len_worst_case =
9551012 (width_in_tiles * height_in_tiles * tile_len_worst_case ) +
956- 44 + // QOIR, QPIX and QEND chunk headers are 12 bytes each.
957- // QOIR also has an 8 byte payload.
958- (QOIR_TILE_SIZE * QOIR_TILE_SIZE ); // See †.
1013+ 44 ; // QOIR, QPIX and QEND chunk headers are 12 bytes each.
1014+ // QOIR also has an 8 byte payload.
9591015 if (dst_len_worst_case > SIZE_MAX ) {
9601016 result .status_message =
9611017 qoir_status_message__error_unsupported_pixbuf_dimensions ;
0 commit comments