@@ -10,13 +10,16 @@ use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_
10
10
use crate :: error:: { Error , Result } ;
11
11
use crate :: header:: Header ;
12
12
use crate :: pixel:: { Pixel , SupportedChannels } ;
13
- use crate :: types:: { Channels , ColorSpace } ;
13
+ use crate :: types:: { Channels , ColorSpace , RawChannels } ;
14
14
#[ cfg( feature = "std" ) ]
15
15
use crate :: utils:: GenericWriter ;
16
16
use crate :: utils:: { unlikely, BytesMut , Writer } ;
17
17
18
18
#[ 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 >
19
+ fn encode_impl < W : Writer , const N : usize , const R : usize > (
20
+ mut buf : W , data : & [ u8 ] , width : usize , height : usize , stride : usize ,
21
+ read_px : impl Fn ( & mut Pixel < N > , & [ u8 ] ) ,
22
+ ) -> Result < usize >
20
23
where
21
24
Pixel < N > : SupportedChannels ,
22
25
[ u8 ; N ] : Pod ,
@@ -30,59 +33,57 @@ where
30
33
let mut px = Pixel :: < N > :: new ( ) . with_a ( 0xff ) ;
31
34
let mut index_allowed = false ;
32
35
33
- let n_pixels = data . len ( ) / N ;
36
+ let n_pixels = width * height ;
34
37
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
- {
38
+ let mut i = 0 ;
39
+ for row in data. chunks ( stride) . take ( height) {
40
+ let pixel_row = & row[ ..width * R ] ;
41
+ for chunk in pixel_row. chunks_exact ( R ) {
42
+ read_px ( & mut px, chunk) ;
43
+ if px == px_prev {
44
+ run += 1 ;
45
+ if run == 62 || unlikely ( i == n_pixels - 1 ) {
56
46
buf = buf. write_one ( QOI_OP_RUN | ( run - 1 ) ) ?;
47
+ run = 0 ;
57
48
}
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
49
} else {
67
- * index_px = px_rgba;
68
- buf = px. encode_into ( px_prev, buf) ?;
50
+ if run != 0 {
51
+ #[ cfg( not( feature = "reference" ) ) ]
52
+ {
53
+ // credits for the original idea: @zakarumych (had to be fixed though)
54
+ buf = buf. write_one ( if run == 1 && index_allowed {
55
+ QOI_OP_INDEX | hash_prev
56
+ } else {
57
+ QOI_OP_RUN | ( run - 1 )
58
+ } ) ?;
59
+ }
60
+ #[ cfg( feature = "reference" ) ]
61
+ {
62
+ buf = buf. write_one ( QOI_OP_RUN | ( run - 1 ) ) ?;
63
+ }
64
+ run = 0 ;
65
+ }
66
+ index_allowed = true ;
67
+ let px_rgba = px. as_rgba ( 0xff ) ;
68
+ hash_prev = px_rgba. hash_index ( ) ;
69
+ let index_px = & mut index[ hash_prev as usize ] ;
70
+ if * index_px == px_rgba {
71
+ buf = buf. write_one ( QOI_OP_INDEX | hash_prev) ?;
72
+ } else {
73
+ * index_px = px_rgba;
74
+ buf = px. encode_into ( px_prev, buf) ?;
75
+ }
76
+ px_prev = px;
69
77
}
70
- px_prev = px ;
78
+ i += 1 ;
71
79
}
72
80
}
73
81
82
+ assert_eq ! ( i, n_pixels) ;
74
83
buf = buf. write_many ( & QOI_PADDING ) ?;
75
84
Ok ( cap. saturating_sub ( buf. capacity ( ) ) )
76
85
}
77
86
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
87
/// The maximum number of bytes the encoded image will take.
87
88
///
88
89
/// Can be used to pre-allocate the buffer to encode the image into.
@@ -116,11 +117,14 @@ pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<
116
117
/// Encode QOI images into buffers or into streams.
117
118
pub struct Encoder < ' a > {
118
119
data : & ' a [ u8 ] ,
120
+ stride : usize ,
121
+ raw_channels : RawChannels ,
119
122
header : Header ,
120
123
}
121
124
122
125
impl < ' a > Encoder < ' a > {
123
126
/// Creates a new encoder from a given array of pixel data and image dimensions.
127
+ /// The data must be in RGB(A) order, without fill borders (extra stride).
124
128
///
125
129
/// The number of channels will be inferred automatically (the valid values
126
130
/// are 3 or 4). The color space will be set to sRGB by default.
@@ -136,7 +140,32 @@ impl<'a> Encoder<'a> {
136
140
return Err ( Error :: InvalidImageLength { size, width, height } ) ;
137
141
}
138
142
header. channels = Channels :: try_from ( n_channels. min ( 0xff ) as u8 ) ?;
139
- Ok ( Self { data, header } )
143
+ let raw_channels = RawChannels :: from ( header. channels ) ;
144
+ let stride = width as usize * raw_channels. bytes_per_pixel ( ) ;
145
+ Ok ( Self { data, stride, raw_channels, header } )
146
+ }
147
+
148
+ /// Creates a new encoder from a given array of pixel data, image
149
+ /// dimensions, stride, and raw format.
150
+ #[ inline]
151
+ #[ allow( clippy:: cast_possible_truncation) ]
152
+ pub fn new_raw (
153
+ data : & ' a ( impl AsRef < [ u8 ] > + ?Sized ) , width : u32 , height : u32 , stride : usize ,
154
+ raw_channels : RawChannels ,
155
+ ) -> Result < Self > {
156
+ let data = data. as_ref ( ) ;
157
+ let channels = raw_channels. into ( ) ;
158
+ let header = Header :: try_new ( width, height, channels, ColorSpace :: default ( ) ) ?;
159
+
160
+ if stride < width as usize * raw_channels. bytes_per_pixel ( ) {
161
+ return Err ( Error :: InvalidStride { stride } ) ;
162
+ }
163
+ let size = data. len ( ) ;
164
+ if stride * ( height - 1 ) as usize + width as usize * raw_channels. bytes_per_pixel ( ) < size {
165
+ return Err ( Error :: InvalidImageLength { size, width, height } ) ;
166
+ }
167
+
168
+ Ok ( Self { data, stride, raw_channels, header } )
140
169
}
141
170
142
171
/// Returns a new encoder with modified color space.
@@ -181,7 +210,7 @@ impl<'a> Encoder<'a> {
181
210
}
182
211
let ( head, tail) = buf. split_at_mut ( QOI_HEADER_SIZE ) ; // can't panic
183
212
head. copy_from_slice ( & self . header . encode ( ) ) ;
184
- let n_written = encode_impl_all ( BytesMut :: new ( tail) , self . data , self . header . channels ) ?;
213
+ let n_written = self . encode_impl_all ( BytesMut :: new ( tail) ) ?;
185
214
Ok ( QOI_HEADER_SIZE + n_written)
186
215
}
187
216
@@ -203,8 +232,62 @@ impl<'a> Encoder<'a> {
203
232
#[ inline]
204
233
pub fn encode_to_stream < W : Write > ( & self , writer : & mut W ) -> Result < usize > {
205
234
writer. write_all ( & self . header . encode ( ) ) ?;
206
- let n_written =
207
- encode_impl_all ( GenericWriter :: new ( writer) , self . data , self . header . channels ) ?;
235
+ let n_written = self . encode_impl_all ( GenericWriter :: new ( writer) ) ?;
208
236
Ok ( n_written + QOI_HEADER_SIZE )
209
237
}
238
+
239
+ #[ inline]
240
+ fn encode_impl_all < W : Writer > ( & self , out : W ) -> Result < usize > {
241
+ let width = self . header . width as usize ;
242
+ let height = self . header . height as usize ;
243
+ let stride = self . stride ;
244
+ match self . raw_channels {
245
+ RawChannels :: Rgb => {
246
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, |px, c| px. read ( c) )
247
+ }
248
+ RawChannels :: Bgr => {
249
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, |px, c| {
250
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
251
+ } )
252
+ }
253
+ RawChannels :: Rgba => {
254
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| px. read ( c) )
255
+ }
256
+ RawChannels :: Argb => {
257
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
258
+ px. update_rgba ( c[ 1 ] , c[ 2 ] , c[ 3 ] , c[ 0 ] )
259
+ } )
260
+ }
261
+ RawChannels :: Rgbx => {
262
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
263
+ px. read ( & c[ ..3 ] )
264
+ } )
265
+ }
266
+ RawChannels :: Xrgb => {
267
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
268
+ px. update_rgb ( c[ 1 ] , c[ 2 ] , c[ 3 ] )
269
+ } )
270
+ }
271
+ RawChannels :: Bgra => {
272
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
273
+ px. update_rgba ( c[ 2 ] , c[ 1 ] , c[ 0 ] , c[ 3 ] )
274
+ } )
275
+ }
276
+ RawChannels :: Abgr => {
277
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
278
+ px. update_rgba ( c[ 3 ] , c[ 2 ] , c[ 1 ] , c[ 0 ] )
279
+ } )
280
+ }
281
+ RawChannels :: Bgrx => {
282
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
283
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] )
284
+ } )
285
+ }
286
+ RawChannels :: Xbgr => {
287
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
288
+ px. update_rgb ( c[ 3 ] , c[ 2 ] , c[ 1 ] )
289
+ } )
290
+ }
291
+ }
292
+ }
210
293
}
0 commit comments