@@ -10,13 +10,16 @@ use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_
1010use crate :: error:: { Error , Result } ;
1111use crate :: header:: Header ;
1212use crate :: pixel:: { Pixel , SupportedChannels } ;
13- use crate :: types:: { Channels , ColorSpace } ;
13+ use crate :: types:: { Channels , ColorSpace , RawChannels } ;
1414#[ cfg( feature = "std" ) ]
1515use crate :: utils:: GenericWriter ;
1616use crate :: utils:: { unlikely, BytesMut , Writer } ;
1717
1818#[ 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 >
2023where
2124 Pixel < N > : SupportedChannels ,
2225 [ u8 ; N ] : Pod ,
@@ -30,59 +33,57 @@ where
3033 let mut px = Pixel :: < N > :: new ( ) . with_a ( 0xff ) ;
3134 let mut index_allowed = false ;
3235
33- let n_pixels = data . len ( ) / N ;
36+ let n_pixels = width * height ;
3437
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 ) {
5646 buf = buf. write_one ( QOI_OP_RUN | ( run - 1 ) ) ?;
47+ run = 0 ;
5748 }
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) ?;
6649 } 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;
6977 }
70- px_prev = px ;
78+ i += 1 ;
7179 }
7280 }
7381
82+ assert_eq ! ( i, n_pixels) ;
7483 buf = buf. write_many ( & QOI_PADDING ) ?;
7584 Ok ( cap. saturating_sub ( buf. capacity ( ) ) )
7685}
7786
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-
8687/// The maximum number of bytes the encoded image will take.
8788///
8889/// 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<
116117/// Encode QOI images into buffers or into streams.
117118pub struct Encoder < ' a > {
118119 data : & ' a [ u8 ] ,
120+ stride : usize ,
121+ raw_channels : RawChannels ,
119122 header : Header ,
120123}
121124
122125impl < ' a > Encoder < ' a > {
123126 /// 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).
124128 ///
125129 /// The number of channels will be inferred automatically (the valid values
126130 /// are 3 or 4). The color space will be set to sRGB by default.
@@ -136,7 +140,32 @@ impl<'a> Encoder<'a> {
136140 return Err ( Error :: InvalidImageLength { size, width, height } ) ;
137141 }
138142 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 } )
140169 }
141170
142171 /// Returns a new encoder with modified color space.
@@ -181,7 +210,7 @@ impl<'a> Encoder<'a> {
181210 }
182211 let ( head, tail) = buf. split_at_mut ( QOI_HEADER_SIZE ) ; // can't panic
183212 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) ) ?;
185214 Ok ( QOI_HEADER_SIZE + n_written)
186215 }
187216
@@ -203,8 +232,62 @@ impl<'a> Encoder<'a> {
203232 #[ inline]
204233 pub fn encode_to_stream < W : Write > ( & self , writer : & mut W ) -> Result < usize > {
205234 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) ) ?;
208236 Ok ( n_written + QOI_HEADER_SIZE )
209237 }
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+ }
210293}
0 commit comments