@@ -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,56 @@ 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_exact ( 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
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.
@@ -116,11 +116,14 @@ pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<
116
116
/// Encode QOI images into buffers or into streams.
117
117
pub struct Encoder < ' a > {
118
118
data : & ' a [ u8 ] ,
119
+ stride : usize ,
120
+ raw_channels : RawChannels ,
119
121
header : Header ,
120
122
}
121
123
122
124
impl < ' a > Encoder < ' a > {
123
125
/// Creates a new encoder from a given array of pixel data and image dimensions.
126
+ /// The data must be in RGB(A) order, without fill borders (extra stride).
124
127
///
125
128
/// The number of channels will be inferred automatically (the valid values
126
129
/// are 3 or 4). The color space will be set to sRGB by default.
@@ -136,7 +139,32 @@ impl<'a> Encoder<'a> {
136
139
return Err ( Error :: InvalidImageLength { size, width, height } ) ;
137
140
}
138
141
header. channels = Channels :: try_from ( n_channels. min ( 0xff ) as u8 ) ?;
139
- Ok ( Self { data, header } )
142
+ let raw_channels = RawChannels :: from ( header. channels ) ;
143
+ let stride = width as usize * raw_channels. bytes_per_pixel ( ) ;
144
+ Ok ( Self { data, stride, raw_channels, header } )
145
+ }
146
+
147
+ /// Creates a new encoder from a given array of pixel data, image
148
+ /// dimensions, stride, and raw format.
149
+ #[ inline]
150
+ #[ allow( clippy:: cast_possible_truncation) ]
151
+ pub fn new_raw (
152
+ data : & ' a ( impl AsRef < [ u8 ] > + ?Sized ) , width : u32 , height : u32 , stride : usize ,
153
+ raw_channels : RawChannels ,
154
+ ) -> Result < Self > {
155
+ let data = data. as_ref ( ) ;
156
+ let channels = raw_channels. into ( ) ;
157
+ let header = Header :: try_new ( width, height, channels, ColorSpace :: default ( ) ) ?;
158
+
159
+ if stride < width as usize * raw_channels. bytes_per_pixel ( ) {
160
+ return Err ( Error :: InvalidStride { stride } ) ;
161
+ }
162
+ let size = data. len ( ) ;
163
+ if stride * ( height - 1 ) as usize + width as usize * raw_channels. bytes_per_pixel ( ) < size {
164
+ return Err ( Error :: InvalidImageLength { size, width, height } ) ;
165
+ }
166
+
167
+ Ok ( Self { data, stride, raw_channels, header } )
140
168
}
141
169
142
170
/// Returns a new encoder with modified color space.
@@ -181,7 +209,7 @@ impl<'a> Encoder<'a> {
181
209
}
182
210
let ( head, tail) = buf. split_at_mut ( QOI_HEADER_SIZE ) ; // can't panic
183
211
head. copy_from_slice ( & self . header . encode ( ) ) ;
184
- let n_written = encode_impl_all ( BytesMut :: new ( tail) , self . data , self . header . channels ) ?;
212
+ let n_written = self . encode_impl_all ( BytesMut :: new ( tail) ) ?;
185
213
Ok ( QOI_HEADER_SIZE + n_written)
186
214
}
187
215
@@ -203,8 +231,62 @@ impl<'a> Encoder<'a> {
203
231
#[ inline]
204
232
pub fn encode_to_stream < W : Write > ( & self , writer : & mut W ) -> Result < usize > {
205
233
writer. write_all ( & self . header . encode ( ) ) ?;
206
- let n_written =
207
- encode_impl_all ( GenericWriter :: new ( writer) , self . data , self . header . channels ) ?;
234
+ let n_written = self . encode_impl_all ( GenericWriter :: new ( writer) ) ?;
208
235
Ok ( n_written + QOI_HEADER_SIZE )
209
236
}
237
+
238
+ #[ inline]
239
+ fn encode_impl_all < W : Writer > ( & self , out : W ) -> Result < usize > {
240
+ let width = self . header . width as usize ;
241
+ let height = self . header . height as usize ;
242
+ let stride = self . stride ;
243
+ match self . raw_channels {
244
+ RawChannels :: Rgb => {
245
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, |px, c| px. read ( c) )
246
+ }
247
+ RawChannels :: Bgr => {
248
+ encode_impl :: < _ , 3 , 3 > ( out, self . data , width, height, stride, |px, c| {
249
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] ) ;
250
+ } )
251
+ }
252
+ RawChannels :: Rgba => {
253
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| px. read ( c) )
254
+ }
255
+ RawChannels :: Argb => {
256
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
257
+ px. update_rgba ( c[ 1 ] , c[ 2 ] , c[ 3 ] , c[ 0 ] )
258
+ } )
259
+ }
260
+ RawChannels :: Rgbx => {
261
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
262
+ px. read ( & c[ ..3 ] )
263
+ } )
264
+ }
265
+ RawChannels :: Xrgb => {
266
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
267
+ px. update_rgb ( c[ 1 ] , c[ 2 ] , c[ 3 ] )
268
+ } )
269
+ }
270
+ RawChannels :: Bgra => {
271
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
272
+ px. update_rgba ( c[ 2 ] , c[ 1 ] , c[ 0 ] , c[ 3 ] )
273
+ } )
274
+ }
275
+ RawChannels :: Abgr => {
276
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
277
+ px. update_rgba ( c[ 3 ] , c[ 2 ] , c[ 1 ] , c[ 0 ] )
278
+ } )
279
+ }
280
+ RawChannels :: Bgrx => {
281
+ encode_impl :: < _ , 3 , 4 > ( out, self . data , width, height, stride, |px, c| {
282
+ px. update_rgb ( c[ 2 ] , c[ 1 ] , c[ 0 ] )
283
+ } )
284
+ }
285
+ RawChannels :: Xbgr => {
286
+ encode_impl :: < _ , 4 , 4 > ( out, self . data , width, height, stride, |px, c| {
287
+ px. update_rgb ( c[ 3 ] , c[ 2 ] , c[ 1 ] )
288
+ } )
289
+ }
290
+ }
291
+ }
210
292
}
0 commit comments