Export: a converted JPG flattens translucent pixels before the conversion

A JPG exported in a profile other than the document's was converted while
still translucent and then composited over white, so translucent fills,
shadows and antialiased edges came out a few levels off what the document
shows. The render is now flattened over white in the document's profile
first. PNG and WebP keep their alpha and are unchanged.
This commit is contained in:
dotCooCoo committed 2026-10-31 03:20:20 -07:00
1 parent afc5e13972
commit b8fa8b87be
2 files changed
+26 -1

No files matched your search

+13 -1
View File
@@ -411,7 +411,19 @@ pub fn export_file(doc: &Document, ids: &[NodeId], setting: &ExportSetting, scal
// The render is in the document's profile; the setting's profile converts the pixels
// (out-of-gamut colors clip) and tags the file.
let profile = setting.color_profile.resolve(doc.color_profile);
doc.color_profile.conversion_to(profile).apply_pixels(pix.data_mut());
let conversion = doc.color_profile.conversion_to(profile);
// JPG has no alpha: the render is flattened over white in the document's profile
// before the conversion, so translucent pixels match what the document shows.
if setting.format == ExportFormat::Jpg && !conversion.is_identity() {
for [r, g, b, a] in pix.data_mut().as_chunks_mut::<4>().0.iter_mut() {
let under = 255 - *a;
*r = r.saturating_add(under);
*g = g.saturating_add(under);
*b = b.saturating_add(under);
*a = 255;
}
}
conversion.apply_pixels(pix.data_mut());
Ok((figcraft_render::encode_export(&pix, setting.format, profile).map_err(EngineError::Other)?, Vec::new()))
}
}
+13
View File
@@ -115,6 +115,19 @@ fn jpg_and_webp_follow_the_setting() {
}
}
#[test]
fn a_translucent_jpg_is_flattened_before_its_conversion() {
let (mut s, r) = session();
s.execute("fill.set", &json!({"ids": [r], "opacity": 0.5})).unwrap();
// The sRGB document shows 50% red over white; the Display P3 JPG holds that color converted.
let flat = export(&mut s, json!({"ids": [r], "format": "jpg", "colorProfile": "srgb"}));
let [fr, fg, fb] = middle(&flat);
let shown = Color::new(f32::from(fr) / 255.0, f32::from(fg) / 255.0, f32::from(fb) / 255.0, 1.0);
let want = rgb8(ColorProfile::Srgb.conversion_to(ColorProfile::DisplayP3).apply(shown));
let p3 = export(&mut s, json!({"ids": [r], "format": "jpg", "colorProfile": "displayP3"}));
assert!(close(middle(&p3), want, 2), "{:?} vs {want:?} (sRGB {:?})", middle(&p3), [fr, fg, fb]);
}
#[test]
fn svg_pdf_and_xml_stay_srgb() {
let (mut s, r) = session();