Skip to content

Commit 9b0e41c

Browse files
committed
Add more flexible EncoderBuilder
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 5ff97e0 commit 9b0e41c

File tree

3 files changed

+129
-47
lines changed

3 files changed

+129
-47
lines changed

src/encode.rs

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#[cfg(any(feature = "std", feature = "alloc"))]
22
use alloc::{vec, vec::Vec};
3-
use core::convert::TryFrom;
43
#[cfg(feature = "std")]
54
use std::io::Write;
65

@@ -114,6 +113,84 @@ pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<
114113
Encoder::new(&data, width, height)?.encode_to_vec()
115114
}
116115

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+
117194
/// Encode QOI images into buffers or into streams.
118195
pub struct Encoder<'a> {
119196
data: &'a [u8],
@@ -131,41 +208,7 @@ impl<'a> Encoder<'a> {
131208
#[inline]
132209
#[allow(clippy::cast_possible_truncation)]
133210
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
134-
let data = data.as_ref();
135-
let mut header =
136-
Header::try_new(width, height, Channels::default(), ColorSpace::default())?;
137-
let size = data.len();
138-
let n_channels = size / header.n_pixels();
139-
if header.n_pixels() * n_channels != size {
140-
return Err(Error::InvalidImageLength { size, width, height });
141-
}
142-
header.channels = Channels::try_from(n_channels.min(0xff) as u8)?;
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 })
211+
EncoderBuilder::new(data, width, height).build()
169212
}
170213

171214
/// Returns a new encoder with modified color space.

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ pub use crate::decode::{decode_header, decode_to_buf, Decoder};
8888

8989
#[cfg(any(feature = "alloc", feature = "std"))]
9090
pub use crate::encode::encode_to_vec;
91-
pub use crate::encode::{encode_max_len, encode_to_buf, Encoder};
91+
pub use crate::encode::{encode_max_len, encode_to_buf, Encoder, EncoderBuilder};
9292

9393
pub use crate::error::{Error, Result};
9494
pub use crate::header::Header;

tests/test_misc.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,57 +97,96 @@ fn test_new_encoder() {
9797
Err(Error::InvalidImageLength { size: 12, width: 3, height: 3 })
9898
));
9999

100-
assert!(matches!(qoi::Encoder::new(&arr3, 1, 1), Err(Error::InvalidChannels { channels: 12 })));
100+
assert!(matches!(
101+
qoi::Encoder::new(&arr3, 1, 1),
102+
Err(Error::InvalidImageLength { size: 12, width: 1, height: 1 })
103+
));
101104

102-
let enc = qoi::Encoder::new_raw(&arr3, 2, 2, 2 * 3, RawChannels::Bgr).unwrap();
105+
let enc = qoi::EncoderBuilder::new(&arr3, 2, 2)
106+
.stride(2 * 3)
107+
.raw_channels(RawChannels::Bgr)
108+
.build()
109+
.unwrap();
103110
assert_eq!(enc.channels(), Channels::Rgb);
104111
let qoi = enc.encode_to_vec().unwrap();
105112
let (_header, res) = decode_to_vec(qoi).unwrap();
106113
assert_eq!(res, [2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9]);
107114

108-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Rgba).unwrap();
115+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
116+
.stride(2 * 4)
117+
.raw_channels(RawChannels::Rgba)
118+
.build()
119+
.unwrap();
109120
assert_eq!(enc.channels(), Channels::Rgba);
110121
let qoi = enc.encode_to_vec().unwrap();
111122
let (_header, res) = decode_to_vec(qoi).unwrap();
112123
assert_eq!(res, arr4);
113124

114-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Bgra).unwrap();
125+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
126+
.stride(2 * 4)
127+
.raw_channels(RawChannels::Bgra)
128+
.build()
129+
.unwrap();
115130
assert_eq!(enc.channels(), Channels::Rgba);
116131
let qoi = enc.encode_to_vec().unwrap();
117132
let (_header, res) = decode_to_vec(qoi).unwrap();
118133
assert_eq!(res, [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15]);
119134

120-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Rgbx).unwrap();
135+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
136+
.stride(2 * 4)
137+
.raw_channels(RawChannels::Rgbx)
138+
.build()
139+
.unwrap();
121140
assert_eq!(enc.channels(), Channels::Rgb);
122141
let qoi = enc.encode_to_vec().unwrap();
123142
let (_header, res) = decode_to_vec(qoi).unwrap();
124143
assert_eq!(res, [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14]);
125144

126-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Xrgb).unwrap();
145+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
146+
.stride(2 * 4)
147+
.raw_channels(RawChannels::Xrgb)
148+
.build()
149+
.unwrap();
127150
assert_eq!(enc.channels(), Channels::Rgb);
128151
let qoi = enc.encode_to_vec().unwrap();
129152
let (_header, res) = decode_to_vec(qoi).unwrap();
130153
assert_eq!(res, [1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15]);
131154

132-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Bgra).unwrap();
155+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
156+
.stride(2 * 4)
157+
.raw_channels(RawChannels::Bgra)
158+
.build()
159+
.unwrap();
133160
assert_eq!(enc.channels(), Channels::Rgba);
134161
let qoi = enc.encode_to_vec().unwrap();
135162
let (_header, res) = decode_to_vec(qoi).unwrap();
136163
assert_eq!(res, [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15]);
137164

138-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Abgr).unwrap();
165+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
166+
.stride(2 * 4)
167+
.raw_channels(RawChannels::Abgr)
168+
.build()
169+
.unwrap();
139170
assert_eq!(enc.channels(), Channels::Rgba);
140171
let qoi = enc.encode_to_vec().unwrap();
141172
let (_header, res) = decode_to_vec(qoi).unwrap();
142173
assert_eq!(res, [3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12]);
143174

144-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Bgrx).unwrap();
175+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
176+
.stride(2 * 4)
177+
.raw_channels(RawChannels::Bgrx)
178+
.build()
179+
.unwrap();
145180
assert_eq!(enc.channels(), Channels::Rgb);
146181
let qoi = enc.encode_to_vec().unwrap();
147182
let (_header, res) = decode_to_vec(qoi).unwrap();
148183
assert_eq!(res, [2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12]);
149184

150-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Xbgr).unwrap();
185+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
186+
.stride(2 * 4)
187+
.raw_channels(RawChannels::Xbgr)
188+
.build()
189+
.unwrap();
151190
assert_eq!(enc.channels(), Channels::Rgb);
152191
let qoi = enc.encode_to_vec().unwrap();
153192
let (_header, res) = decode_to_vec(qoi).unwrap();

0 commit comments

Comments
 (0)