1
1
#[ cfg( any( feature = "std" , feature = "alloc" ) ) ]
2
2
use alloc:: { vec, vec:: Vec } ;
3
- use core:: convert:: TryFrom ;
4
3
#[ cfg( feature = "std" ) ]
5
4
use std:: io:: Write ;
6
5
@@ -10,13 +9,16 @@ use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_
10
9
use crate :: error:: { Error , Result } ;
11
10
use crate :: header:: Header ;
12
11
use crate :: pixel:: { Pixel , SupportedChannels } ;
13
- use crate :: types:: { Channels , ColorSpace } ;
12
+ use crate :: types:: { Channels , ColorSpace , SourceChannels } ;
14
13
#[ cfg( feature = "std" ) ]
15
14
use crate :: utils:: GenericWriter ;
16
15
use crate :: utils:: { unlikely, BytesMut , Writer } ;
17
16
18
17
#[ allow( clippy:: cast_possible_truncation, unused_assignments, unused_variables) ]
19
- fn encode_impl < W : Writer , const N : usize > ( mut buf : W , data : & [ u8 ] ) -> Result < usize >
18
+ fn encode_impl < W : Writer , const N : usize , const R : usize > (
19
+ mut buf : W , data : & [ u8 ] , width : usize , height : usize , stride : usize ,
20
+ read_px : impl Fn ( & mut Pixel < N > , & [ u8 ] ) ,
21
+ ) -> Result < usize >
20
22
where
21
23
Pixel < N > : SupportedChannels ,
22
24
[ u8 ; N ] : Pod ,
@@ -30,59 +32,57 @@ where
30
32
let mut px = Pixel :: < N > :: new ( ) . with_a ( 0xff ) ;
31
33
let mut index_allowed = false ;
32
34
33
- let n_pixels = data . len ( ) / N ;
35
+ let n_pixels = width * height ;
34
36
35
- for ( i, chunk) in data. chunks_exact ( N ) . enumerate ( ) {
36
- px. read ( chunk) ;
37
- if px == px_prev {
38
- run += 1 ;
39
- if run == 62 || unlikely ( i == n_pixels - 1 ) {
40
- buf = buf. write_one ( QOI_OP_RUN | ( run - 1 ) ) ?;
41
- run = 0 ;
42
- }
43
- } else {
44
- if run != 0 {
45
- #[ cfg( not( feature = "reference" ) ) ]
46
- {
47
- // credits for the original idea: @zakarumych (had to be fixed though)
48
- buf = buf. write_one ( if run == 1 && index_allowed {
49
- QOI_OP_INDEX | hash_prev
50
- } else {
51
- QOI_OP_RUN | ( run - 1 )
52
- } ) ?;
53
- }
54
- #[ cfg( feature = "reference" ) ]
55
- {
37
+ let mut i = 0 ;
38
+ for row in data. chunks ( stride) . take ( height) {
39
+ let pixel_row = & row[ ..width * R ] ;
40
+ for chunk in pixel_row. chunks_exact ( R ) {
41
+ read_px ( & mut px, chunk) ;
42
+ if px == px_prev {
43
+ run += 1 ;
44
+ if run == 62 || unlikely ( i == n_pixels - 1 ) {
56
45
buf = buf. write_one ( QOI_OP_RUN | ( run - 1 ) ) ?;
46
+ run = 0 ;
57
47
}
58
- run = 0 ;
59
- }
60
- index_allowed = true ;
61
- let px_rgba = px. as_rgba ( 0xff ) ;
62
- hash_prev = px_rgba. hash_index ( ) ;
63
- let index_px = & mut index[ hash_prev as usize ] ;
64
- if * index_px == px_rgba {
65
- buf = buf. write_one ( QOI_OP_INDEX | hash_prev) ?;
66
48
} else {
67
- * index_px = px_rgba;
68
- buf = px. encode_into ( px_prev, buf) ?;
49
+ if run != 0 {
50
+ #[ cfg( not( feature = "reference" ) ) ]
51
+ {
52
+ // credits for the original idea: @zakarumych (had to be fixed though)
53
+ buf = buf. write_one ( if run == 1 && index_allowed {
54
+ QOI_OP_INDEX | hash_prev
55
+ } else {
56
+ QOI_OP_RUN | ( run - 1 )
57
+ } ) ?;
58
+ }
59
+ #[ cfg( feature = "reference" ) ]
60
+ {
61
+ buf = buf. write_one ( QOI_OP_RUN | ( run - 1 ) ) ?;
62
+ }
63
+ run = 0 ;
64
+ }
65
+ index_allowed = true ;
66
+ let px_rgba = px. as_rgba ( 0xff ) ;
67
+ hash_prev = px_rgba. hash_index ( ) ;
68
+ let index_px = & mut index[ hash_prev as usize ] ;
69
+ if * index_px == px_rgba {
70
+ buf = buf. write_one ( QOI_OP_INDEX | hash_prev) ?;
71
+ } else {
72
+ * index_px = px_rgba;
73
+ buf = px. encode_into ( px_prev, buf) ?;
74
+ }
75
+ px_prev = px;
69
76
}
70
- px_prev = px ;
77
+ i += 1 ;
71
78
}
72
79
}
73
80
81
+ debug_assert_eq ! ( i, n_pixels) ;
74
82
buf = buf. write_many ( & QOI_PADDING ) ?;
75
83
Ok ( cap. saturating_sub ( buf. capacity ( ) ) )
76
84
}
77
85
78
- #[ inline]
79
- fn encode_impl_all < W : Writer > ( out : W , data : & [ u8 ] , channels : Channels ) -> Result < usize > {
80
- match channels {
81
- Channels :: Rgb => encode_impl :: < _ , 3 > ( out, data) ,
82
- Channels :: Rgba => encode_impl :: < _ , 4 > ( out, data) ,
83
- }
84
- }
85
-
86
86
/// The maximum number of bytes the encoded image will take.
87
87
///
88
88
/// Can be used to pre-allocate the buffer to encode the image into.
@@ -113,30 +113,111 @@ pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<
113
113
Encoder :: new ( & data, width, height) ?. encode_to_vec ( )
114
114
}
115
115
116
+ /// A builder for creating an encoder.
117
+ pub struct EncoderBuilder < ' a > {
118
+ data : & ' a [ u8 ] ,
119
+ width : u32 ,
120
+ height : u32 ,
121
+ stride : Option < usize > ,
122
+ source_channels : Option < SourceChannels > ,
123
+ colorspace : Option < ColorSpace > ,
124
+ }
125
+
126
+ impl < ' a > EncoderBuilder < ' a > {
127
+ /// Creates a new encoder builder from a given array of pixel data and image dimensions.
128
+ pub fn new ( data : & ' a ( impl AsRef < [ u8 ] > + ?Sized ) , width : u32 , height : u32 ) -> Self {
129
+ Self {
130
+ data : data. as_ref ( ) ,
131
+ width,
132
+ height,
133
+ stride : None ,
134
+ source_channels : None ,
135
+ colorspace : None ,
136
+ }
137
+ }
138
+
139
+ /// Set the stride of the pixel data.
140
+ pub const fn stride ( mut self , stride : usize ) -> Self {
141
+ self . stride = Some ( stride) ;
142
+ self
143
+ }
144
+
145
+ /// Set the input format of the pixel data.
146
+ pub const fn source_channels ( mut self , source_channels : SourceChannels ) -> Self {
147
+ self . source_channels = Some ( source_channels) ;
148
+ self
149
+ }
150
+
151
+ /// Set the colorspace.
152
+ pub const fn colorspace ( mut self , colorspace : ColorSpace ) -> Self {
153
+ self . colorspace = Some ( colorspace) ;
154
+ self
155
+ }
156
+
157
+ /// Build the encoder.
158
+ pub fn build ( self ) -> Result < Encoder < ' a > > {
159
+ let EncoderBuilder { data, width, height, stride, source_channels, colorspace } = self ;
160
+
161
+ let size = data. len ( ) ;
162
+ let no_stride = stride. is_none ( ) ;
163
+ let stride = stride. unwrap_or (
164
+ size. checked_div ( height as usize ) . ok_or ( Error :: InvalidImageLength {
165
+ size,
166
+ width,
167
+ height,
168
+ } ) ?,
169
+ ) ;
170
+ let source_channels = source_channels. unwrap_or ( if stride == width as usize * 3 {
171
+ SourceChannels :: Rgb
172
+ } else {
173
+ SourceChannels :: Rgba
174
+ } ) ;
175
+
176
+ if no_stride {
177
+ if size != width as usize * height as usize * source_channels. bytes_per_pixel ( ) {
178
+ return Err ( Error :: InvalidImageLength { size, width, height } ) ;
179
+ }
180
+ } else {
181
+ if stride < width as usize * source_channels. bytes_per_pixel ( ) {
182
+ return Err ( Error :: InvalidImageStride { size, width, height, stride } ) ;
183
+ }
184
+ if stride * ( height - 1 ) as usize + width as usize * source_channels. bytes_per_pixel ( )
185
+ < size
186
+ {
187
+ return Err ( Error :: InvalidImageStride { size, width, height, stride } ) ;
188
+ }
189
+ }
190
+
191
+ let channels = source_channels. image_channels ( ) ;
192
+ let colorspace = colorspace. unwrap_or_default ( ) ;
193
+
194
+ Ok ( Encoder {
195
+ data,
196
+ stride,
197
+ source_channels,
198
+ header : Header :: try_new ( self . width , self . height , channels, colorspace) ?,
199
+ } )
200
+ }
201
+ }
202
+
116
203
/// Encode QOI images into buffers or into streams.
117
204
pub struct Encoder < ' a > {
118
205
data : & ' a [ u8 ] ,
206
+ stride : usize ,
207
+ source_channels : SourceChannels ,
119
208
header : Header ,
120
209
}
121
210
122
211
impl < ' a > Encoder < ' a > {
123
212
/// Creates a new encoder from a given array of pixel data and image dimensions.
213
+ /// The data must be in RGB(A) order, without fill borders (no stride).
124
214
///
125
215
/// The number of channels will be inferred automatically (the valid values
126
216
/// are 3 or 4). The color space will be set to sRGB by default.
127
217
#[ inline]
128
218
#[ allow( clippy:: cast_possible_truncation) ]
129
219
pub fn new ( data : & ' a ( impl AsRef < [ u8 ] > + ?Sized ) , width : u32 , height : u32 ) -> Result < Self > {
130
- let data = data. as_ref ( ) ;
131
- let mut header =
132
- Header :: try_new ( width, height, Channels :: default ( ) , ColorSpace :: default ( ) ) ?;
133
- let size = data. len ( ) ;
134
- let n_channels = size / header. n_pixels ( ) ;
135
- if header. n_pixels ( ) * n_channels != size {
136
- return Err ( Error :: InvalidImageLength { size, width, height } ) ;
137
- }
138
- header. channels = Channels :: try_from ( n_channels. min ( 0xff ) as u8 ) ?;
139
- Ok ( Self { data, header } )
220
+ EncoderBuilder :: new ( data, width, height) . build ( )
140
221
}
141
222
142
223
/// Returns a new encoder with modified color space.
@@ -181,7 +262,7 @@ impl<'a> Encoder<'a> {
181
262
}
182
263
let ( head, tail) = buf. split_at_mut ( QOI_HEADER_SIZE ) ; // can't panic
183
264
head. copy_from_slice ( & self . header . encode ( ) ) ;
184
- let n_written = encode_impl_all ( BytesMut :: new ( tail) , self . data , self . header . channels ) ?;
265
+ let n_written = self . encode_impl_all ( BytesMut :: new ( tail) ) ?;
185
266
Ok ( QOI_HEADER_SIZE + n_written)
186
267
}
187
268
@@ -203,8 +284,62 @@ impl<'a> Encoder<'a> {
203
284
#[ inline]
204
285
pub fn encode_to_stream < W : Write > ( & self , writer : & mut W ) -> Result < usize > {
205
286
writer. write_all ( & self . header . encode ( ) ) ?;
206
- let n_written =
207
- encode_impl_all ( GenericWriter :: new ( writer) , self . data , self . header . channels ) ?;
287
+ let n_written = self . encode_impl_all ( GenericWriter :: new ( writer) ) ?;
208
288
Ok ( n_written + QOI_HEADER_SIZE )
209
289
}
290
+
291
+ #[ inline]
292
+ fn encode_impl_all < W : Writer > ( & self , out : W ) -> Result < usize > {
293
+ let width = self . header . width as usize ;
294
+ let height = self . header . height as usize ;
295
+ let stride = self . stride ;
296
+ match self . source_channels {
297
+ SourceChannels :: Rgb => {
298
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, Pixel :: read)
299
+ }
300
+ SourceChannels :: Bgr => {
301
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, |px, c| {
302
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
303
+ } )
304
+ }
305
+ SourceChannels :: Rgba => {
306
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, Pixel :: read)
307
+ }
308
+ SourceChannels :: Argb => {
309
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
310
+ px. update_rgba ( c[ 1 ] , c[ 2 ] , c[ 3 ] , c[ 0 ] ) ;
311
+ } )
312
+ }
313
+ SourceChannels :: Rgbx => {
314
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
315
+ px. read ( & c[ ..3 ] ) ;
316
+ } )
317
+ }
318
+ SourceChannels :: Xrgb => {
319
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
320
+ px. update_rgb ( c[ 1 ] , c[ 2 ] , c[ 3 ] ) ;
321
+ } )
322
+ }
323
+ SourceChannels :: Bgra => {
324
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
325
+ px. update_rgba ( c[ 2 ] , c[ 1 ] , c[ 0 ] , c[ 3 ] ) ;
326
+ } )
327
+ }
328
+ SourceChannels :: Abgr => {
329
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
330
+ px. update_rgba ( c[ 3 ] , c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
331
+ } )
332
+ }
333
+ SourceChannels :: Bgrx => {
334
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
335
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
336
+ } )
337
+ }
338
+ SourceChannels :: Xbgr => {
339
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
340
+ px. update_rgb ( c[ 3 ] , c[ 2 ] , c[ 1 ] ) ;
341
+ } )
342
+ }
343
+ }
344
+ }
210
345
}
0 commit comments