use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::export::PluginExport;
use crate::plugin::PluginRuntime;
pub const DEFAULT_SCREENSHOT_SCALE: f64 = 2.0;
static SCREENSHOT_SCALE_BITS: AtomicU64 = AtomicU64::new(0);
#[must_use]
pub fn override_scale() -> Option<f64> {
let bits = SCREENSHOT_SCALE_BITS.load(Ordering::Relaxed);
if bits == 0 {
return None;
}
let v = f64::from_bits(bits);
(v.is_finite() && v > 0.0).then_some(v)
}
struct ScreenshotScaleGuard;
impl ScreenshotScaleGuard {
fn set(scale: f64) -> Self {
let bits = if scale.is_finite() && scale > 0.0 {
scale.to_bits()
} else {
0
};
SCREENSHOT_SCALE_BITS.store(bits, Ordering::Relaxed);
Self
}
}
impl Drop for ScreenshotScaleGuard {
fn drop(&mut self) {
SCREENSHOT_SCALE_BITS.store(0, Ordering::Relaxed);
}
}
#[must_use]
pub fn render_pixels<P: PluginExport>() -> (Vec<u8>, u32, u32) {
let mut plugin = P::create();
plugin.init();
render_pixels_for::<P>(&mut plugin)
}
#[must_use]
pub fn render_with_state<P: PluginExport>(state: Option<&[u8]>) -> (Vec<u8>, u32, u32) {
render_with_state_at_scale::<P>(state, DEFAULT_SCREENSHOT_SCALE)
}
#[must_use]
pub fn render_with_state_at_scale<P: PluginExport>(
state: Option<&[u8]>,
scale: f64,
) -> (Vec<u8>, u32, u32) {
let mut plugin = P::create();
plugin.init();
if let Some(bytes) = state
&& let Err(e) = plugin.load_state(bytes)
{
eprintln!("truce: screenshot load_state failed: {e}");
}
render_pixels_for_at_scale::<P>(&mut plugin, scale)
}
pub fn render_pixels_for<P: PluginExport>(plugin: &mut P) -> (Vec<u8>, u32, u32) {
render_pixels_for_at_scale::<P>(plugin, DEFAULT_SCREENSHOT_SCALE)
}
pub fn render_pixels_for_at_scale<P: PluginExport>(
plugin: &mut P,
scale: f64,
) -> (Vec<u8>, u32, u32) {
let _scale_guard = ScreenshotScaleGuard::set(scale);
let mut editor = <P as PluginRuntime>::editor(plugin).unwrap_or_else(|| {
panic!(
"plugin {} returned no editor: PluginRuntime::editor() returned None. \
Implement `fn editor(&mut self)` on your plugin (or one of \
the built-in editor wrappers - truce-gpu / truce-egui / \
truce-iced / truce-slint) so screenshot rendering has \
something to draw.",
std::any::type_name::<P>()
)
});
let params: Arc<dyn truce_params::Params> = plugin.params_arc();
editor.screenshot(params).unwrap_or_else(|| {
panic!(
"editor for {} returned None from Editor::screenshot(). \
If this is a custom editor implementation, override \
Editor::screenshot() to return RGBA pixels. Built-in \
backends (truce-gpu / truce-egui / truce-iced / \
truce-slint) all implement it.",
std::any::type_name::<P>()
)
})
}
#[must_use]
pub fn load_png(path: &Path) -> (Vec<u8>, u32, u32) {
let file = std::fs::File::open(path)
.unwrap_or_else(|e| panic!("Failed to open {}: {e}", path.display()));
let decoder = png::Decoder::new(std::io::BufReader::new(file));
let mut reader = decoder
.read_info()
.unwrap_or_else(|e| panic!("Failed to read PNG info: {e}"));
let info = reader.info();
let buf_size = reader
.output_buffer_size()
.unwrap_or_else(|| (info.width as usize) * (info.height as usize) * 4);
let mut buf = vec![0u8; buf_size];
let info = reader
.next_frame(&mut buf)
.unwrap_or_else(|e| panic!("Failed to decode PNG frame: {e}"));
buf.truncate(info.buffer_size());
(buf, info.width, info.height)
}
pub fn save_png(path: &Path, pixels: &[u8], w: u32, h: u32) {
let file = std::fs::File::create(path)
.unwrap_or_else(|e| panic!("Failed to create {}: {e}", path.display()));
let mut encoder = png::Encoder::new(file, w, h);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
encoder.set_pixel_dims(Some(png::PixelDimensions {
xppu: 5669, yppu: 5669,
unit: png::Unit::Meter,
}));
let mut writer = encoder
.write_header()
.unwrap_or_else(|e| panic!("Failed to write PNG header: {e}"));
writer
.write_image_data(pixels)
.unwrap_or_else(|e| panic!("Failed to write PNG data: {e}"));
}