mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-11 16:38:24 -06:00
Merge PR #8776 into integration batch
This commit is contained in:
+29
-5
@@ -668,6 +668,9 @@ pub(crate) fn export_flat_with(doc: &Document, format: Format, opts: &ExportOpti
|
||||
if format == Format::Tga {
|
||||
img = tga_image(doc, img, opts.tga_bits, &mut warnings)?;
|
||||
}
|
||||
if format == Format::Dds {
|
||||
img = dds_image(doc, img, &mut warnings)?;
|
||||
}
|
||||
encode_image(&img, format, opts, warnings)
|
||||
}
|
||||
|
||||
@@ -706,7 +709,7 @@ fn tga_image(doc: &Document, img: Image, bits: Option<TgaBits>, warnings: &mut V
|
||||
warnings.push("transparency composited over white; a 24-bit Targa has no alpha".into());
|
||||
matte_over_white(&img)
|
||||
}
|
||||
(TgaBits::Bits32, [channel]) => tga_with_alpha_channel(doc, img, channel, warnings),
|
||||
(TgaBits::Bits32, [channel]) => with_alpha_channel(doc, img, channel, "Targa", warnings),
|
||||
(TgaBits::Bits32, alphas) => {
|
||||
if !alphas.is_empty() {
|
||||
warnings.push("Targa holds one alpha channel; with several, none was written (as in Photoshop)".into());
|
||||
@@ -719,11 +722,32 @@ fn tga_image(doc: &Document, img: Image, bits: Option<TgaBits>, warnings: &mut V
|
||||
}
|
||||
}
|
||||
|
||||
/// A 32-bit Targa's image: the colours (composited over white where the layers are transparent)
|
||||
/// and `channel`'s values as the alpha.
|
||||
fn tga_with_alpha_channel(doc: &Document, img: Image, channel: &AlphaChannel, warnings: &mut Vec<String>) -> Result<Image, IoError> {
|
||||
/// The image a DDS texture holds (#8563): as with a 32-bit Targa, the document's alpha channel
|
||||
/// becomes the texture's alpha when it has exactly one (spot channels don't count), the colours
|
||||
/// composited over white where the layers are transparent; BC1 keeps it as on/off, BC2 and BC3
|
||||
/// in full. With none, the layers' transparency is the alpha as before; with several, none of
|
||||
/// them is written. Grayscale textures keep their transparency.
|
||||
fn dds_image(doc: &Document, img: Image, warnings: &mut Vec<String>) -> Result<Image, IoError> {
|
||||
let alphas: Vec<&AlphaChannel> = doc.channels.iter().filter(|c| c.spot.is_none()).collect();
|
||||
match alphas.as_slice() {
|
||||
[] => Ok(img),
|
||||
[channel] if img.layout().is_rgb() => with_alpha_channel(doc, img, channel, "texture", warnings),
|
||||
[_] => {
|
||||
warnings.push("a grayscale DDS keeps the layers' transparency; the alpha channel was not saved".into());
|
||||
Ok(img)
|
||||
}
|
||||
_ => {
|
||||
warnings.push("a DDS texture holds one alpha channel; with several, none was written".into());
|
||||
Ok(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A 32-bit Targa's or a texture's image: the colours (composited over white where the layers are
|
||||
/// transparent) and `channel`'s values as the alpha. `what` names the file in the warning.
|
||||
fn with_alpha_channel(doc: &Document, img: Image, channel: &AlphaChannel, what: &str, warnings: &mut Vec<String>) -> Result<Image, IoError> {
|
||||
let img = if img.layout().has_alpha() {
|
||||
warnings.push("transparency composited over white; the alpha channel is the Targa's alpha".into());
|
||||
warnings.push(format!("transparency composited over white; the alpha channel is the {what}'s alpha"));
|
||||
matte_over_white(&img)?
|
||||
} else {
|
||||
img
|
||||
|
||||
@@ -1190,3 +1190,39 @@ fn a_stereo_png_keeps_its_stereo_mark_on_save() {
|
||||
let plain = export(&single(ColorMode::Rgb, SampleType::U8, false), "png", &ExportOptions::default()).unwrap();
|
||||
assert_eq!(photocraft_codecs::png_stereo(&plain.bytes), None);
|
||||
}
|
||||
|
||||
/// #8563: a DDS export writes the document's alpha channel as the texture's alpha, as a 32-bit
|
||||
/// Targa does: exactly in an uncompressed texture, to 4 bits in BC2, interpolated in BC3, and on
|
||||
/// or off in BC1. With several alpha channels none is written, with a warning.
|
||||
#[test]
|
||||
fn dds_writes_the_alpha_channel_as_the_texture_alpha() {
|
||||
use photocraft_codecs::DdsCompression;
|
||||
let d = with_alpha_channel(single(ColorMode::Rgb, SampleType::U8, false), "Alpha 1");
|
||||
for compression in DdsCompression::ALL {
|
||||
let mut opts = ExportOptions::default();
|
||||
opts.encode.dds.compression = compression;
|
||||
let r = export(&d, "a.dds", &opts).unwrap();
|
||||
let img = photocraft_codecs::decode(&r.bytes).expect("decode dds");
|
||||
assert_eq!(img.layout(), photocraft_codecs::ChannelLayout::Rgba, "{compression:?}");
|
||||
let (w, _) = img.dimensions();
|
||||
let tolerance = match compression {
|
||||
DdsCompression::None => 1.0,
|
||||
DdsCompression::Bc2 => 9.0,
|
||||
_ => 18.0,
|
||||
};
|
||||
for (i, px) in img.data().as_chunks::<4>().0.iter().enumerate() {
|
||||
let x = i as u32 % w;
|
||||
let want = alpha_value(x) * 255.0;
|
||||
if compression == DdsCompression::Bc1 && x == 4 {
|
||||
continue; // half opacity sits on BC1's on/off threshold
|
||||
}
|
||||
assert!((f32::from(px[3]) - want).abs() <= tolerance, "{compression:?} pixel {i}: alpha {} want {want}", px[3]);
|
||||
}
|
||||
}
|
||||
// Two alpha channels: Photoshop's rule for Targa, none of them is written.
|
||||
let two = with_alpha_channel(d.clone(), "Alpha 2");
|
||||
let r = export(&two, "a.dds", &ExportOptions::default()).unwrap();
|
||||
assert!(r.warnings.iter().any(|w| w.contains("several")), "{:?}", r.warnings);
|
||||
let img = photocraft_codecs::decode(&r.bytes).expect("decode dds");
|
||||
assert!(!img.layout().has_alpha() || img.data().as_chunks::<4>().0.iter().all(|p| p[3] == 255));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user