Skip to content

Commit 2c0bb0d

Browse files
committed
Add more flexible EncoderBuilder
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 53258e7 commit 2c0bb0d

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
@@ -45,57 +45,96 @@ fn test_new_encoder() {
4545
Err(Error::InvalidImageLength { size: 12, width: 3, height: 3 })
4646
));
4747

48-
assert!(matches!(qoi::Encoder::new(&arr3, 1, 1), Err(Error::InvalidChannels { channels: 12 })));
48+
assert!(matches!(
49+
qoi::Encoder::new(&arr3, 1, 1),
50+
Err(Error::InvalidImageLength { size: 12, width: 1, height: 1 })
51+
));
4952

50-
let enc = qoi::Encoder::new_raw(&arr3, 2, 2, 2 * 3, RawChannels::Bgr).unwrap();
53+
let enc = qoi::EncoderBuilder::new(&arr3, 2, 2)
54+
.stride(2 * 3)
55+
.raw_channels(RawChannels::Bgr)
56+
.build()
57+
.unwrap();
5158
assert_eq!(enc.channels(), Channels::Rgb);
5259
let qoi = enc.encode_to_vec().unwrap();
5360
let (_header, res) = decode_to_vec(qoi).unwrap();
5461
assert_eq!(res, [2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9]);
5562

56-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Rgba).unwrap();
63+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
64+
.stride(2 * 4)
65+
.raw_channels(RawChannels::Rgba)
66+
.build()
67+
.unwrap();
5768
assert_eq!(enc.channels(), Channels::Rgba);
5869
let qoi = enc.encode_to_vec().unwrap();
5970
let (_header, res) = decode_to_vec(qoi).unwrap();
6071
assert_eq!(res, arr4);
6172

62-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Bgra).unwrap();
73+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
74+
.stride(2 * 4)
75+
.raw_channels(RawChannels::Bgra)
76+
.build()
77+
.unwrap();
6378
assert_eq!(enc.channels(), Channels::Rgba);
6479
let qoi = enc.encode_to_vec().unwrap();
6580
let (_header, res) = decode_to_vec(qoi).unwrap();
6681
assert_eq!(res, [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15]);
6782

68-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Rgbx).unwrap();
83+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
84+
.stride(2 * 4)
85+
.raw_channels(RawChannels::Rgbx)
86+
.build()
87+
.unwrap();
6988
assert_eq!(enc.channels(), Channels::Rgb);
7089
let qoi = enc.encode_to_vec().unwrap();
7190
let (_header, res) = decode_to_vec(qoi).unwrap();
7291
assert_eq!(res, [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14]);
7392

74-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Xrgb).unwrap();
93+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
94+
.stride(2 * 4)
95+
.raw_channels(RawChannels::Xrgb)
96+
.build()
97+
.unwrap();
7598
assert_eq!(enc.channels(), Channels::Rgb);
7699
let qoi = enc.encode_to_vec().unwrap();
77100
let (_header, res) = decode_to_vec(qoi).unwrap();
78101
assert_eq!(res, [1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15]);
79102

80-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Bgra).unwrap();
103+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
104+
.stride(2 * 4)
105+
.raw_channels(RawChannels::Bgra)
106+
.build()
107+
.unwrap();
81108
assert_eq!(enc.channels(), Channels::Rgba);
82109
let qoi = enc.encode_to_vec().unwrap();
83110
let (_header, res) = decode_to_vec(qoi).unwrap();
84111
assert_eq!(res, [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15]);
85112

86-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Abgr).unwrap();
113+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
114+
.stride(2 * 4)
115+
.raw_channels(RawChannels::Abgr)
116+
.build()
117+
.unwrap();
87118
assert_eq!(enc.channels(), Channels::Rgba);
88119
let qoi = enc.encode_to_vec().unwrap();
89120
let (_header, res) = decode_to_vec(qoi).unwrap();
90121
assert_eq!(res, [3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12]);
91122

92-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Bgrx).unwrap();
123+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
124+
.stride(2 * 4)
125+
.raw_channels(RawChannels::Bgrx)
126+
.build()
127+
.unwrap();
93128
assert_eq!(enc.channels(), Channels::Rgb);
94129
let qoi = enc.encode_to_vec().unwrap();
95130
let (_header, res) = decode_to_vec(qoi).unwrap();
96131
assert_eq!(res, [2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12]);
97132

98-
let enc = qoi::Encoder::new_raw(&arr4, 2, 2, 2 * 4, RawChannels::Xbgr).unwrap();
133+
let enc = qoi::EncoderBuilder::new(&arr4, 2, 2)
134+
.stride(2 * 4)
135+
.raw_channels(RawChannels::Xbgr)
136+
.build()
137+
.unwrap();
99138
assert_eq!(enc.channels(), Channels::Rgb);
100139
let qoi = enc.encode_to_vec().unwrap();
101140
let (_header, res) = decode_to_vec(qoi).unwrap();

0 commit comments

Comments
 (0)