26
26
#include <stdlib.h>
27
27
#include <string.h>
28
28
29
+ // TODO: remove the dependency.
30
+ #include <lz4.h>
31
+
29
32
#ifdef __cplusplus
30
33
extern "C" {
31
34
#endif
@@ -127,6 +130,9 @@ qoir_pixel_format__bytes_per_pixel(qoir_pixel_format pixfmt) {
127
130
#define QOIR_TILE_SIZE 0x80
128
131
#define QOIR_TILE_SHIFT 7
129
132
133
+ // QOIR_TS2 is the maximum (inclusive) number of pixels in a tile.
134
+ #define QOIR_TS2 (QOIR_TILE_SIZE * QOIR_TILE_SIZE)
135
+
130
136
// -------- QOIR Decode
131
137
132
138
typedef struct qoir_decode_pixel_configuration_result_struct {
@@ -141,7 +147,10 @@ qoir_decode_pixel_configuration( //
141
147
142
148
typedef struct qoir_decode_buffer_struct {
143
149
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 ];
145
154
} private_impl ;
146
155
} qoir_decode_buffer ;
147
156
@@ -173,7 +182,11 @@ qoir_decode( //
173
182
174
183
typedef struct qoir_encode_buffer_struct {
175
184
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 ];
177
190
} private_impl ;
178
191
} qoir_encode_buffer ;
179
192
@@ -558,27 +571,57 @@ qoir_private_decode_qpix_payload( //
558
571
src_ptr += 4 ;
559
572
src_len -= 4 ;
560
573
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 ))) {
562
576
return qoir_status_message__error_invalid_data ;
563
577
}
564
578
565
- const uint8_t * rgba = NULL ;
579
+ const uint8_t * literals = NULL ;
566
580
switch (prefix >> 24 ) {
567
581
case 0 : { // Literals tile format.
568
582
if (tile_len != (4 * tw * th )) {
569
583
return qoir_status_message__error_invalid_data ;
570
584
}
571
- rgba = src_ptr ;
585
+ literals = src_ptr ;
572
586
break ;
573
587
}
574
588
case 1 : { // Opcodes tile format.
575
589
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 , //
577
591
src_ptr , tile_len + 8 ); // See § for +8.
578
592
if (status_message ) {
579
593
return status_message ;
580
594
}
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 ;
582
625
break ;
583
626
}
584
627
default :
@@ -590,7 +633,7 @@ qoir_private_decode_qpix_payload( //
590
633
591
634
uint8_t * dp =
592
635
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 );
594
637
}
595
638
}
596
639
@@ -879,30 +922,44 @@ qoir_private_encode_qpix_payload( //
879
922
const uint8_t * sp = src_pixbuf -> data +
880
923
(src_pixbuf -> stride_in_bytes * ty ) +
881
924
(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 , //
884
927
tw , th );
885
928
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: †
891
929
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 ;
893
932
if (r .status_message ) {
894
933
result .status_message = r .status_message ;
895
934
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
+
902
950
} 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
+ }
906
963
}
907
964
}
908
965
}
@@ -950,12 +1007,11 @@ qoir_encode( //
950
1007
uint64_t height_in_tiles =
951
1008
(src_pixbuf -> pixcfg .height_in_pixels + QOIR_TILE_MASK ) >> QOIR_TILE_SHIFT ;
952
1009
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.
954
1011
uint64_t dst_len_worst_case =
955
1012
(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.
959
1015
if (dst_len_worst_case > SIZE_MAX ) {
960
1016
result .status_message =
961
1017
qoir_status_message__error_unsupported_pixbuf_dimensions ;
0 commit comments