Media: Targa/HDR stills keep their Exif orientation; DPX test accepts a stated transfer

#755 decoded stills of an extension-decided format with load_from_memory_with_format,
bypassing #827's oriented decode: load_oriented now takes the format.
The DPX ffmpeg oracle test expected the bare codec name; ffmpeg builds that write
the transfer characteristic make it "DPX 8-bit RGB, linear".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-12 03:45:49 -04:00
1 parent 42ef79a2fd
commit 6d4e721d2c
4 files changed
+13 -9

No files matched your search

+1 -1
View File
@@ -87,7 +87,7 @@ pub(crate) fn decode(path: &str, bytes: &[u8], footage: &Footage, op: AlphaOp) -
let inner = effectcraft_psd::Psd::parse(data.to_vec()).map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?;
inner.composite().map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?
} else {
let img = crate::probe::load_oriented(data).map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?;
let img = crate::probe::load_oriented(data, None).map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?;
let rgba = img.to_rgba32f();
effectcraft_psd::Pixels { width: rgba.width(), height: rgba.height(), data: rgba.pixels().map(|p| p.0).collect() }
};
+1 -4
View File
@@ -770,10 +770,7 @@ impl Inner {
{
return Err(MediaError::Decode(format!("{path}: {why}")));
}
let loaded = match crate::still_format(path, &bytes) {
Some(fmt) => image::load_from_memory_with_format(&bytes, fmt),
None => crate::probe::load_oriented(&bytes),
};
let loaded = crate::probe::load_oriented(&bytes, crate::still_format(path, &bytes));
let img = match loaded {
Ok(img) => img,
// A multi-layer OpenEXR file without an unnamed RGB layer: its colour layer.
+8 -3
View File
@@ -221,10 +221,15 @@ pub(crate) fn swaps_sides(o: Orientation) -> bool {
/// Decode a still the way it is meant to be seen: its Exif orientation (a camera held sideways, a
/// mirrored scan) is applied to the pixels, as image viewers do. A file without
/// the tag, or with a broken one, decodes as stored.
pub(crate) fn load_oriented(bytes: &[u8]) -> image::ImageResult<image::DynamicImage> {
/// the tag, or with a broken one, decodes as stored. `fmt`: the format when the file's extension
/// decides it (Targa has no signature to guess from), else guessed from the bytes.
pub(crate) fn load_oriented(bytes: &[u8], fmt: Option<image::ImageFormat>) -> image::ImageResult<image::DynamicImage> {
use image::ImageDecoder;
let mut dec = image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format()?.into_decoder()?;
let reader = match fmt {
Some(fmt) => image::ImageReader::with_format(std::io::Cursor::new(bytes), fmt),
None => image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format()?,
};
let mut dec = reader.into_decoder()?;
let orientation = dec.orientation().unwrap_or(Orientation::NoTransforms);
let mut img = image::DynamicImage::from_decoder(dec)?;
img.apply_orientation(orientation);
+3 -1
View File
@@ -943,7 +943,9 @@ fn dpx_stills_match_ffmpeg() {
assert!(effectcraft_media::is_importable(&p));
let f = probe(&p).expect(pf);
assert_eq!(f.kind, FootageKind::Still);
assert_eq!((f.width, f.height, f.codec.as_str()), (250, 100, codec), "{pf}");
assert_eq!((f.width, f.height), (250, 100), "{pf}");
// Some ffmpeg builds state the transfer (", linear"), which the codec name then carries.
assert!(f.codec == codec || f.codec.starts_with(&format!("{codec}, ")), "{pf}: {}", f.codec);
assert_eq!(f.alpha, if alpha { AlphaMode::Straight } else { AlphaMode::Ignore }, "{pf}");
let img = MediaPool::new().frame_at(&f, Tick::ZERO).expect(pf);
let want = ffmpeg_rgba(&p, !codec.contains("8-bit"));