Skip to content

Commit 5c5c0f3

Browse files
committed
Add alpha mask utilities to ImageBuffer
The respective methods on `DynamicImage` are blocked on the type representation. We can't use any specific pixel type if the goal is preserving the type but `DynamicImage` has no Luma32F variant. For applying a mask we would have to take the parameter type as a non-`DynamicImage` somehow? Feels weird, we should just resolve to add at least 32-bit Luma to the crate, even if we do not add 32-bit LumaAlpha.
1 parent 80a41b2 commit 5c5c0f3

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ pub enum ParameterErrorKind {
139139
/// The cicp that was found.
140140
found: Cicp,
141141
},
142+
/// The operation is only applicable to pixels with an alpha channel.
143+
NoAlphaChannel,
142144
}
143145

144146
/// An error was encountered while decoding an image.
@@ -450,6 +452,12 @@ impl fmt::Display for ParameterError {
450452
"The color space {found:?} does not match the expected {expected:?}",
451453
)
452454
}
455+
ParameterErrorKind::NoAlphaChannel => {
456+
write!(
457+
fmt,
458+
"The operation requires an alpha channel but the pixel type does not have one",
459+
)
460+
}
453461
}?;
454462

455463
if let Some(underlying) = &self.underlying {

src/images/buffer.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,25 @@ where
888888
color_hint: None, // TODO: the pixel type might contain P::COLOR_TYPE if it satisfies PixelWithColorType
889889
}
890890
}
891+
892+
/// Extract the alpha channel as a Luma image.
893+
///
894+
/// If the pixel does not have an alpha channel, the value is filled with a fully opaque mask
895+
/// using the maximum value of the corresponding subpixel type.
896+
pub fn to_alpha_mask(&self) -> ImageBuffer<Luma<P::Subpixel>, Vec<P::Subpixel>> {
897+
let pixels = self.inner_pixels().chunks_exact(P::CHANNEL_COUNT.into());
898+
let mut mask = vec![<P::Subpixel as crate::Primitive>::DEFAULT_MAX_VALUE; pixels.len()];
899+
900+
if P::HAS_ALPHA {
901+
for (p, alpha) in pixels.zip(mask.iter_mut()) {
902+
// If the pixel has an alpha channel, use it.
903+
*alpha = *p.last().unwrap();
904+
}
905+
}
906+
907+
ImageBuffer::from_vec(self.width, self.height, mask)
908+
.expect("used the right pixel and channel count")
909+
}
891910
}
892911

893912
impl<P, Container> ImageBuffer<P, Container>
@@ -989,6 +1008,42 @@ where
9891008
pub fn put_pixel(&mut self, x: u32, y: u32, pixel: P) {
9901009
*self.get_pixel_mut(x, y) = pixel;
9911010
}
1011+
1012+
/// Fill the alpha channel of this image from a Luma mask.
1013+
///
1014+
/// Returns an [`ImageError::Parameter`] if the mask dimensions do not match the image
1015+
/// dimensions. Otherwise, if the pixel type does not have an alpha channel this is a no-op.
1016+
pub fn apply_alpha_mask<RhsContainer>(
1017+
&mut self,
1018+
mask: &ImageBuffer<Luma<P::Subpixel>, RhsContainer>,
1019+
) -> ImageResult<()>
1020+
where
1021+
RhsContainer: Deref<Target = [P::Subpixel]>,
1022+
{
1023+
if (self.width, self.height) != (mask.width(), mask.height()) {
1024+
return Err(ImageError::Parameter(ParameterError::from_kind(
1025+
ParameterErrorKind::DimensionMismatch,
1026+
)));
1027+
}
1028+
1029+
if !P::HAS_ALPHA {
1030+
return Err(ImageError::Parameter(ParameterError::from_kind(
1031+
ParameterErrorKind::NoAlphaChannel,
1032+
)));
1033+
}
1034+
1035+
let pixels = self
1036+
.inner_pixels_mut()
1037+
.chunks_exact_mut(P::CHANNEL_COUNT.into());
1038+
1039+
let mask = mask.inner_pixels();
1040+
for (p, alpha) in pixels.zip(mask.iter()) {
1041+
// If the pixel has an alpha channel, use it.
1042+
*p.last_mut().unwrap() = *alpha;
1043+
}
1044+
1045+
Ok(())
1046+
}
9921047
}
9931048

9941049
impl<P: Pixel, Container> ImageBuffer<P, Container> {
@@ -2114,6 +2169,27 @@ mod test {
21142169
let result = target.copy_from_color_space(&source, options);
21152170
assert!(matches!(result, Err(crate::ImageError::Parameter(_))));
21162171
}
2172+
2173+
#[test]
2174+
fn alpha_mask_of_gray() {
2175+
let image: GrayImage = ImageBuffer::new(4, 4);
2176+
let mask = image.to_alpha_mask();
2177+
assert_eq!(mask.as_raw(), &[255; 16]);
2178+
}
2179+
2180+
#[test]
2181+
#[rustfmt::skip]
2182+
fn alpha_mask_extraction() {
2183+
let image: ImageBuffer<LumaA<u8>, _> = ImageBuffer::from_raw(4, 4, vec![
2184+
0, 1, 0, 2, 0, 3, 0, 4,
2185+
0, 5, 0, 6, 0, 7, 0, 8,
2186+
0, 9, 0, 10, 0, 11, 0, 12,
2187+
0, 13, 0, 14, 0, 15, 0, 16,
2188+
]).unwrap();
2189+
2190+
let mask = image.to_alpha_mask();
2191+
assert_eq!(mask.as_raw(), &(1u8..17).collect::<Vec<_>>());
2192+
}
21172193
}
21182194

21192195
#[cfg(test)]

0 commit comments

Comments
 (0)