Skip to content

Commit f67d6e9

Browse files
committed
Apply compression to alpha channel
1 parent 35ef545 commit f67d6e9

File tree

1 file changed

+51
-7
lines changed

1 file changed

+51
-7
lines changed

src/image.rs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use base64::Engine;
55
use image::{DynamicImage, GenericImageView};
66
use serde::de::Error;
77
use serde_derive::{Deserialize, Serialize};
8+
use svg2pdf::usvg::Image;
89

910
use crate::{ColorBits, ColorSpace};
1011

@@ -1049,8 +1050,8 @@ pub(crate) fn image_to_stream(
10491050
),
10501051
]);
10511052

1053+
// Apply compression filter based on options
10521054
if let Some(opts) = options {
1053-
// Set compression filter based on options
10541055
if let Some(filter) = get_compression_filter(opts, &rgb8) {
10551056
dict.set("Filter", Name(filter.into()));
10561057

@@ -1065,7 +1066,10 @@ pub(crate) fn image_to_stream(
10651066
}
10661067

10671068
if let Some(alpha) = alpha {
1068-
let smask_dict = lopdf::Dictionary::from_iter(vec![
1069+
1070+
use crate::image::ImageCompression::*;
1071+
1072+
let mut smask_dict = lopdf::Dictionary::from_iter(vec![
10691073
("Type", Name("XObject".into())),
10701074
("Subtype", Name("Image".into())),
10711075
("Width", Integer(rgb8.width as i64)),
@@ -1075,16 +1079,56 @@ pub(crate) fn image_to_stream(
10751079
("ColorSpace", Name(ColorSpace::Greyscale.as_string().into())),
10761080
]);
10771081

1078-
let mut stream = lopdf::Stream::new(smask_dict, alpha.pixels).with_compression(true);
1082+
let format = options.as_ref()
1083+
.and_then(|s| s.format)
1084+
.unwrap_or_default();
1085+
1086+
// Create alpha-specific options that prefer lossless compression
1087+
let alpha_opts = ImageOptimizationOptions {
1088+
// For alpha channel, we generally want to use lossless compression
1089+
// unless specifically configured otherwise
1090+
format: Some(if matches!(format, Auto | Jpeg | Jpeg2000) {
1091+
Flate // Use Flate for alpha by default
1092+
} else {
1093+
format // Otherwise use the same format as main image
1094+
}),
1095+
.. options.cloned().unwrap_or_default()
1096+
};
1097+
1098+
// Apply compression to alpha channel too, but prefer lossless methods for alpha
1099+
if let Some(filter) = get_compression_filter(&alpha_opts, &alpha) {
1100+
smask_dict.set("Filter", Name(filter.into()));
1101+
1102+
// Set DecodeParms for alpha channel if needed
1103+
let jpeg_quality = options.as_ref().and_then(|s| s.quality);
1104+
if matches!(filter, "DCTDecode") && jpeg_quality.is_some() {
1105+
let quality = (jpeg_quality.unwrap() * 100.0) as i64;
1106+
smask_dict.set("DecodeParms", Dictionary(lopdf::Dictionary::from_iter(vec![
1107+
("Quality", Integer(quality))
1108+
])));
1109+
}
1110+
}
10791111

1080-
let _ = stream.compress();
1112+
let smask_has_filter = smask_dict.has(b"Filter");
1113+
let mut stream = lopdf::Stream::new(smask_dict, alpha.pixels);
1114+
1115+
// Only apply default compression if no filter was specified
1116+
if !smask_has_filter {
1117+
stream = stream.with_compression(true);
1118+
let _ = stream.compress();
1119+
}
10811120

10821121
dict.set("SMask", Reference(doc.add_object(stream)));
10831122
}
10841123

1085-
let mut s = lopdf::Stream::new(dict, rgb8.pixels).with_compression(true);
1086-
1087-
let _ = s.compress();
1124+
let dict_has_filter = dict.has(b"Filter");
1125+
let mut s = lopdf::Stream::new(dict, rgb8.pixels);
1126+
1127+
// Only apply default compression if no filter was specified
1128+
if !dict_has_filter {
1129+
s = s.with_compression(true);
1130+
let _ = s.compress();
1131+
}
10881132

10891133
s
10901134
}

0 commit comments

Comments
 (0)