Skip to content

Commit 1e7c79f

Browse files
Encode alpha data for lossy with alpha images (#164)
1 parent b215651 commit 1e7c79f

File tree

1 file changed

+74
-4
lines changed

1 file changed

+74
-4
lines changed

src/encoder.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ pub enum ColorType {
2424
Rgba8,
2525
}
2626

27+
impl ColorType {
28+
fn has_alpha(self) -> bool {
29+
self == ColorType::La8 || self == ColorType::Rgba8
30+
}
31+
}
32+
2733
quick_error! {
2834
/// Error that can occur during encoding.
2935
#[derive(Debug)]
@@ -604,6 +610,51 @@ fn encode_frame_lossless<W: Write>(
604610
Ok(())
605611
}
606612

613+
/// Encodes the alpha part of the image data losslessly.
614+
/// Used for lossy images that include transparency.
615+
///
616+
/// # Panics
617+
///
618+
/// Panics if the image data is not of the indicated dimensions.
619+
fn encode_alpha_lossless<W: Write>(
620+
mut writer: W,
621+
data: &[u8],
622+
width: u32,
623+
height: u32,
624+
color: ColorType,
625+
) -> Result<(), EncodingError> {
626+
let bytes_per_pixel = match color {
627+
ColorType::La8 => 2,
628+
ColorType::Rgba8 => 4,
629+
_ => unreachable!(),
630+
};
631+
if width == 0 || width > 16384 || height == 0 || height > 16384 {
632+
return Err(EncodingError::InvalidDimensions);
633+
}
634+
635+
let preprocessing = 0u8;
636+
let filtering_method = 0u8;
637+
let compression_method = 0u8;
638+
639+
let initial_byte = preprocessing << 4 | filtering_method << 2 | compression_method;
640+
641+
writer.write_all(&[initial_byte])?;
642+
643+
// uncompressed raw alpha data
644+
let alpha_data: Vec<u8> = data
645+
.iter()
646+
.skip(bytes_per_pixel - 1)
647+
.step_by(bytes_per_pixel)
648+
.copied()
649+
.collect();
650+
651+
debug_assert_eq!(alpha_data.len(), (width * height) as usize);
652+
653+
writer.write_all(&alpha_data)?;
654+
655+
Ok(())
656+
}
657+
607658
const fn chunk_size(inner_bytes: usize) -> u32 {
608659
if inner_bytes % 2 == 1 {
609660
(inner_bytes + 1) as u32 + 8
@@ -681,6 +732,8 @@ impl<W: Write> WebPEncoder<W> {
681732
) -> Result<(), EncodingError> {
682733
let mut frame = Vec::new();
683734

735+
let lossy_with_alpha = self.params.use_lossy && color.has_alpha();
736+
684737
let frame_chunk = if self.params.use_lossy {
685738
encode_frame_lossy(
686739
&mut frame,
@@ -696,11 +749,14 @@ impl<W: Write> WebPEncoder<W> {
696749
b"VP8L"
697750
};
698751

699-
// If the image has no metadata, it can be encoded with the "simple" WebP container format.
700-
if self.icc_profile.is_empty()
752+
// If the image has no metadata and isn't lossy with alpha,
753+
// it can be encoded with the "simple" WebP container format.
754+
let use_simple_container = self.icc_profile.is_empty()
701755
&& self.exif_metadata.is_empty()
702756
&& self.xmp_metadata.is_empty()
703-
{
757+
&& !lossy_with_alpha;
758+
759+
if use_simple_container {
704760
self.writer.write_all(b"RIFF")?;
705761
self.writer
706762
.write_all(&(chunk_size(frame.len()) + 4).to_le_bytes())?;
@@ -718,14 +774,24 @@ impl<W: Write> WebPEncoder<W> {
718774
total_bytes += chunk_size(self.xmp_metadata.len());
719775
}
720776

777+
let alpha_chunk_data = if lossy_with_alpha {
778+
let mut alpha_chunk = Vec::new();
779+
encode_alpha_lossless(&mut alpha_chunk, data, width, height, color)?;
780+
781+
total_bytes += chunk_size(alpha_chunk.len());
782+
Some(alpha_chunk)
783+
} else {
784+
None
785+
};
786+
721787
let mut flags = 0;
722788
if !self.xmp_metadata.is_empty() {
723789
flags |= 1 << 2;
724790
}
725791
if !self.exif_metadata.is_empty() {
726792
flags |= 1 << 3;
727793
}
728-
if let ColorType::La8 | ColorType::Rgba8 = color {
794+
if color.has_alpha() {
729795
flags |= 1 << 4;
730796
}
731797
if !self.icc_profile.is_empty() {
@@ -747,6 +813,10 @@ impl<W: Write> WebPEncoder<W> {
747813
write_chunk(&mut self.writer, b"ICCP", &self.icc_profile)?;
748814
}
749815

816+
if let Some(alpha_chunk) = alpha_chunk_data {
817+
write_chunk(&mut self.writer, b"ALPH", &alpha_chunk)?;
818+
}
819+
750820
write_chunk(&mut self.writer, frame_chunk, &frame)?;
751821

752822
if !self.exif_metadata.is_empty() {

0 commit comments

Comments
 (0)