fix(engine): propagate transparency setting to queued frame saves

This commit is contained in:
Kushagra Kumar committed 2026-10-25 19:31:46 +05:30
1 parent 77e2cf0554
commit 5d25346547
2 files changed
+27 -5

No files matched your search

+13 -4
View File
@@ -5,7 +5,7 @@ use effectcraft_project::Marker;
use effectcraft_time::Tick;
use serde_json::{Value, json};
use super::{CommandSpec, always, bad, comp_id, f_p, has_comp, has_layers, layers_p, str_p};
use super::{CommandSpec, always, b_p, bad, comp_id, f_p, has_comp, has_layers, layers_p, str_p};
use crate::prefs::CompPreset;
use crate::{EngineError, Result, Session, cmd, query};
@@ -82,7 +82,7 @@ pub(crate) fn encode_png(rgba: &[u8], w: u32, h: u32) -> std::result::Result<Vec
/// Save Frame As ▸ File with `queue`: a Render Queue item for the current frame, from the Frame
/// Default templates (Edit ▸ Templates).
fn queue_frame(s: &mut Session, p: &Value) -> Result<Value> {
use effectcraft_project::render_queue::{RenderQueueItem, TimeSpan};
use effectcraft_project::render_queue::{Channels, RenderQueueItem, TimeSpan};
use effectcraft_project::render_templates::TemplateSlot;
let cid = comp_id(s, p)?;
let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?;
@@ -91,6 +91,15 @@ fn queue_frame(s: &mut Session, p: &Value) -> Result<Value> {
it.settings = s.project.render_templates.default_render_settings(TemplateSlot::Still);
it.settings.time_span = TimeSpan::Custom { start: t, end: t + comp.frame_duration() };
it.output = s.project.render_templates.default_output_module(TemplateSlot::Still);
if let Some(transparent) = b_p(p, "transparent") {
if !transparent {
it.output.channels = Channels::Rgb;
} else if it.output.supports_alpha() {
it.output.channels = Channels::Rgba;
} else {
return Err(bad("comp.saveFrameAs", format!("{} doesn't support an alpha channel", it.output.format.label())));
}
}
if let Some(path) = str_p(p, "path") {
it.output.output = path.to_string();
} else if !it.output.format.is_sequence() {
@@ -107,7 +116,7 @@ fn queue_frame(s: &mut Session, p: &Value) -> Result<Value> {
}
fn save_frame(s: &mut Session, p: &Value) -> Result<Value> {
if p.get("queue").and_then(Value::as_bool) == Some(true) {
if b_p(p, "queue") == Some(true) {
return queue_frame(s, p);
}
let path = str_p(p, "path").ok_or_else(|| bad("comp.saveFrameAs", "missing `path` (.png)"))?.to_string();
@@ -117,7 +126,7 @@ fn save_frame(s: &mut Session, p: &Value) -> Result<Value> {
let scale = f_p(p, "scale").unwrap_or(1.0).clamp(0.01, 4.0);
let bg = comp.background;
let img = s.render(cid, t, effectcraft_render::RenderOpts { scale, backend: effectcraft_render::Backend::Auto, ..Default::default() });
let rgba = if p.get("transparent").and_then(Value::as_bool) == Some(false) { img.to_rgba8_over(bg) } else { img.to_rgba8() };
let rgba = if b_p(p, "transparent") == Some(false) { img.to_rgba8_over(bg) } else { img.to_rgba8() };
let png = encode_png(&rgba, img.width, img.height).map_err(|e| EngineError::Other(format!("PNG encoding failed: {e}")))?;
s.services.write_file(&path, &png).map_err(|e| EngineError::Other(format!("cannot write {path}: {e}")))?;
s.toast(format!("Saved frame to {path}"));
+14 -1
View File
@@ -194,11 +194,24 @@ fn save_frame_as_png_preserves_alpha_channel() {
fn save_frame_as_defaults_to_png_sequence_with_alpha() {
let mut s = Session::default();
s.execute("comp.new", json!({"name": "Comp", "width": 32, "height": 32, "frameRate": 24, "duration": 1})).unwrap();
let r = s.execute("comp.saveFrameAs", json!({"queue": true})).unwrap();
let r = s.execute_checked("comp.saveFrameAs", json!({"queue": true})).unwrap();
assert_eq!(r["queued"], true);
let it = s.project.render_queue.last().unwrap();
assert_eq!(it.output.name, "PNG Sequence with Alpha");
assert_eq!(it.output.channels, effectcraft_project::render_queue::Channels::Rgba);
// Queued save with transparent: false sets Channels::Rgb (composited over background).
s.execute_checked("comp.saveFrameAs", json!({"queue": true, "transparent": false})).unwrap();
let it = s.project.render_queue.last().unwrap();
assert_eq!(it.output.channels, effectcraft_project::render_queue::Channels::Rgb);
// Queued save with transparent: true preserves Channels::Rgba.
s.execute_checked("comp.saveFrameAs", json!({"queue": true, "transparent": true})).unwrap();
let it = s.project.render_queue.last().unwrap();
assert_eq!(it.output.channels, effectcraft_project::render_queue::Channels::Rgba);
s.execute("edit.undo", json!({})).unwrap();
s.execute("edit.undo", json!({})).unwrap();
s.execute("edit.undo", json!({})).unwrap();
assert!(s.project.render_queue.is_empty());
s.execute("edit.redo", json!({})).unwrap();