mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-04 01:28:49 +00:00
Render Queue EXR sequences are scan-line, not tiled (#2805)
image's OpenEXR encoder writes 64x64 RLE tiles, which FilmCraft and other scan-line readers refuse. Write the frames with the exr crate in scan lines with ZIP, like comp.saveFrameAsExr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad7abfa507
commit
3b2e26ceef
4 files changed
+29
-3
No files matched your search
Generated
+1
@@ -1555,6 +1555,7 @@ dependencies = [
|
||||
"effectcraft-render",
|
||||
"effectcraft-time",
|
||||
"effectcraft-vp9enc",
|
||||
"exr",
|
||||
"filmcraft-aac",
|
||||
"filmcraft-h264enc",
|
||||
"filmcraft-isobmff",
|
||||
|
||||
@@ -23,6 +23,7 @@ effectcraft-vp9enc = { workspace = true }
|
||||
effectcraft-hevcenc = { workspace = true }
|
||||
effectcraft-av1enc = { workspace = true }
|
||||
image = { workspace = true, features = ["png", "jpeg", "tiff", "exr", "tga"] }
|
||||
exr = { workspace = true }
|
||||
gif = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
web-time = { workspace = true }
|
||||
|
||||
@@ -297,7 +297,7 @@ struct StillOutput {
|
||||
|
||||
fn write_still<W: std::io::Write + std::io::Seek>(cx: &Cx, file: &mut W, img: &Image, comp: &Comp, output: StillOutput) -> Result<()> {
|
||||
let StillOutput { format: fmt, channels, size: (w, h), quality } = output;
|
||||
use image::{ExtendedColorType, ImageEncoder, ImageFormat};
|
||||
use image::{ExtendedColorType, ImageEncoder};
|
||||
match fmt {
|
||||
OutputFormat::ExrSequence => {
|
||||
// Linear light, premultiplied (the EXR convention); RGB keeps alpha = 1 over the background.
|
||||
@@ -326,8 +326,16 @@ fn write_still<W: std::io::Write + std::io::Seek>(cx: &Cx, file: &mut W, img: &I
|
||||
data.extend_from_slice(&[c[0], c[1], c[2], a]);
|
||||
}
|
||||
}
|
||||
let buf = image::Rgba32FImage::from_raw(w, h, data).ok_or_else(|| ExportError::Encode("exr buffer".into()))?;
|
||||
image::DynamicImage::ImageRgba32F(buf).write_to(file, ImageFormat::OpenExr).map_err(|e| ExportError::Encode(e.to_string()))
|
||||
// Scan lines with ZIP like `comp.saveFrameAsExr`: `image`'s encoder writes 64×64 RLE tiles,
|
||||
// which FilmCraft and other scan-line readers refuse (#2805).
|
||||
use exr::prelude::{Encoding, Layer, LayerAttributes, SpecificChannels, Vec2, WritableImage};
|
||||
let row = w as usize * 4;
|
||||
let pixels = SpecificChannels::rgba(|p: Vec2<usize>| {
|
||||
let o = p.y() * row + p.x() * 4;
|
||||
(data[o], data[o + 1], data[o + 2], data[o + 3])
|
||||
});
|
||||
let layer = Layer::new((w as usize, h as usize), LayerAttributes::default(), Encoding::SMALL_LOSSLESS, pixels);
|
||||
exr::image::Image::from_layer(layer).write().to_buffered(file).map_err(|e| ExportError::Encode(e.to_string()))
|
||||
}
|
||||
OutputFormat::JpegSequence => {
|
||||
let px = cx.pixels(img, comp, if channels == Channels::Alpha { Channels::Alpha } else { Channels::Rgb }, w, h);
|
||||
|
||||
@@ -302,6 +302,22 @@ fn exr_sequence_is_linear() {
|
||||
assert!((c[2] - 0.1329).abs() < 0.01 && (c[3] - 1.0).abs() < 1e-6, "{c:?}");
|
||||
}
|
||||
|
||||
/// Sequence frames are scan-line EXR, not tiled, so scan-line readers such as FilmCraft open
|
||||
/// them (#2805).
|
||||
#[test]
|
||||
fn exr_sequence_writes_scan_lines() {
|
||||
let (p, cid) = project(None);
|
||||
let d = out_dir("seq-exr-scanline");
|
||||
let om = OutputModule::for_format(OutputFormat::ExrSequence);
|
||||
run(&p, cid, &NoFootage, &om, &d.join("f_[#####].exr"));
|
||||
let bytes = std::fs::read(d.join("f_00000.exr")).expect("exr");
|
||||
// Version field: 2, then the flags byte whose 0x02 bit marks a single-part tiled file.
|
||||
assert_eq!(&bytes[4..6], &[2, 0], "{:02x?}", &bytes[..8]);
|
||||
let meta = exr::meta::MetaData::read_from_buffered(std::io::Cursor::new(&bytes), false).expect("exr header");
|
||||
assert_eq!(meta.headers[0].blocks, exr::meta::BlockDescription::ScanLines);
|
||||
assert_eq!(meta.headers[0].compression, exr::compression::Compression::ZIP16);
|
||||
}
|
||||
|
||||
/// A 32 bpc project whose only layer is a full-frame opaque solid of the given colour.
|
||||
fn bright_project(color: [f32; 3]) -> (Project, ItemId) {
|
||||
let mut p = Project::default();
|
||||
|
||||
Reference in new issue
Block a user