mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Merge pull request #618 from MarekSpodBiedry/issue-547
This commit is contained in:
3 files changed
+101
-6
No files matched your search
@@ -349,6 +349,14 @@ fn transformed(img: &egui::ColorImage, ch: Channel, colorized: bool, stops: f32,
|
|||||||
egui::ColorImage::new(img.size, px.into_iter().map(|a| Color32::from_rgba_premultiplied(a[0], a[1], a[2], a[3])).collect())
|
egui::ColorImage::new(img.size, px.into_iter().map(|a| Color32::from_rgba_premultiplied(a[0], a[1], a[2], a[3])).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The display conversion's inputs, for the texture caches.
|
||||||
|
pub(crate) fn display_key(app: &EffectcraftApp, has_conversion: bool) -> String {
|
||||||
|
let opts = &app.session.state.viewer;
|
||||||
|
let custom = if opts.simulation.profile == vw::SimProfile::MyCustom { format!("{:?}", app.session.prefs.custom_rgb) } else { String::new() };
|
||||||
|
format!("{:?}{:?}{}{custom}", has_conversion, opts.simulation, app.session.prefs.previews.display_profile)
|
||||||
|
+ &format!("{:?}{:?}{}", app.session.project.settings.working_space, app.session.project.settings.output_space, opts.display_color_management)
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the frame into `comp_rect`: the rendered frame (covering the region of interest when one
|
/// Draw the frame into `comp_rect`: the rendered frame (covering the region of interest when one
|
||||||
/// is set), or the snapshot while Show Snapshot is on / F5 is held, through Show Channel and the
|
/// is set), or the snapshot while Show Snapshot is on / F5 is held, through Show Channel and the
|
||||||
/// exposure. Fast Previews ▸ Wireframe draws layer outlines instead.
|
/// exposure. Fast Previews ▸ Wireframe draws layer outlines instead.
|
||||||
@@ -378,10 +386,7 @@ pub(crate) fn draw_frame(app: &mut EffectcraftApp, ctx: &egui::Context, painter:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let dc = vw::DisplayColor::of(&app.session);
|
let dc = vw::DisplayColor::of(&app.session);
|
||||||
// The display conversion's inputs, for the texture caches.
|
let dc_key = display_key(app, dc.is_some());
|
||||||
let custom = if opts.simulation.profile == vw::SimProfile::MyCustom { format!("{:?}", app.session.prefs.custom_rgb) } else { String::new() };
|
|
||||||
let dc_key = format!("{:?}{:?}{}{custom}", dc.is_some(), opts.simulation, app.session.prefs.previews.display_profile)
|
|
||||||
+ &format!("{:?}{:?}{}", app.session.project.settings.working_space, app.session.project.settings.output_space, opts.display_color_management);
|
|
||||||
let plain = opts.channel == Channel::Rgb && opts.exposure == 0.0 && dc.is_none();
|
let plain = opts.channel == Channel::Rgb && opts.exposure == 0.0 && dc.is_none();
|
||||||
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
||||||
if snap {
|
if snap {
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: u32, rect: Rect) {
|
|||||||
app.request_frame(cid, frame, scale);
|
app.request_frame(cid, frame, scale);
|
||||||
let key = app.frame_key(cid, frame, scale);
|
let key = app.frame_key(cid, frame, scale);
|
||||||
if let Some((key, img)) = app.frames.get_for_viewer(&key, app.passive_tex.get(&id).map(|(k, _)| k)) {
|
if let Some((key, img)) = app.frames.get_for_viewer(&key, app.passive_tex.get(&id).map(|(k, _)| k)) {
|
||||||
set_texture(app, &ctx, id, key, img);
|
let dc = effectcraft_engine::viewer::DisplayColor::of(&app.session);
|
||||||
|
let dkey = display_hash(&super::viewer_tools::display_key(app, dc.is_some()));
|
||||||
|
set_texture(app, &ctx, id, key, img, dc, dkey);
|
||||||
}
|
}
|
||||||
match app.passive_tex.get(&id).filter(|(k, _)| k.comp == cid.0) {
|
match app.passive_tex.get(&id).filter(|(k, _)| k.comp == cid.0) {
|
||||||
Some((_, tex)) => {
|
Some((_, tex)) => {
|
||||||
@@ -126,16 +128,38 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: u32, rect: Rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Keep the frame viewer `id` shows in its texture.
|
/// Keep the frame viewer `id` shows in its texture.
|
||||||
fn set_texture(app: &mut EffectcraftApp, ctx: &egui::Context, id: u32, key: FrameKey, img: FrameImage) {
|
fn set_texture(
|
||||||
|
app: &mut EffectcraftApp,
|
||||||
|
ctx: &egui::Context,
|
||||||
|
id: u32,
|
||||||
|
key: FrameKey,
|
||||||
|
img: FrameImage,
|
||||||
|
dc: Option<effectcraft_engine::viewer::DisplayColor>,
|
||||||
|
dkey: u64,
|
||||||
|
) {
|
||||||
|
let dc_id = egui::Id::new(("passive-viewer-display", id));
|
||||||
|
let same_display = ctx.data(|d| d.get_temp::<u64>(dc_id)) == Some(dkey);
|
||||||
if let Some((k, _)) = app.passive_tex.get_mut(&id)
|
if let Some((k, _)) = app.passive_tex.get_mut(&id)
|
||||||
&& *k == key
|
&& *k == key
|
||||||
|
&& same_display
|
||||||
{
|
{
|
||||||
// An undo or an edit elsewhere can reuse this content at a newer revision. Keep that
|
// An undo or an edit elsewhere can reuse this content at a newer revision. Keep that
|
||||||
// revision as the lower bound for subsequent intermediate edit frames.
|
// revision as the lower bound for subsequent intermediate edit frames.
|
||||||
k.revision = key.revision;
|
k.revision = key.revision;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ctx.data_mut(|d| d.insert_temp(dc_id, dkey));
|
||||||
let opts = super::viewer::zoom_texture_options(app.session.prefs.viewer_zoom_smooth());
|
let opts = super::viewer::zoom_texture_options(app.session.prefs.viewer_zoom_smooth());
|
||||||
|
// The monitor conversion the active viewer applies (Use Display Color Management).
|
||||||
|
let img = match (img, &dc) {
|
||||||
|
(FrameImage::Cpu(img), Some(dc)) => {
|
||||||
|
let mut px: Vec<[u8; 4]> = img.pixels.iter().map(|c| c.to_array()).collect();
|
||||||
|
dc.apply(&mut px);
|
||||||
|
let pixels = px.into_iter().map(|a| Color32::from_rgba_premultiplied(a[0], a[1], a[2], a[3])).collect();
|
||||||
|
FrameImage::Cpu(Arc::new(egui::ColorImage::new(img.size, pixels)))
|
||||||
|
}
|
||||||
|
(img, _) => img,
|
||||||
|
};
|
||||||
let tex = match (img, app.passive_tex.remove(&id)) {
|
let tex = match (img, app.passive_tex.remove(&id)) {
|
||||||
(FrameImage::Cpu(img), Some((_, PassiveTexture::Cpu(mut h))))
|
(FrameImage::Cpu(img), Some((_, PassiveTexture::Cpu(mut h))))
|
||||||
if h.size() == crate::frames::fitted_size(img.size, crate::frames::max_texture_side(ctx)) =>
|
if h.size() == crate::frames::fitted_size(img.size, crate::frames::max_texture_side(ctx)) =>
|
||||||
@@ -165,6 +189,14 @@ fn set_texture(app: &mut EffectcraftApp, ctx: &egui::Context, id: u32, key: Fram
|
|||||||
app.passive_tex.insert(id, (key, tex));
|
app.passive_tex.insert(id, (key, tex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A hash of the display conversion's inputs (see `viewer_tools::display_key`).
|
||||||
|
fn display_hash(key: &str) -> u64 {
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
key.hash(&mut h);
|
||||||
|
h.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Give a passive viewer's texture back (GPU textures are registered with the renderer).
|
/// Give a passive viewer's texture back (GPU textures are registered with the renderer).
|
||||||
fn free(app: &EffectcraftApp, tex: Option<PassiveTexture>) {
|
fn free(app: &EffectcraftApp, tex: Option<PassiveTexture>) {
|
||||||
if let (Some(PassiveTexture::Gpu(id, _)), Some(rs)) = (tex, &app.wgpu) {
|
if let (Some(PassiveTexture::Gpu(id, _)), Some(rs)) = (tex, &app.wgpu) {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//! A passive Composition viewer applies the same monitor conversion as the active one (#547).
|
||||||
|
use effectcraft_engine::Session;
|
||||||
|
use effectcraft_ui_egui::{EffectcraftApp, menus, panels::viewers};
|
||||||
|
use egui::{Color32, Context, ImageData, RawInput, Rect, pos2, vec2};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn frames(app: &mut EffectcraftApp, ctx: &Context, textures: &mut HashMap<String, Vec<Color32>>) {
|
||||||
|
for _ in 0..4 {
|
||||||
|
let mut output = ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(256.0, 256.0))), ..Default::default() }, |ui| {
|
||||||
|
viewers::show(app, ui, 0, Rect::from_min_size(pos2(0.0, 0.0), vec2(128.0, 256.0)));
|
||||||
|
viewers::show(app, ui, 1, Rect::from_min_size(pos2(128.0, 0.0), vec2(128.0, 256.0)));
|
||||||
|
});
|
||||||
|
for (&id, deltas) in &output.textures_delta.set {
|
||||||
|
let Some(name) = ctx.tex_manager().read().meta(id).map(|m| m.name.clone()) else { continue };
|
||||||
|
for delta in deltas {
|
||||||
|
let ImageData::Color(image) = &delta.image;
|
||||||
|
if delta.pos.is_none() {
|
||||||
|
textures.insert(name.clone(), image.pixels.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.textures_delta.clear();
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixel(textures: &HashMap<String, Vec<Color32>>, name: &str) -> [u8; 4] {
|
||||||
|
let p = &textures[name];
|
||||||
|
p[p.len() / 2].to_array()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn passive_viewer_matches_the_monitor_conversion() {
|
||||||
|
let mut s = Session::default();
|
||||||
|
s.execute("comp.new", json!({"name": "Display test", "width": 16, "height": 16, "duration": 1})).unwrap();
|
||||||
|
s.execute("layer.newSolid", json!({"name": "Color", "color": "#406080", "width": 16, "height": 16})).unwrap();
|
||||||
|
s.execute("edit.deselectAll", json!({})).unwrap();
|
||||||
|
let mut app = EffectcraftApp::new(s);
|
||||||
|
let ctx = Context::default();
|
||||||
|
menus::invoke(&mut app, &ctx, "view.newViewer", json!({})).unwrap();
|
||||||
|
let mut textures = HashMap::new();
|
||||||
|
app.session.execute("file.projectSettings", json!({"workingSpace": "rec2020"})).unwrap();
|
||||||
|
app.session.execute("prefs.set", json!({"key": "previews.displayProfile", "value": "p3"})).unwrap();
|
||||||
|
frames(&mut app, &ctx, &mut textures);
|
||||||
|
assert_eq!(pixel(&textures, "viewer-display"), [37, 89, 122, 255]);
|
||||||
|
assert_eq!(pixel(&textures, "viewer-0"), [37, 89, 122, 255]);
|
||||||
|
// Changing which viewer is active keeps the conversion in both.
|
||||||
|
viewers::activate(&mut app, &ctx, 0);
|
||||||
|
frames(&mut app, &ctx, &mut textures);
|
||||||
|
assert_eq!(pixel(&textures, "viewer-display"), [37, 89, 122, 255]);
|
||||||
|
assert_eq!(pixel(&textures, "viewer-1"), [37, 89, 122, 255]);
|
||||||
|
// No output-to-monitor conversion needed: both show the raw frame.
|
||||||
|
app.session.execute("prefs.set", json!({"key": "previews.displayProfile", "value": "srgb"})).unwrap();
|
||||||
|
frames(&mut app, &ctx, &mut textures);
|
||||||
|
assert_eq!(pixel(&textures, "viewer-frame"), [0, 90, 125, 255]);
|
||||||
|
assert_eq!(pixel(&textures, "viewer-1"), [0, 90, 125, 255]);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user