blitz-pdf 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -12
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/document-options.d.ts +36 -0
- package/dist/document-options.d.ts.map +1 -0
- package/dist/document-options.js +236 -0
- package/dist/document-options.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/options.d.ts +35 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +21 -14
- package/dist/options.js.map +1 -1
- package/dist/types.d.ts +71 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +7 -0
- package/dist/validation.js.map +1 -0
- package/examples/zugferd.mjs +41 -0
- package/licenses/RUST_DEPENDENCIES.txt +27 -0
- package/package.json +3 -2
- package/src/browser.ts +1 -1
- package/src/document-options.ts +218 -0
- package/src/node.ts +1 -1
- package/src/options.ts +18 -13
- package/src/types.ts +76 -1
- package/src/validation.ts +6 -0
- package/vendor/README.md +27 -2
- package/vendor/krilla/.cargo-ok +1 -0
- package/vendor/krilla/.cargo_vcs_info.json +6 -0
- package/vendor/krilla/Cargo.lock +1357 -0
- package/vendor/krilla/Cargo.toml +165 -0
- package/vendor/krilla/LICENSE_APACHE +202 -0
- package/vendor/krilla/LICENSE_MIT +21 -0
- package/vendor/krilla/README.md +156 -0
- package/vendor/krilla/examples/basic.rs +117 -0
- package/vendor/krilla/examples/empty_document.rs +25 -0
- package/vendor/krilla/examples/parley.rs +174 -0
- package/vendor/krilla/examples/simple_text.rs +118 -0
- package/vendor/krilla/examples/stream_builder.rs +93 -0
- package/vendor/krilla/icc/LICENSE +116 -0
- package/vendor/krilla/icc/README.md +1 -0
- package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sGrey-v4.icc +0 -0
- package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sRGB-v4.icc +0 -0
- package/vendor/krilla/src/chunk_container.rs +503 -0
- package/vendor/krilla/src/configure/PDF_A1.md +227 -0
- package/vendor/krilla/src/configure/PDF_A2.md +269 -0
- package/vendor/krilla/src/configure/PDF_A3.md +31 -0
- package/vendor/krilla/src/configure/PDF_A4.md +211 -0
- package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
- package/vendor/krilla/src/configure/README.md +23 -0
- package/vendor/krilla/src/configure/mod.rs +197 -0
- package/vendor/krilla/src/configure/validate.rs +1244 -0
- package/vendor/krilla/src/configure/version.rs +134 -0
- package/vendor/krilla/src/content.rs +1352 -0
- package/vendor/krilla/src/data.rs +49 -0
- package/vendor/krilla/src/document.rs +156 -0
- package/vendor/krilla/src/error.rs +170 -0
- package/vendor/krilla/src/geom.rs +341 -0
- package/vendor/krilla/src/graphics/blend.rs +46 -0
- package/vendor/krilla/src/graphics/color.rs +552 -0
- package/vendor/krilla/src/graphics/graphic.rs +30 -0
- package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
- package/vendor/krilla/src/graphics/icc.rs +218 -0
- package/vendor/krilla/src/graphics/image.rs +835 -0
- package/vendor/krilla/src/graphics/mask.rs +128 -0
- package/vendor/krilla/src/graphics/mod.rs +14 -0
- package/vendor/krilla/src/graphics/paint.rs +434 -0
- package/vendor/krilla/src/graphics/separation.rs +86 -0
- package/vendor/krilla/src/graphics/shading_function.rs +865 -0
- package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
- package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
- package/vendor/krilla/src/graphics/xobject.rs +150 -0
- package/vendor/krilla/src/interactive/action.rs +68 -0
- package/vendor/krilla/src/interactive/annotation.rs +283 -0
- package/vendor/krilla/src/interactive/destination.rs +289 -0
- package/vendor/krilla/src/interactive/mod.rs +3 -0
- package/vendor/krilla/src/interchange/embed.rs +240 -0
- package/vendor/krilla/src/interchange/metadata.rs +564 -0
- package/vendor/krilla/src/interchange/mod.rs +4 -0
- package/vendor/krilla/src/interchange/outline.rs +263 -0
- package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
- package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
- package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
- package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
- package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
- package/vendor/krilla/src/lib.rs +179 -0
- package/vendor/krilla/src/num.rs +26 -0
- package/vendor/krilla/src/page.rs +612 -0
- package/vendor/krilla/src/pdf.rs +313 -0
- package/vendor/krilla/src/resource.rs +436 -0
- package/vendor/krilla/src/serialize.rs +1101 -0
- package/vendor/krilla/src/stream.rs +368 -0
- package/vendor/krilla/src/surface.rs +664 -0
- package/vendor/krilla/src/text/cid.rs +488 -0
- package/vendor/krilla/src/text/font.rs +351 -0
- package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
- package/vendor/krilla/src/text/glyph/colr.rs +581 -0
- package/vendor/krilla/src/text/glyph/mod.rs +205 -0
- package/vendor/krilla/src/text/glyph/outline.rs +72 -0
- package/vendor/krilla/src/text/glyph/svg.rs +34 -0
- package/vendor/krilla/src/text/group.rs +463 -0
- package/vendor/krilla/src/text/mod.rs +232 -0
- package/vendor/krilla/src/text/shape.rs +102 -0
- package/vendor/krilla/src/text/type3.rs +514 -0
- package/vendor/krilla/src/util.rs +286 -0
- package/vendor/stylo/properties/longhands.toml +0 -1
- package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
- package/wasm/blitz_pdf.js +10 -5
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
//! Creating and using bitmap images.
|
|
2
|
+
//!
|
|
3
|
+
//! krilla allows you to add bitmap images to your PDF very easily.
|
|
4
|
+
//! The currently supported formats include
|
|
5
|
+
//! - PNG
|
|
6
|
+
//! - JPG
|
|
7
|
+
//! - GIF
|
|
8
|
+
//! - WEBP
|
|
9
|
+
//! - Custom image formats via [`CustomImage`]
|
|
10
|
+
|
|
11
|
+
use std::fmt::{Debug, Formatter};
|
|
12
|
+
use std::hash::{Hash, Hasher};
|
|
13
|
+
use std::io::Cursor;
|
|
14
|
+
use std::ops::DerefMut;
|
|
15
|
+
use std::sync::Arc;
|
|
16
|
+
|
|
17
|
+
use pdf_writer::{Finish, Name, Ref};
|
|
18
|
+
use png::{BitDepth, ColorType, Transformations};
|
|
19
|
+
use zune_jpeg::zune_core::colorspace::ColorSpace;
|
|
20
|
+
use zune_jpeg::JpegDecoder;
|
|
21
|
+
|
|
22
|
+
use crate::chunk_container::ChunkContainer;
|
|
23
|
+
use crate::configure::ValidationError;
|
|
24
|
+
use crate::error::KrillaError;
|
|
25
|
+
use crate::graphics::color::DEVICE_GRAY;
|
|
26
|
+
use crate::graphics::color::{cmyk, luma, rgb};
|
|
27
|
+
use crate::graphics::icc::{GenericICCProfile, ICCBasedColorSpace, ICCProfile};
|
|
28
|
+
use crate::serialize::SerializeContext;
|
|
29
|
+
use crate::stream::{deflate_encode, FilterStreamBuilder};
|
|
30
|
+
use crate::util::{set_colorspace, Deferred, NameExt, SipHashable};
|
|
31
|
+
use crate::Data;
|
|
32
|
+
|
|
33
|
+
/// The number of bits per color component.
|
|
34
|
+
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
|
|
35
|
+
pub enum BitsPerComponent {
|
|
36
|
+
/// Eight bits per component.
|
|
37
|
+
Eight,
|
|
38
|
+
/// Sixteen bits per component.
|
|
39
|
+
Sixteen,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl BitsPerComponent {
|
|
43
|
+
fn as_u8(&self) -> u8 {
|
|
44
|
+
match self {
|
|
45
|
+
BitsPerComponent::Eight => 8,
|
|
46
|
+
BitsPerComponent::Sixteen => 16,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// The color space of the image.
|
|
52
|
+
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
|
|
53
|
+
pub enum ImageColorspace {
|
|
54
|
+
/// The RGB color space.
|
|
55
|
+
Rgb,
|
|
56
|
+
/// The luma color space.
|
|
57
|
+
Luma,
|
|
58
|
+
/// The CMYK color space.
|
|
59
|
+
Cmyk,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl ImageColorspace {
|
|
63
|
+
fn num_components(&self) -> u8 {
|
|
64
|
+
match self {
|
|
65
|
+
ImageColorspace::Luma => 1,
|
|
66
|
+
ImageColorspace::Rgb => 3,
|
|
67
|
+
ImageColorspace::Cmyk => 4,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn matches_icc_profile(&self, profile: &GenericICCProfile) -> bool {
|
|
72
|
+
match self {
|
|
73
|
+
ImageColorspace::Rgb => matches!(profile, GenericICCProfile::Rgb(_)),
|
|
74
|
+
ImageColorspace::Luma => matches!(profile, GenericICCProfile::Luma(_)),
|
|
75
|
+
ImageColorspace::Cmyk => matches!(profile, GenericICCProfile::Cmyk(_)),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl TryFrom<ColorSpace> for ImageColorspace {
|
|
81
|
+
type Error = ();
|
|
82
|
+
|
|
83
|
+
fn try_from(value: ColorSpace) -> Result<Self, Self::Error> {
|
|
84
|
+
match value {
|
|
85
|
+
ColorSpace::RGB => Ok(ImageColorspace::Rgb),
|
|
86
|
+
ColorSpace::RGBA => Ok(ImageColorspace::Rgb),
|
|
87
|
+
ColorSpace::YCbCr => Ok(ImageColorspace::Rgb),
|
|
88
|
+
ColorSpace::Luma => Ok(ImageColorspace::Luma),
|
|
89
|
+
ColorSpace::LumaA => Ok(ImageColorspace::Luma),
|
|
90
|
+
ColorSpace::YCCK => Ok(ImageColorspace::Cmyk),
|
|
91
|
+
ColorSpace::CMYK => Ok(ImageColorspace::Cmyk),
|
|
92
|
+
_ => Err(()),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
struct SampledRepr {
|
|
98
|
+
color_channel: Vec<u8>,
|
|
99
|
+
alpha_channel: Option<Vec<u8>>,
|
|
100
|
+
bits_per_component: BitsPerComponent,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
struct JpegRepr {
|
|
104
|
+
data: Data,
|
|
105
|
+
bits_per_component: BitsPerComponent,
|
|
106
|
+
invert_cmyk: bool,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
enum Repr {
|
|
110
|
+
Sampled(SampledRepr),
|
|
111
|
+
Jpeg(JpegRepr),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
impl Repr {
|
|
115
|
+
fn bits_per_component(&self) -> BitsPerComponent {
|
|
116
|
+
match self {
|
|
117
|
+
Repr::Sampled(s) => s.bits_per_component,
|
|
118
|
+
Repr::Jpeg(j) => j.bits_per_component,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// A trait for custom images, which you can use if the
|
|
124
|
+
/// current methods provided by krilla (JPEG/PNG/WEBP/GIF) images
|
|
125
|
+
/// are not suitable for your own purpose.
|
|
126
|
+
///
|
|
127
|
+
/// Note that a struct implementing this trait should be cheap to
|
|
128
|
+
/// hash and clone, otherwise performance might be bad!
|
|
129
|
+
pub trait CustomImage: Hash + Clone + Send + Sync + 'static {
|
|
130
|
+
/// Return the raw bytes of the color channel.
|
|
131
|
+
fn color_channel(&self) -> &[u8];
|
|
132
|
+
/// Return the raw bytes of the alpha channel, if available.
|
|
133
|
+
fn alpha_channel(&self) -> Option<&[u8]>;
|
|
134
|
+
/// Return the bits per component of the image.
|
|
135
|
+
fn bits_per_component(&self) -> BitsPerComponent;
|
|
136
|
+
/// Return the dimensions of the image.
|
|
137
|
+
fn size(&self) -> (u32, u32);
|
|
138
|
+
/// Return the ICC profile of the image, if available.
|
|
139
|
+
fn icc_profile(&self) -> Option<&[u8]>;
|
|
140
|
+
/// Return the color space of the image.
|
|
141
|
+
fn color_space(&self) -> ImageColorspace;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
struct ImageMetadata {
|
|
145
|
+
size: (u32, u32),
|
|
146
|
+
color_space: ImageColorspace,
|
|
147
|
+
has_alpha: bool,
|
|
148
|
+
bits_per_component: BitsPerComponent,
|
|
149
|
+
icc: Option<GenericICCProfile>,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
struct ImageRepr {
|
|
153
|
+
inner: Deferred<Result<Repr, String>>,
|
|
154
|
+
metadata: ImageMetadata,
|
|
155
|
+
sip: u128,
|
|
156
|
+
interpolate: bool,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
impl ImageRepr {
|
|
160
|
+
fn size(&self) -> (u32, u32) {
|
|
161
|
+
self.metadata.size
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn icc(&self) -> Option<GenericICCProfile> {
|
|
165
|
+
self.metadata.icc.clone()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fn color_space(&self) -> ImageColorspace {
|
|
169
|
+
self.metadata.color_space
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl Debug for ImageRepr {
|
|
174
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
175
|
+
write!(f, "ImageRepr {{..}}")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl Hash for ImageRepr {
|
|
180
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
181
|
+
self.sip.hash(state);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
impl PartialEq for ImageRepr {
|
|
186
|
+
fn eq(&self, other: &Self) -> bool {
|
|
187
|
+
self.sip == other.sip
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
impl Eq for ImageRepr {}
|
|
192
|
+
|
|
193
|
+
/// A bitmap image.
|
|
194
|
+
///
|
|
195
|
+
/// This type is cheap to hash and clone, but expensive to create.
|
|
196
|
+
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
|
197
|
+
pub struct Image(Arc<ImageRepr>);
|
|
198
|
+
|
|
199
|
+
impl Image {
|
|
200
|
+
/// Create a new bitmap image from a `.png` file.
|
|
201
|
+
pub fn from_png(data: Data, interpolate: bool) -> Result<Image, String> {
|
|
202
|
+
let hash = data.as_ref().sip_hash();
|
|
203
|
+
let metadata = png_metadata(data.as_ref())?;
|
|
204
|
+
|
|
205
|
+
Ok(Self(Arc::new(ImageRepr {
|
|
206
|
+
inner: Deferred::new(move || decode_png(data.as_ref())),
|
|
207
|
+
metadata,
|
|
208
|
+
sip: hash,
|
|
209
|
+
interpolate,
|
|
210
|
+
})))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Create a new bitmap image from a `.jpg` file.
|
|
214
|
+
pub fn from_jpeg(data: Data, interpolate: bool) -> Result<Image, String> {
|
|
215
|
+
let hash = data.as_ref().sip_hash();
|
|
216
|
+
let metadata = jpeg_metadata(data.as_ref())?;
|
|
217
|
+
|
|
218
|
+
Ok(Self(Arc::new(ImageRepr {
|
|
219
|
+
inner: Deferred::new(move || decode_jpeg(data)),
|
|
220
|
+
metadata,
|
|
221
|
+
sip: hash,
|
|
222
|
+
interpolate,
|
|
223
|
+
})))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/// Create a new bitmap image from a `.jpg` file with custom ICC profile.
|
|
227
|
+
#[doc(hidden)]
|
|
228
|
+
pub fn from_jpeg_with_icc(
|
|
229
|
+
data: Data,
|
|
230
|
+
icc_profile: Option<Data>,
|
|
231
|
+
interpolate: bool,
|
|
232
|
+
) -> Result<Image, String> {
|
|
233
|
+
let hash = data.as_ref().sip_hash();
|
|
234
|
+
let mut metadata = jpeg_metadata(data.as_ref())?;
|
|
235
|
+
let icc_profile =
|
|
236
|
+
icc_profile.and_then(|d| get_icc_profile_type(d.as_ref(), metadata.color_space));
|
|
237
|
+
metadata.icc = icc_profile;
|
|
238
|
+
|
|
239
|
+
Ok(Self(Arc::new(ImageRepr {
|
|
240
|
+
inner: Deferred::new(move || decode_jpeg(data)),
|
|
241
|
+
metadata,
|
|
242
|
+
sip: hash,
|
|
243
|
+
interpolate,
|
|
244
|
+
})))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Create a new bitmap image from a `.gif` file.
|
|
248
|
+
pub fn from_gif(data: Data, interpolate: bool) -> Result<Image, String> {
|
|
249
|
+
let hash = data.as_ref().sip_hash();
|
|
250
|
+
let metadata = gif_metadata(data.as_ref())?;
|
|
251
|
+
|
|
252
|
+
Ok(Self(Arc::new(ImageRepr {
|
|
253
|
+
inner: Deferred::new(move || decode_gif(data)),
|
|
254
|
+
metadata,
|
|
255
|
+
sip: hash,
|
|
256
|
+
interpolate,
|
|
257
|
+
})))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Create a new bitmap image from a `.webp` file.
|
|
261
|
+
///
|
|
262
|
+
/// Returns `None` if krilla was unable to parse the file.
|
|
263
|
+
pub fn from_webp(data: Data, interpolate: bool) -> Result<Image, String> {
|
|
264
|
+
let hash = data.as_ref().sip_hash();
|
|
265
|
+
let metadata = webp_metadata(data.as_ref())?;
|
|
266
|
+
|
|
267
|
+
Ok(Self(Arc::new(ImageRepr {
|
|
268
|
+
inner: Deferred::new(move || decode_webp(data)),
|
|
269
|
+
metadata,
|
|
270
|
+
sip: hash,
|
|
271
|
+
interpolate,
|
|
272
|
+
})))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// Create a new image from a custom image.
|
|
276
|
+
///
|
|
277
|
+
/// Panics if the dimensions of the image and the length of the
|
|
278
|
+
/// data doesn't match.
|
|
279
|
+
pub fn from_custom<T: CustomImage>(image: T, interpolate: bool) -> Result<Image, String> {
|
|
280
|
+
let hash = (image.clone(), interpolate).sip_hash();
|
|
281
|
+
let metadata = ImageMetadata {
|
|
282
|
+
size: image.size(),
|
|
283
|
+
color_space: image.color_space(),
|
|
284
|
+
has_alpha: image.alpha_channel().is_some(),
|
|
285
|
+
bits_per_component: image.bits_per_component(),
|
|
286
|
+
icc: image
|
|
287
|
+
.icc_profile()
|
|
288
|
+
.and_then(|d| get_icc_profile_type(d, image.color_space())),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
Ok(Self(Arc::new(ImageRepr {
|
|
292
|
+
inner: Deferred::new(move || {
|
|
293
|
+
let bytes_per_component = (image.bits_per_component().as_u8() / 8) as u32;
|
|
294
|
+
let color_channel_len = bytes_per_component
|
|
295
|
+
* image.color_space().num_components() as u32
|
|
296
|
+
* metadata.size.0
|
|
297
|
+
* metadata.size.1;
|
|
298
|
+
let color_channel = image.color_channel();
|
|
299
|
+
assert_eq!(color_channel.len(), color_channel_len as usize);
|
|
300
|
+
|
|
301
|
+
let alpha_channel_len = bytes_per_component * metadata.size.0 * metadata.size.1;
|
|
302
|
+
let alpha_channel = image.alpha_channel();
|
|
303
|
+
if let Some(alpha_channel) = alpha_channel {
|
|
304
|
+
assert_eq!(alpha_channel.len(), alpha_channel_len as usize);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
Ok(Repr::Sampled(SampledRepr {
|
|
308
|
+
color_channel: deflate_encode(color_channel),
|
|
309
|
+
alpha_channel: image.alpha_channel().map(deflate_encode),
|
|
310
|
+
bits_per_component: image.bits_per_component(),
|
|
311
|
+
}))
|
|
312
|
+
}),
|
|
313
|
+
metadata,
|
|
314
|
+
sip: hash,
|
|
315
|
+
interpolate,
|
|
316
|
+
})))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Create a new RGB image from raw RGBA pixels.
|
|
320
|
+
pub fn from_rgba8(data: Vec<u8>, width: u32, height: u32) -> Self {
|
|
321
|
+
let hash = data.sip_hash();
|
|
322
|
+
let metadata = ImageMetadata {
|
|
323
|
+
has_alpha: true,
|
|
324
|
+
size: (width, height),
|
|
325
|
+
bits_per_component: BitsPerComponent::Eight,
|
|
326
|
+
color_space: ImageColorspace::Rgb,
|
|
327
|
+
icc: None,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
Self(Arc::new(ImageRepr {
|
|
331
|
+
inner: Deferred::new(move || {
|
|
332
|
+
let (color_channel, alpha_channel, bits_per_component) =
|
|
333
|
+
handle_u8_image(&data, ColorSpace::RGBA);
|
|
334
|
+
|
|
335
|
+
Ok(Repr::Sampled(SampledRepr {
|
|
336
|
+
color_channel,
|
|
337
|
+
alpha_channel,
|
|
338
|
+
bits_per_component,
|
|
339
|
+
}))
|
|
340
|
+
}),
|
|
341
|
+
metadata,
|
|
342
|
+
sip: hash,
|
|
343
|
+
interpolate: false,
|
|
344
|
+
}))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// Return the size of the image.
|
|
348
|
+
pub fn size(&self) -> (u32, u32) {
|
|
349
|
+
self.0.size()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
fn icc(&self) -> Option<GenericICCProfile> {
|
|
353
|
+
self.0.icc()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
fn color_space(&self) -> ImageColorspace {
|
|
357
|
+
self.0.color_space()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
pub(crate) fn serialize(
|
|
361
|
+
self,
|
|
362
|
+
sc: &mut SerializeContext,
|
|
363
|
+
chunk_container: &mut ChunkContainer,
|
|
364
|
+
root_ref: Ref,
|
|
365
|
+
) {
|
|
366
|
+
let soft_mask_id = self.0.metadata.has_alpha.then(|| {
|
|
367
|
+
sc.register_validation_error(ValidationError::Transparency(sc.location));
|
|
368
|
+
sc.new_ref()
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
let icc_ref = self.icc().and_then(|ic| {
|
|
372
|
+
if sc
|
|
373
|
+
.serialize_settings()
|
|
374
|
+
.pdf_version()
|
|
375
|
+
.supports_icc(ic.metadata())
|
|
376
|
+
&& self.color_space().matches_icc_profile(&ic)
|
|
377
|
+
{
|
|
378
|
+
let ref_ = match ic {
|
|
379
|
+
GenericICCProfile::Luma(l) => {
|
|
380
|
+
sc.register_cacheable(chunk_container, ICCBasedColorSpace(l))
|
|
381
|
+
}
|
|
382
|
+
GenericICCProfile::Rgb(r) => {
|
|
383
|
+
sc.register_cacheable(chunk_container, ICCBasedColorSpace(r))
|
|
384
|
+
}
|
|
385
|
+
GenericICCProfile::Cmyk(c) => {
|
|
386
|
+
sc.register_cacheable(chunk_container, ICCBasedColorSpace(c))
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
Some(ref_)
|
|
391
|
+
} else {
|
|
392
|
+
// Don't embed ICC profiles from images if the current
|
|
393
|
+
// PDF version does not support it.
|
|
394
|
+
None
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
if self.0.interpolate {
|
|
399
|
+
sc.register_validation_error(ValidationError::ImageInterpolation(sc.location));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let serialize_settings = sc.serialize_settings().clone();
|
|
403
|
+
|
|
404
|
+
let cs = {
|
|
405
|
+
let cs = match self.color_space() {
|
|
406
|
+
ImageColorspace::Rgb => {
|
|
407
|
+
rgb::color_space(sc.serialize_settings().no_device_cs).into()
|
|
408
|
+
}
|
|
409
|
+
ImageColorspace::Luma => {
|
|
410
|
+
luma::color_space(sc.serialize_settings().no_device_cs).into()
|
|
411
|
+
}
|
|
412
|
+
ImageColorspace::Cmyk => match cmyk::color_space(&sc.serialize_settings()) {
|
|
413
|
+
None => {
|
|
414
|
+
sc.register_validation_error(ValidationError::MissingCMYKProfile);
|
|
415
|
+
crate::color::DeviceColorSpace::Cmyk.into()
|
|
416
|
+
}
|
|
417
|
+
Some(cs) => cs.into(),
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
sc.register_colorspace(chunk_container, cs)
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
let supports_bit_depth = sc
|
|
425
|
+
.serialize_settings()
|
|
426
|
+
.configuration
|
|
427
|
+
.version()
|
|
428
|
+
.supports_bit_depth(self.0.metadata.bits_per_component);
|
|
429
|
+
let location = sc.location;
|
|
430
|
+
let mut chunk = sc.new_chunk();
|
|
431
|
+
|
|
432
|
+
let chunk = Deferred::new(move || {
|
|
433
|
+
if !supports_bit_depth {
|
|
434
|
+
return Err(KrillaError::SixteenBitImage(self.clone(), location));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let repr = self
|
|
438
|
+
.0
|
|
439
|
+
.inner
|
|
440
|
+
.wait()
|
|
441
|
+
.as_ref()
|
|
442
|
+
.map_err(|e| KrillaError::Image(self.clone(), location, e.clone()))?;
|
|
443
|
+
|
|
444
|
+
let alpha_mask = match repr {
|
|
445
|
+
Repr::Sampled(sampled) => sampled.alpha_channel.as_ref().map(|mask_data| {
|
|
446
|
+
let soft_mask_id = soft_mask_id.unwrap();
|
|
447
|
+
let mask_stream = FilterStreamBuilder::new_from_deflated(mask_data)
|
|
448
|
+
.finish(&serialize_settings);
|
|
449
|
+
let mut s_mask = chunk.image_xobject(soft_mask_id, mask_stream.encoded_data());
|
|
450
|
+
mask_stream.write_filters(s_mask.deref_mut().deref_mut());
|
|
451
|
+
s_mask.width(self.size().0 as i32);
|
|
452
|
+
s_mask.height(self.size().1 as i32);
|
|
453
|
+
s_mask.pair(
|
|
454
|
+
Name(b"ColorSpace"),
|
|
455
|
+
// Mask color space must be device gray -- see Table 145.
|
|
456
|
+
DEVICE_GRAY.to_pdf_name(),
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
if self.0.interpolate {
|
|
460
|
+
s_mask.interpolate(true);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
s_mask.bits_per_component(repr.bits_per_component().as_u8() as i32);
|
|
464
|
+
soft_mask_id
|
|
465
|
+
}),
|
|
466
|
+
Repr::Jpeg(_) => None,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
let filter_stream = match repr {
|
|
470
|
+
Repr::Sampled(s) => FilterStreamBuilder::new_from_deflated(&s.color_channel)
|
|
471
|
+
.finish(&serialize_settings),
|
|
472
|
+
Repr::Jpeg(j) => FilterStreamBuilder::new_from_jpeg_data(j.data.as_ref())
|
|
473
|
+
.finish(&serialize_settings),
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
let mut image_x_object = chunk.image_xobject(root_ref, filter_stream.encoded_data());
|
|
477
|
+
filter_stream.write_filters(image_x_object.deref_mut().deref_mut());
|
|
478
|
+
image_x_object.width(self.size().0 as i32);
|
|
479
|
+
image_x_object.height(self.size().1 as i32);
|
|
480
|
+
|
|
481
|
+
if let Some(icc_ref) = icc_ref {
|
|
482
|
+
image_x_object.pair(Name(b"ColorSpace"), icc_ref);
|
|
483
|
+
} else {
|
|
484
|
+
set_colorspace(cs, image_x_object.deref_mut());
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if self.0.interpolate {
|
|
488
|
+
image_x_object.interpolate(true);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Photoshop CMYK images need to be inverted, see
|
|
492
|
+
// https://github.com/sile-typesetter/libtexpdf/blob/1891bee5e0b73165e4a259f910d3ea3fe1df0b42/jpegimage.c#L25-L51
|
|
493
|
+
// I'm not sure if this applies to all JPEG CMYK images out there, but for now we just
|
|
494
|
+
// always do it. In libtexpdf, they only seem to do it if they can find the Adobe APP
|
|
495
|
+
// marker.
|
|
496
|
+
if let Repr::Jpeg(j) = repr {
|
|
497
|
+
if j.invert_cmyk {
|
|
498
|
+
image_x_object
|
|
499
|
+
.decode([1.0, 0.0].repeat(self.color_space().num_components() as usize));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
image_x_object.bits_per_component(repr.bits_per_component().as_u8() as i32);
|
|
504
|
+
if let Some(soft_mask_id) = alpha_mask {
|
|
505
|
+
image_x_object.s_mask(soft_mask_id);
|
|
506
|
+
}
|
|
507
|
+
image_x_object.finish();
|
|
508
|
+
|
|
509
|
+
Ok(chunk)
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
chunk_container.streams.images.push(chunk);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const PNG_TRANSFORMATIONS: Transformations = Transformations::EXPAND;
|
|
517
|
+
|
|
518
|
+
fn png_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
|
|
519
|
+
let cursor = Cursor::new(data);
|
|
520
|
+
let mut decoder = png::Decoder::new(cursor);
|
|
521
|
+
decoder.set_transformations(PNG_TRANSFORMATIONS);
|
|
522
|
+
let reader = decoder
|
|
523
|
+
.read_info()
|
|
524
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
525
|
+
let info = reader.info();
|
|
526
|
+
|
|
527
|
+
let size = (info.width, info.height);
|
|
528
|
+
let (color_type, bit_depth) = reader.output_color_type();
|
|
529
|
+
let bits_per_component = match bit_depth {
|
|
530
|
+
// We will normalize to 8 when decoding.
|
|
531
|
+
BitDepth::One | BitDepth::Two | BitDepth::Four | BitDepth::Eight => BitsPerComponent::Eight,
|
|
532
|
+
BitDepth::Sixteen => BitsPerComponent::Sixteen,
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
let (image_color_space, has_alpha) = match color_type {
|
|
536
|
+
ColorType::Grayscale => (ImageColorspace::Luma, false),
|
|
537
|
+
ColorType::GrayscaleAlpha => (ImageColorspace::Luma, true),
|
|
538
|
+
ColorType::Rgb => (ImageColorspace::Rgb, false),
|
|
539
|
+
ColorType::Rgba => (ImageColorspace::Rgb, true),
|
|
540
|
+
ColorType::Indexed => {
|
|
541
|
+
return Err("image uses an indexed color space, which is unsupported".to_string())
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
let icc = info
|
|
545
|
+
.icc_profile
|
|
546
|
+
.as_ref()
|
|
547
|
+
.and_then(|i| get_icc_profile_type(i, image_color_space));
|
|
548
|
+
|
|
549
|
+
Ok(ImageMetadata {
|
|
550
|
+
has_alpha,
|
|
551
|
+
size,
|
|
552
|
+
bits_per_component,
|
|
553
|
+
color_space: image_color_space,
|
|
554
|
+
icc,
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
fn decode_png(data: &[u8]) -> Result<Repr, String> {
|
|
559
|
+
let cursor = Cursor::new(data);
|
|
560
|
+
let mut decoder = png::Decoder::new(cursor);
|
|
561
|
+
decoder.set_transformations(PNG_TRANSFORMATIONS);
|
|
562
|
+
let mut reader = decoder
|
|
563
|
+
.read_info()
|
|
564
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
565
|
+
let mut img_data = vec![0; reader.output_buffer_size().ok_or("image is too large")?];
|
|
566
|
+
let _ = reader
|
|
567
|
+
.next_frame(&mut img_data)
|
|
568
|
+
.map_err(|e| e.to_string())?;
|
|
569
|
+
let (color_type, bit_depth) = reader.output_color_type();
|
|
570
|
+
|
|
571
|
+
let color_space = match color_type {
|
|
572
|
+
ColorType::Rgb => ColorSpace::RGB,
|
|
573
|
+
ColorType::Rgba => ColorSpace::RGBA,
|
|
574
|
+
ColorType::Grayscale => ColorSpace::Luma,
|
|
575
|
+
ColorType::GrayscaleAlpha => ColorSpace::LumaA,
|
|
576
|
+
_ => unreachable!(),
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
let (color_channel, alpha_channel, bits_per_component) = match bit_depth {
|
|
580
|
+
BitDepth::Eight => handle_u8_image(&img_data, color_space),
|
|
581
|
+
BitDepth::Sixteen => handle_u16_image(&img_data, color_space),
|
|
582
|
+
_ => return Err("image has an unsupported bit-depth".to_string()),
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
Ok(Repr::Sampled(SampledRepr {
|
|
586
|
+
color_channel,
|
|
587
|
+
alpha_channel,
|
|
588
|
+
bits_per_component,
|
|
589
|
+
}))
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
fn jpeg_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
|
|
593
|
+
let reader = Cursor::new(data);
|
|
594
|
+
let mut decoder = JpegDecoder::new(reader);
|
|
595
|
+
decoder
|
|
596
|
+
.decode_headers()
|
|
597
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
598
|
+
|
|
599
|
+
let size = {
|
|
600
|
+
let dimensions = decoder
|
|
601
|
+
.dimensions()
|
|
602
|
+
.ok_or("failed to read image dimensions".to_string())?;
|
|
603
|
+
(dimensions.0 as u32, dimensions.1 as u32)
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
let image_color_space = decoder
|
|
607
|
+
.input_colorspace()
|
|
608
|
+
.and_then(|c| c.try_into().ok())
|
|
609
|
+
.ok_or("failed to read image colorspace".to_string())?;
|
|
610
|
+
|
|
611
|
+
let icc = decoder
|
|
612
|
+
.icc_profile()
|
|
613
|
+
.and_then(|d| get_icc_profile_type(&d, image_color_space));
|
|
614
|
+
|
|
615
|
+
Ok(ImageMetadata {
|
|
616
|
+
has_alpha: false,
|
|
617
|
+
size,
|
|
618
|
+
bits_per_component: BitsPerComponent::Eight,
|
|
619
|
+
color_space: image_color_space,
|
|
620
|
+
icc,
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
fn decode_jpeg(data: Data) -> Result<Repr, String> {
|
|
625
|
+
let reader = Cursor::new(data.as_ref());
|
|
626
|
+
let mut decoder = JpegDecoder::new(reader);
|
|
627
|
+
decoder
|
|
628
|
+
.decode_headers()
|
|
629
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
630
|
+
|
|
631
|
+
let input_color_space = decoder
|
|
632
|
+
.input_colorspace()
|
|
633
|
+
.ok_or("failed to read image colorspace".to_string())?;
|
|
634
|
+
|
|
635
|
+
if matches!(
|
|
636
|
+
input_color_space,
|
|
637
|
+
ColorSpace::Luma
|
|
638
|
+
| ColorSpace::YCbCr
|
|
639
|
+
| ColorSpace::RGB
|
|
640
|
+
| ColorSpace::CMYK
|
|
641
|
+
| ColorSpace::YCCK
|
|
642
|
+
) {
|
|
643
|
+
Ok(Repr::Jpeg(JpegRepr {
|
|
644
|
+
data,
|
|
645
|
+
bits_per_component: BitsPerComponent::Eight,
|
|
646
|
+
invert_cmyk: matches!(input_color_space, ColorSpace::YCCK | ColorSpace::CMYK),
|
|
647
|
+
}))
|
|
648
|
+
} else {
|
|
649
|
+
// JPEGs shouldn't be able to have a different color space?
|
|
650
|
+
Err("image has an unknown color space".to_string())
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
fn decode_gif(data: Data) -> Result<Repr, String> {
|
|
655
|
+
let mut decoder = gif::DecodeOptions::new();
|
|
656
|
+
decoder.set_color_output(gif::ColorOutput::RGBA);
|
|
657
|
+
let mut decoder = decoder
|
|
658
|
+
.read_info(data.as_ref())
|
|
659
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
660
|
+
let first_frame = decoder
|
|
661
|
+
.read_next_frame()
|
|
662
|
+
.map_err(|e| e.to_string())?
|
|
663
|
+
.ok_or("GIF image seems to be empty".to_string())?;
|
|
664
|
+
|
|
665
|
+
let (color_channel, alpha_channel, bits_per_component) =
|
|
666
|
+
handle_u8_image(first_frame.buffer.as_ref(), ColorSpace::RGBA);
|
|
667
|
+
|
|
668
|
+
Ok(Repr::Sampled(SampledRepr {
|
|
669
|
+
color_channel,
|
|
670
|
+
alpha_channel,
|
|
671
|
+
bits_per_component,
|
|
672
|
+
}))
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
fn gif_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
|
|
676
|
+
let size = imagesize::blob_size(data).map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
677
|
+
|
|
678
|
+
Ok(ImageMetadata {
|
|
679
|
+
// We always decode GIFs using RGBA, see `decode_gif`
|
|
680
|
+
has_alpha: true,
|
|
681
|
+
bits_per_component: BitsPerComponent::Eight,
|
|
682
|
+
size: (size.width as u32, size.height as u32),
|
|
683
|
+
color_space: ImageColorspace::Rgb,
|
|
684
|
+
icc: None,
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
fn webp_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
|
|
689
|
+
let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data))
|
|
690
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
691
|
+
let size = decoder.dimensions();
|
|
692
|
+
let color_space = ImageColorspace::Rgb;
|
|
693
|
+
let icc = decoder
|
|
694
|
+
.icc_profile()
|
|
695
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?
|
|
696
|
+
.and_then(|d| get_icc_profile_type(&d, color_space));
|
|
697
|
+
|
|
698
|
+
Ok(ImageMetadata {
|
|
699
|
+
has_alpha: decoder.has_alpha(),
|
|
700
|
+
bits_per_component: BitsPerComponent::Eight,
|
|
701
|
+
size,
|
|
702
|
+
color_space,
|
|
703
|
+
icc,
|
|
704
|
+
})
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
fn decode_webp(data: Data) -> Result<Repr, String> {
|
|
708
|
+
let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data.as_ref()))
|
|
709
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
710
|
+
let mut first_frame = vec![0; decoder.output_buffer_size().ok_or("image is too large")?];
|
|
711
|
+
decoder
|
|
712
|
+
.read_image(&mut first_frame)
|
|
713
|
+
.map_err(|e| e.to_string().to_ascii_lowercase())?;
|
|
714
|
+
|
|
715
|
+
let color_space = if decoder.has_alpha() {
|
|
716
|
+
ColorSpace::RGBA
|
|
717
|
+
} else {
|
|
718
|
+
ColorSpace::RGB
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
let (color_channel, alpha_channel, bits_per_component) =
|
|
722
|
+
handle_u8_image(&first_frame, color_space);
|
|
723
|
+
|
|
724
|
+
Ok(Repr::Sampled(SampledRepr {
|
|
725
|
+
color_channel,
|
|
726
|
+
alpha_channel,
|
|
727
|
+
bits_per_component,
|
|
728
|
+
}))
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
fn handle_u8_image(data: &[u8], cs: ColorSpace) -> (Vec<u8>, Option<Vec<u8>>, BitsPerComponent) {
|
|
732
|
+
let mut alphas = if cs.has_alpha() {
|
|
733
|
+
Vec::with_capacity(data.len() / cs.num_components())
|
|
734
|
+
} else {
|
|
735
|
+
Vec::new()
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
let color_channel = match cs {
|
|
739
|
+
ColorSpace::RGB => deflate_encode(data),
|
|
740
|
+
ColorSpace::RGBA => {
|
|
741
|
+
let mut buf = Vec::with_capacity(data.len() * 3 / 4);
|
|
742
|
+
data.chunks_exact(4).for_each(|data| {
|
|
743
|
+
buf.extend_from_slice(&data[0..3]);
|
|
744
|
+
alphas.push(data[3]);
|
|
745
|
+
});
|
|
746
|
+
deflate_encode(&buf)
|
|
747
|
+
}
|
|
748
|
+
ColorSpace::Luma => deflate_encode(data),
|
|
749
|
+
ColorSpace::LumaA => {
|
|
750
|
+
let mut buf = Vec::with_capacity(data.len() / 2);
|
|
751
|
+
data.chunks_exact(2).for_each(|data| {
|
|
752
|
+
buf.push(data[0]);
|
|
753
|
+
alphas.push(data[1]);
|
|
754
|
+
});
|
|
755
|
+
deflate_encode(&buf)
|
|
756
|
+
}
|
|
757
|
+
// PNG/WEBP/GIF only support those three, so should be enough?
|
|
758
|
+
_ => unimplemented!(),
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
let alpha_channel = if !alphas.is_empty() && alphas.iter().any(|v| *v != 255) {
|
|
762
|
+
Some(deflate_encode(&alphas))
|
|
763
|
+
} else {
|
|
764
|
+
None
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
(color_channel, alpha_channel, BitsPerComponent::Eight)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
fn handle_u16_image(data: &[u8], cs: ColorSpace) -> (Vec<u8>, Option<Vec<u8>>, BitsPerComponent) {
|
|
771
|
+
let mut alphas = if cs.has_alpha() {
|
|
772
|
+
Vec::with_capacity(data.len() / cs.num_components())
|
|
773
|
+
} else {
|
|
774
|
+
Vec::new()
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
let encoded_image = match cs {
|
|
778
|
+
ColorSpace::RGB => deflate_encode(data),
|
|
779
|
+
ColorSpace::RGBA => {
|
|
780
|
+
let mut buf = Vec::with_capacity(data.len() * 3 / 4);
|
|
781
|
+
data.chunks_exact(8).for_each(|data| {
|
|
782
|
+
buf.extend_from_slice(&data[0..6]);
|
|
783
|
+
alphas.extend_from_slice(&data[6..]);
|
|
784
|
+
});
|
|
785
|
+
deflate_encode(&buf)
|
|
786
|
+
}
|
|
787
|
+
ColorSpace::Luma => deflate_encode(data),
|
|
788
|
+
ColorSpace::LumaA => {
|
|
789
|
+
let mut buf = Vec::with_capacity(data.len() / 2);
|
|
790
|
+
data.chunks_exact(4).for_each(|data| {
|
|
791
|
+
buf.extend_from_slice(&data[0..2]);
|
|
792
|
+
alphas.extend_from_slice(&data[2..]);
|
|
793
|
+
});
|
|
794
|
+
deflate_encode(&buf)
|
|
795
|
+
}
|
|
796
|
+
// PNG/WEBP/GIF only support those three, so should be enough?
|
|
797
|
+
_ => unimplemented!(),
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
let encoded_mask = if !alphas.is_empty() {
|
|
801
|
+
Some(deflate_encode(&alphas))
|
|
802
|
+
} else {
|
|
803
|
+
None
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
(encoded_image, encoded_mask, BitsPerComponent::Sixteen)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
fn get_icc_profile_type(data: &[u8], color_space: ImageColorspace) -> Option<GenericICCProfile> {
|
|
810
|
+
let wrapper = match color_space {
|
|
811
|
+
ImageColorspace::Rgb => GenericICCProfile::Rgb(ICCProfile::new(data)?),
|
|
812
|
+
ImageColorspace::Luma => GenericICCProfile::Luma(ICCProfile::new(data)?),
|
|
813
|
+
ImageColorspace::Cmyk => GenericICCProfile::Cmyk(ICCProfile::new(data)?),
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
Some(wrapper)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
#[cfg(test)]
|
|
820
|
+
mod tests {
|
|
821
|
+
use crate::image::Image;
|
|
822
|
+
use std::sync::Arc;
|
|
823
|
+
|
|
824
|
+
#[test]
|
|
825
|
+
fn invalid_png_image() {
|
|
826
|
+
let e = Image::from_png(Arc::new(b"dfngiudfg".to_vec()).into(), false);
|
|
827
|
+
assert!(e.is_err());
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
#[test]
|
|
831
|
+
fn invalid_jpeg_image() {
|
|
832
|
+
let e = Image::from_jpeg(Arc::new(b"dfngiudfg".to_vec()).into(), false);
|
|
833
|
+
assert!(e.is_err());
|
|
834
|
+
}
|
|
835
|
+
}
|