1use pdfrum_object::{Array, ByteSpan, Dict, Object, Stream};
5
6use super::EmbeddedImage;
7use super::jpeg::{device_space, image_dict};
8use crate::doc::EditDoc;
9use crate::error::Error;
10use crate::names;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum PixelFormat {
19 Gray8,
21 Rgb8,
23 Cmyk8,
25 Rgba8,
28 Mask1,
33}
34
35impl PixelFormat {
36 fn row_bytes(self, width: u32) -> Option<usize> {
38 let width = usize::try_from(width).ok()?;
39 match self {
40 Self::Mask1 => width.checked_add(7).map(|w| w / 8),
41 Self::Gray8 => Some(width),
42 Self::Rgb8 => width.checked_mul(3),
43 Self::Cmyk8 | Self::Rgba8 => width.checked_mul(4),
44 }
45 }
46
47 fn stored_components(self) -> u8 {
50 match self {
51 Self::Mask1 | Self::Gray8 => 1,
52 Self::Rgb8 | Self::Rgba8 => 3,
53 Self::Cmyk8 => 4,
54 }
55 }
56}
57
58pub(super) fn embed(
59 doc: &mut EditDoc<'_>,
60 pixels: &[u8],
61 width: u32,
62 height: u32,
63 format: PixelFormat,
64) -> Result<EmbeddedImage, Error> {
65 if width == 0 || height == 0 {
69 return Err(Error::EmptyImage);
70 }
71 let row = format.row_bytes(width).ok_or(Error::EmptyImage)?;
72 let expected = usize::try_from(height)
73 .ok()
74 .and_then(|h| row.checked_mul(h))
75 .ok_or(Error::EmptyImage)?;
76 if pixels.len() != expected {
77 return Err(Error::ImageDataLength {
78 expected,
79 found: pixels.len(),
80 });
81 }
82
83 let mut dict = image_dict(width, height);
84 let data = match format {
85 PixelFormat::Mask1 => {
92 dict.push(names::IMAGE_MASK.clone(), Object::Bool(true));
93 dict.push(
94 names::DECODE.clone(),
95 Object::Array(Array::of([Object::Int(1), Object::Int(0)])),
96 );
97 dict.push(names::BITS_PER_COMPONENT.clone(), Object::Int(1));
98 pixels.to_vec()
99 }
100 PixelFormat::Rgba8 => {
101 let (colour, alpha) = split_alpha(pixels);
102 let smask = doc.add(Object::Stream(Box::new(Stream::new(
103 smask_dict(width, height),
104 ByteSpan::from(alpha),
105 ))));
106 push_space(&mut dict, format);
107 dict.push(names::SMASK.clone(), Object::Ref(smask));
108 colour
109 }
110 PixelFormat::Gray8 | PixelFormat::Rgb8 | PixelFormat::Cmyk8 => {
111 push_space(&mut dict, format);
112 pixels.to_vec()
113 }
114 };
115
116 Ok(EmbeddedImage {
119 image: doc.add(Object::Stream(Box::new(Stream::new(
120 dict,
121 ByteSpan::from(data),
122 )))),
123 width,
124 height,
125 })
126}
127
128fn push_space(dict: &mut Dict, format: PixelFormat) {
129 if let Some(space) = device_space(format.stored_components()) {
130 dict.push(names::COLOR_SPACE.clone(), Object::Name(space));
131 }
132 dict.push(names::BITS_PER_COMPONENT.clone(), Object::Int(8));
133}
134
135fn smask_dict(width: u32, height: u32) -> Dict {
138 let mut dict = image_dict(width, height);
139 dict.push(
140 names::COLOR_SPACE.clone(),
141 Object::Name(names::DEVICE_GRAY.clone()),
142 );
143 dict.push(names::BITS_PER_COMPONENT.clone(), Object::Int(8));
144 dict
145}
146
147fn split_alpha(pixels: &[u8]) -> (Vec<u8>, Vec<u8>) {
149 let count = pixels.len() / 4;
150 let mut colour = Vec::with_capacity(count * 3);
151 let mut alpha = Vec::with_capacity(count);
152 for [red, green, blue, opacity] in pixels.as_chunks::<4>().0 {
153 colour.extend_from_slice(&[*red, *green, *blue]);
154 alpha.push(*opacity);
155 }
156 (colour, alpha)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::{PixelFormat, split_alpha};
162
163 #[test]
164 fn a_row_is_padded_to_a_byte_only_for_a_mask() {
165 assert_eq!(PixelFormat::Mask1.row_bytes(9), Some(2));
166 assert_eq!(PixelFormat::Gray8.row_bytes(9), Some(9));
167 assert_eq!(PixelFormat::Rgb8.row_bytes(9), Some(27));
168 assert_eq!(PixelFormat::Rgba8.row_bytes(9), Some(36));
169 assert_eq!(PixelFormat::Cmyk8.row_bytes(9), Some(36));
170 }
171
172 #[test]
174 fn rgba_stores_three_components() {
175 assert_eq!(PixelFormat::Rgba8.stored_components(), 3);
176 }
177
178 #[test]
179 fn the_alpha_channel_leaves_the_colour_channels_in_order() {
180 let (colour, alpha) = split_alpha(&[1, 2, 3, 4, 5, 6, 7, 8]);
181 assert_eq!(colour, vec![1, 2, 3, 5, 6, 7]);
182 assert_eq!(alpha, vec![4, 8]);
183 }
184}