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 , RawChannels } ;
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
+ 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,102 @@ 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
+ pub struct EncoderBuilder < ' a > {
117
+ data : & ' a [ u8 ] ,
118
+ width : u32 ,
119
+ height : u32 ,
120
+ stride : Option < usize > ,
121
+ raw_channels : Option < RawChannels > ,
122
+ colorspace : Option < ColorSpace > ,
123
+ }
124
+
125
+ impl < ' a > EncoderBuilder < ' a > {
126
+ /// Creates a new encoder builder from a given array of pixel data and image dimensions.
127
+ pub fn new ( data : & ' a ( impl AsRef < [ u8 ] > + ?Sized ) , width : u32 , height : u32 ) -> Self {
128
+ Self {
129
+ data : data. as_ref ( ) ,
130
+ width,
131
+ height,
132
+ stride : None ,
133
+ raw_channels : None ,
134
+ colorspace : None ,
135
+ }
136
+ }
137
+
138
+ /// Set the stride of the pixel data.
139
+ pub const fn stride ( mut self , stride : usize ) -> Self {
140
+ self . stride = Some ( stride) ;
141
+ self
142
+ }
143
+
144
+ /// Set the input format of the pixel data.
145
+ pub const fn raw_channels ( mut self , raw_channels : RawChannels ) -> Self {
146
+ self . raw_channels = Some ( raw_channels) ;
147
+ self
148
+ }
149
+
150
+ /// Set the colorspace.
151
+ pub const fn colorspace ( mut self , colorspace : ColorSpace ) -> Self {
152
+ self . colorspace = Some ( colorspace) ;
153
+ self
154
+ }
155
+
156
+ /// Build the encoder.
157
+ pub fn build ( self ) -> Result < Encoder < ' a > > {
158
+ let EncoderBuilder { data, width, height, stride, raw_channels, colorspace } = self ;
159
+
160
+ let size = data. len ( ) ;
161
+ let no_stride = stride. is_none ( ) ;
162
+ let stride = stride. unwrap_or (
163
+ size. checked_div ( height as usize )
164
+ . ok_or ( Error :: InvalidImageDimensions { width, height } ) ?,
165
+ ) ;
166
+ let raw_channels = raw_channels. unwrap_or ( if stride == width as usize * 3 {
167
+ RawChannels :: Rgb
168
+ } else {
169
+ RawChannels :: Rgba
170
+ } ) ;
171
+
172
+ if stride < width as usize * raw_channels. bytes_per_pixel ( ) {
173
+ return Err ( Error :: InvalidImageLength { size, width, height } ) ;
174
+ }
175
+ if stride * ( height - 1 ) as usize + width as usize * raw_channels. bytes_per_pixel ( ) < size {
176
+ return Err ( Error :: InvalidImageLength { size, width, height } ) ;
177
+ }
178
+ if no_stride && size != width as usize * height as usize * raw_channels. bytes_per_pixel ( ) {
179
+ return Err ( Error :: InvalidImageLength { size, width, height } ) ;
180
+ }
181
+
182
+ let channels = raw_channels. into ( ) ;
183
+ let colorspace = colorspace. unwrap_or_default ( ) ;
184
+
185
+ Ok ( Encoder {
186
+ data,
187
+ stride,
188
+ raw_channels,
189
+ header : Header :: try_new ( self . width , self . height , channels, colorspace) ?,
190
+ } )
191
+ }
192
+ }
193
+
116
194
/// Encode QOI images into buffers or into streams.
117
195
pub struct Encoder < ' a > {
118
196
data : & ' a [ u8 ] ,
197
+ stride : usize ,
198
+ raw_channels : RawChannels ,
119
199
header : Header ,
120
200
}
121
201
122
202
impl < ' a > Encoder < ' a > {
123
203
/// Creates a new encoder from a given array of pixel data and image dimensions.
204
+ /// The data must be in RGB(A) order, without fill borders (extra stride).
124
205
///
125
206
/// The number of channels will be inferred automatically (the valid values
126
207
/// are 3 or 4). The color space will be set to sRGB by default.
127
208
#[ inline]
128
209
#[ allow( clippy:: cast_possible_truncation) ]
129
210
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 } )
211
+ EncoderBuilder :: new ( data, width, height) . build ( )
140
212
}
141
213
142
214
/// Returns a new encoder with modified color space.
@@ -181,7 +253,7 @@ impl<'a> Encoder<'a> {
181
253
}
182
254
let ( head, tail) = buf. split_at_mut ( QOI_HEADER_SIZE ) ; // can't panic
183
255
head. copy_from_slice ( & self . header . encode ( ) ) ;
184
- let n_written = encode_impl_all ( BytesMut :: new ( tail) , self . data , self . header . channels ) ?;
256
+ let n_written = self . encode_impl_all ( BytesMut :: new ( tail) ) ?;
185
257
Ok ( QOI_HEADER_SIZE + n_written)
186
258
}
187
259
@@ -203,8 +275,62 @@ impl<'a> Encoder<'a> {
203
275
#[ inline]
204
276
pub fn encode_to_stream < W : Write > ( & self , writer : & mut W ) -> Result < usize > {
205
277
writer. write_all ( & self . header . encode ( ) ) ?;
206
- let n_written =
207
- encode_impl_all ( GenericWriter :: new ( writer) , self . data , self . header . channels ) ?;
278
+ let n_written = self . encode_impl_all ( GenericWriter :: new ( writer) ) ?;
208
279
Ok ( n_written + QOI_HEADER_SIZE )
209
280
}
281
+
282
+ #[ inline]
283
+ fn encode_impl_all < W : Writer > ( & self , out : W ) -> Result < usize > {
284
+ let width = self . header . width as usize ;
285
+ let height = self . header . height as usize ;
286
+ let stride = self . stride ;
287
+ match self . raw_channels {
288
+ RawChannels :: Rgb => {
289
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, Pixel :: read)
290
+ }
291
+ RawChannels :: Bgr => {
292
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, |px, c| {
293
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
294
+ } )
295
+ }
296
+ RawChannels :: Rgba => {
297
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, Pixel :: read)
298
+ }
299
+ RawChannels :: Argb => {
300
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
301
+ px. update_rgba ( c[ 1 ] , c[ 2 ] , c[ 3 ] , c[ 0 ] ) ;
302
+ } )
303
+ }
304
+ RawChannels :: Rgbx => {
305
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
306
+ px. read ( & c[ ..3 ] ) ;
307
+ } )
308
+ }
309
+ RawChannels :: Xrgb => {
310
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
311
+ px. update_rgb ( c[ 1 ] , c[ 2 ] , c[ 3 ] ) ;
312
+ } )
313
+ }
314
+ RawChannels :: Bgra => {
315
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
316
+ px. update_rgba ( c[ 2 ] , c[ 1 ] , c[ 0 ] , c[ 3 ] ) ;
317
+ } )
318
+ }
319
+ RawChannels :: Abgr => {
320
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
321
+ px. update_rgba ( c[ 3 ] , c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
322
+ } )
323
+ }
324
+ RawChannels :: Bgrx => {
325
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
326
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
327
+ } )
328
+ }
329
+ RawChannels :: Xbgr => {
330
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
331
+ px. update_rgb ( c[ 3 ] , c[ 2 ] , c[ 1 ] ) ;
332
+ } )
333
+ }
334
+ }
335
+ }
210
336
}
0 commit comments