Render live preview texture at native capture resolution and persist settings to settings.json
This commit is contained in:
3 files changed
+345
-46
No files matched your search
@@ -236,14 +236,108 @@ impl Insta360App {
|
||||
if let Ok(ae) = self.controller.get_auto_exposure() {
|
||||
self.state.auto_exposure = ae;
|
||||
}
|
||||
if let Ok(ev) = self.controller.get_exposure_compensation() {
|
||||
self.state.ev_compensation = ev.clamp(-3.0_f32, 3.0_f32);
|
||||
}
|
||||
if let Ok(iso) = self.controller.get_iso() {
|
||||
if let Some(idx) = crate::state::ISO_LIST.iter().position(|&v| v == iso) {
|
||||
self.state.iso_index = idx;
|
||||
}
|
||||
}
|
||||
if let Ok(shutter) = self.controller.get_shutter_speed() {
|
||||
if let Some(idx) = crate::state::SHUTTER_DENOM_LIST
|
||||
.iter()
|
||||
.position(|&v| v == shutter)
|
||||
{
|
||||
self.state.shutter_index = idx;
|
||||
}
|
||||
}
|
||||
if let Ok(af) = self.controller.get_auto_focus() {
|
||||
self.state.auto_focus = af;
|
||||
}
|
||||
if let Ok(focus) = self.controller.get_focus() {
|
||||
self.state.manual_focus = focus.min(100);
|
||||
}
|
||||
if let Ok(pl) = self.controller.get_power_line_frequency() {
|
||||
self.state.anti_flicker = pl;
|
||||
}
|
||||
if let Ok(roll) = self.controller.get_roll() {
|
||||
self.state.roll_deg = (roll as f32 / 10.0_f32).clamp(-1.0_f32, 1.0_f32);
|
||||
}
|
||||
if let Ok(denoise) = self.controller.get_audio_noise_reduction() {
|
||||
self.state.audio_denoise = denoise;
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_saved_settings_to_hardware(&mut self) {
|
||||
let Some(saved) = self.state.last_saved_settings.clone() else {
|
||||
self.state.last_saved_settings = Some(self.state.to_persisted());
|
||||
return;
|
||||
};
|
||||
self.state.apply_persisted(&saved);
|
||||
let _ = self.controller.set_brightness(self.state.brightness);
|
||||
let _ = self.controller.set_contrast(self.state.contrast);
|
||||
let _ = self.controller.set_saturation(self.state.saturation);
|
||||
let _ = self.controller.set_sharpness(self.state.sharpness);
|
||||
let _ = self
|
||||
.controller
|
||||
.set_auto_white_balance(self.state.auto_white_balance);
|
||||
if !self.state.auto_white_balance {
|
||||
let _ = self
|
||||
.controller
|
||||
.set_white_balance_temperature(self.state.wb_kelvin);
|
||||
}
|
||||
let _ = self.controller.set_auto_focus(self.state.auto_focus);
|
||||
if !self.state.auto_focus {
|
||||
let _ = self.controller.set_focus(self.state.manual_focus);
|
||||
}
|
||||
let _ = self
|
||||
.controller
|
||||
.set_power_line_frequency(self.state.anti_flicker);
|
||||
let _ = self.controller.set_hdr(self.state.hdr_enabled);
|
||||
if !self.state.hdr_enabled {
|
||||
let _ = self.controller.set_auto_exposure(self.state.auto_exposure);
|
||||
if self.state.auto_exposure {
|
||||
let _ = self
|
||||
.controller
|
||||
.set_exposure_compensation(self.state.ev_compensation);
|
||||
} else {
|
||||
if let Some(&iso) = crate::state::ISO_LIST.get(self.state.iso_index) {
|
||||
let _ = self.controller.set_iso(iso);
|
||||
}
|
||||
if let Some(&shutter) = crate::state::SHUTTER_DENOM_LIST.get(self.state.shutter_index)
|
||||
{
|
||||
let _ = self.controller.set_shutter_speed(shutter);
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = self.controller.set_track_speed(self.state.track_speed);
|
||||
let _ = self
|
||||
.controller
|
||||
.set_single_tap_tracking_enabled(self.state.enable_single_tap_tracking);
|
||||
let _ = self
|
||||
.controller
|
||||
.set_all_gestures_enabled(self.state.enable_all_gestures);
|
||||
let _ = self.controller.set_gesture_enabled(
|
||||
insta360_link_protocol::Gesture::Palm,
|
||||
self.state.use_gesture_palm,
|
||||
);
|
||||
let _ = self.controller.set_gesture_enabled(
|
||||
insta360_link_protocol::Gesture::VShape,
|
||||
self.state.use_gesture_v,
|
||||
);
|
||||
let _ = self.controller.set_gesture_enabled(
|
||||
insta360_link_protocol::Gesture::LShape,
|
||||
self.state.use_gesture_l,
|
||||
);
|
||||
let _ = self
|
||||
.controller
|
||||
.set_audio_noise_reduction(self.state.audio_denoise);
|
||||
let _ = self
|
||||
.controller
|
||||
.set_horizontal_mirror(self.state.horizontal_mirror);
|
||||
}
|
||||
|
||||
fn set_video_mode(&mut self, target_mode: VideoMode) {
|
||||
let prev_mode = self.state.video_mode;
|
||||
let mode_to_set = if self.state.video_mode == target_mode {
|
||||
@@ -2007,7 +2101,13 @@ impl eframe::App for Insta360App {
|
||||
// 1. One-time hardware initialization once the physical video stream is open and delivering frames
|
||||
if self.video_engine.has_live_physical_frame() && !self.state.stream_initialized {
|
||||
self.state.stream_initialized = true;
|
||||
let had_saved_settings = self.state.last_saved_settings.is_some();
|
||||
self.sync_device_info();
|
||||
if had_saved_settings && !self.self_test_enabled {
|
||||
self.apply_saved_settings_to_hardware();
|
||||
} else {
|
||||
self.state.last_saved_settings = Some(self.state.to_persisted());
|
||||
}
|
||||
// If the camera was left in Overhead or DeskView mode (which flips the ISP 180 degrees),
|
||||
// restore Normal mode so the live video stream is right-side up.
|
||||
if self.state.video_mode == VideoMode::OverheadShot
|
||||
@@ -2020,6 +2120,15 @@ impl eframe::App for Insta360App {
|
||||
}
|
||||
}
|
||||
|
||||
// 1b. Persist user settings to ~/.config/insta360-link-controller/settings.json whenever modified
|
||||
if self.state.stream_initialized && !self.self_test_enabled {
|
||||
let cur_persisted = self.state.to_persisted();
|
||||
if self.state.last_saved_settings.as_ref() != Some(&cur_persisted) {
|
||||
cur_persisted.save();
|
||||
self.state.last_saved_settings = Some(cur_persisted);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Auto-stop short PTZ directional nudges (`CameraInsta::addPanTilt` 100-120ms timer)
|
||||
if let Some(stop_at) = self.state.nudge_stop_at {
|
||||
if Instant::now() >= stop_at {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use egui::{ColorImage, Pos2, TextureHandle};
|
||||
pub use insta360_link_protocol::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -52,7 +53,7 @@ pub enum BackgroundEffectMode {
|
||||
}
|
||||
|
||||
/// Resolution & frame rate preset (`DefFormatCombo.qml`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum VideoFormatPreset {
|
||||
Uhd4k30,
|
||||
Fhd1080p60,
|
||||
@@ -281,6 +282,81 @@ pub struct CameraAppState {
|
||||
pub ready_auto_track: bool,
|
||||
pub last_track_poll: Option<Instant>,
|
||||
pub last_hw_sync: Option<Instant>,
|
||||
pub last_saved_settings: Option<PersistedAppSettings>,
|
||||
}
|
||||
|
||||
/// User settings persisted across sessions in `~/.config/insta360-link-controller/settings.json`.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PersistedAppSettings {
|
||||
pub format_preset: VideoFormatPreset,
|
||||
pub auto_exposure: bool,
|
||||
pub ev_compensation: f32,
|
||||
pub iso_index: usize,
|
||||
pub shutter_index: usize,
|
||||
pub auto_focus: bool,
|
||||
pub manual_focus: u16,
|
||||
pub auto_white_balance: bool,
|
||||
pub wb_kelvin: u16,
|
||||
pub brightness: i16,
|
||||
pub contrast: u16,
|
||||
pub saturation: u16,
|
||||
pub sharpness: u16,
|
||||
pub hdr_enabled: bool,
|
||||
pub anti_flicker: PowerLineFrequency,
|
||||
pub startup_params_enabled: bool,
|
||||
pub filter_mode: FilterMode,
|
||||
pub makeup_enabled: bool,
|
||||
pub makeup_intensity: u8,
|
||||
pub track_speed: TrackSpeed,
|
||||
pub enable_single_tap_tracking: bool,
|
||||
pub enable_all_gestures: bool,
|
||||
pub use_gesture_palm: bool,
|
||||
pub use_gesture_v: bool,
|
||||
pub use_gesture_l: bool,
|
||||
pub audio_denoise: bool,
|
||||
pub horizontal_mirror: bool,
|
||||
pub vertical_mirror: bool,
|
||||
pub smart_adjustment: bool,
|
||||
pub horizontal_correction: bool,
|
||||
pub roll_deg: f32,
|
||||
pub vertical_resolution_mode: bool,
|
||||
pub forced_vertical_lock: bool,
|
||||
pub low_resolution_mode: bool,
|
||||
}
|
||||
|
||||
impl PersistedAppSettings {
|
||||
pub fn config_path() -> PathBuf {
|
||||
if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
|
||||
if !xdg.trim().is_empty() {
|
||||
return PathBuf::from(xdg)
|
||||
.join("insta360-link-controller")
|
||||
.join("settings.json");
|
||||
}
|
||||
}
|
||||
let home = std::env::var("HOME")
|
||||
.or_else(|_| std::env::var("USERPROFILE"))
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("."));
|
||||
home.join(".config")
|
||||
.join("insta360-link-controller")
|
||||
.join("settings.json")
|
||||
}
|
||||
|
||||
pub fn load() -> Option<Self> {
|
||||
let path = Self::config_path();
|
||||
let data = std::fs::read_to_string(path).ok()?;
|
||||
serde_json::from_str(&data).ok()
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
let path = Self::config_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
if let Ok(json) = serde_json::to_string_pretty(self) {
|
||||
let _ = std::fs::write(path, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CameraAppState {
|
||||
@@ -328,7 +404,7 @@ impl Default for CameraAppState {
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("."));
|
||||
|
||||
Self {
|
||||
let mut s = Self {
|
||||
is_light_mode: false,
|
||||
ui_mode: UiMode::MainWindow,
|
||||
right_tab: RightSidebarTab::View,
|
||||
@@ -489,7 +565,13 @@ impl Default for CameraAppState {
|
||||
ready_auto_track: false,
|
||||
last_track_poll: None,
|
||||
last_hw_sync: None,
|
||||
last_saved_settings: None,
|
||||
};
|
||||
if let Some(saved) = PersistedAppSettings::load() {
|
||||
s.apply_persisted(&saved);
|
||||
s.last_saved_settings = Some(saved);
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,4 +579,80 @@ impl CameraAppState {
|
||||
pub fn show_toast(&mut self, msg: impl Into<String>, is_error: bool) {
|
||||
self.toast_message = Some((msg.into(), Instant::now(), is_error));
|
||||
}
|
||||
|
||||
pub fn to_persisted(&self) -> PersistedAppSettings {
|
||||
PersistedAppSettings {
|
||||
format_preset: self.format_preset,
|
||||
auto_exposure: self.auto_exposure,
|
||||
ev_compensation: (self.ev_compensation * 10.0).round() / 10.0,
|
||||
iso_index: self.iso_index,
|
||||
shutter_index: self.shutter_index,
|
||||
auto_focus: self.auto_focus,
|
||||
manual_focus: self.manual_focus,
|
||||
auto_white_balance: self.auto_white_balance,
|
||||
wb_kelvin: self.wb_kelvin,
|
||||
brightness: self.brightness,
|
||||
contrast: self.contrast,
|
||||
saturation: self.saturation,
|
||||
sharpness: self.sharpness,
|
||||
hdr_enabled: self.hdr_enabled,
|
||||
anti_flicker: self.anti_flicker,
|
||||
startup_params_enabled: self.startup_params_enabled,
|
||||
filter_mode: self.filter_mode,
|
||||
makeup_enabled: self.makeup_enabled,
|
||||
makeup_intensity: self.makeup_intensity,
|
||||
track_speed: self.track_speed,
|
||||
enable_single_tap_tracking: self.enable_single_tap_tracking,
|
||||
enable_all_gestures: self.enable_all_gestures,
|
||||
use_gesture_palm: self.use_gesture_palm,
|
||||
use_gesture_v: self.use_gesture_v,
|
||||
use_gesture_l: self.use_gesture_l,
|
||||
audio_denoise: self.audio_denoise,
|
||||
horizontal_mirror: self.horizontal_mirror,
|
||||
vertical_mirror: self.vertical_mirror,
|
||||
smart_adjustment: self.smart_adjustment,
|
||||
horizontal_correction: self.horizontal_correction,
|
||||
roll_deg: (self.roll_deg * 10.0).round() / 10.0,
|
||||
vertical_resolution_mode: self.vertical_resolution_mode,
|
||||
forced_vertical_lock: self.forced_vertical_lock,
|
||||
low_resolution_mode: self.low_resolution_mode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_persisted(&mut self, p: &PersistedAppSettings) {
|
||||
self.format_preset = p.format_preset;
|
||||
self.auto_exposure = p.auto_exposure;
|
||||
self.ev_compensation = p.ev_compensation;
|
||||
self.iso_index = p.iso_index.min(ISO_LIST.len() - 1);
|
||||
self.shutter_index = p.shutter_index.min(SHUTTER_DENOM_LIST.len() - 1);
|
||||
self.auto_focus = p.auto_focus;
|
||||
self.manual_focus = p.manual_focus.min(100);
|
||||
self.auto_white_balance = p.auto_white_balance;
|
||||
self.wb_kelvin = p.wb_kelvin.clamp(2000, 10000);
|
||||
self.brightness = p.brightness.clamp(0, 100);
|
||||
self.contrast = p.contrast.min(100);
|
||||
self.saturation = p.saturation.min(100);
|
||||
self.sharpness = p.sharpness.min(100);
|
||||
self.hdr_enabled = p.hdr_enabled;
|
||||
self.anti_flicker = p.anti_flicker;
|
||||
self.startup_params_enabled = p.startup_params_enabled;
|
||||
self.filter_mode = p.filter_mode;
|
||||
self.makeup_enabled = p.makeup_enabled;
|
||||
self.makeup_intensity = p.makeup_intensity.min(100);
|
||||
self.track_speed = p.track_speed;
|
||||
self.enable_single_tap_tracking = p.enable_single_tap_tracking;
|
||||
self.enable_all_gestures = p.enable_all_gestures;
|
||||
self.use_gesture_palm = p.use_gesture_palm;
|
||||
self.use_gesture_v = p.use_gesture_v;
|
||||
self.use_gesture_l = p.use_gesture_l;
|
||||
self.audio_denoise = p.audio_denoise;
|
||||
self.horizontal_mirror = p.horizontal_mirror;
|
||||
self.vertical_mirror = p.vertical_mirror;
|
||||
self.smart_adjustment = p.smart_adjustment;
|
||||
self.horizontal_correction = p.horizontal_correction;
|
||||
self.roll_deg = p.roll_deg.clamp(-1.0, 1.0);
|
||||
self.vertical_resolution_mode = p.vertical_resolution_mode;
|
||||
self.forced_vertical_lock = p.forced_vertical_lock;
|
||||
self.low_resolution_mode = p.low_resolution_mode;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use insta360_link_protocol::{TrackSpeed, UvcRect, VideoMode};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
@@ -60,6 +60,9 @@ pub struct VideoPreviewEngine {
|
||||
capture_stop: Arc<AtomicBool>,
|
||||
capture_child: Arc<Mutex<Option<Child>>>,
|
||||
capture_frame: Arc<Mutex<Option<(usize, usize, Vec<u8>)>>>,
|
||||
frame_seq: Arc<AtomicU64>,
|
||||
last_rendered_seq: u64,
|
||||
last_effects_key: (FilterMode, bool, u8),
|
||||
recording_stdin: Arc<Mutex<Option<(ChildStdin, usize, usize)>>>,
|
||||
recording_child: Option<Child>,
|
||||
recording_path: Option<PathBuf>,
|
||||
@@ -97,6 +100,9 @@ impl VideoPreviewEngine {
|
||||
capture_stop: Arc::new(AtomicBool::new(false)),
|
||||
capture_child: Arc::new(Mutex::new(None)),
|
||||
capture_frame: Arc::new(Mutex::new(None)),
|
||||
frame_seq: Arc::new(AtomicU64::new(0)),
|
||||
last_rendered_seq: 0,
|
||||
last_effects_key: (FilterMode::None, false, 0),
|
||||
recording_stdin: Arc::new(Mutex::new(None)),
|
||||
recording_child: None,
|
||||
recording_path: None,
|
||||
@@ -283,6 +289,7 @@ impl VideoPreviewEngine {
|
||||
|
||||
let stop_flag = self.capture_stop.clone();
|
||||
let frame_buf = self.capture_frame.clone();
|
||||
let frame_seq = self.frame_seq.clone();
|
||||
let child_store = self.capture_child.clone();
|
||||
let rec_stdin = self.recording_stdin.clone();
|
||||
let out_w = self.capture_width;
|
||||
@@ -438,6 +445,7 @@ impl VideoPreviewEngine {
|
||||
}
|
||||
if let Ok(mut lock) = frame_buf.lock() {
|
||||
*lock = Some((out_w, out_h, buffer.clone()));
|
||||
frame_seq.fetch_add(1, Ordering::Release);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
@@ -466,15 +474,75 @@ impl VideoPreviewEngine {
|
||||
self.active_device = None;
|
||||
}
|
||||
|
||||
/// Renders a full frame (either physical or virtual studio), processes all real-time
|
||||
/// filters/effects, uploads to egui GPU texture, and returns the texture handle.
|
||||
/// Renders a full frame (either physical or virtual studio) at the selected capture resolution,
|
||||
/// processes real-time filters/effects, uploads to egui GPU texture, and returns the texture handle.
|
||||
pub fn render_frame(&mut self, ctx: &Context, state: &mut CameraAppState) -> TextureHandle {
|
||||
let now = Instant::now();
|
||||
let dt = (now - self.last_frame_time).as_secs_f32().clamp(0.001_f32, 0.1_f32);
|
||||
self.last_frame_time = now;
|
||||
let elapsed = self.start_time.elapsed().as_secs_f32();
|
||||
|
||||
// 1. Output dimensions
|
||||
let cur_seq = self.frame_seq.load(Ordering::Acquire);
|
||||
let effects_key = (state.filter_mode, state.makeup_enabled, state.makeup_intensity);
|
||||
|
||||
// Fast-path: if physical capture is active and neither the frame nor host effects changed,
|
||||
// return the existing GPU texture handle without re-converting or re-uploading.
|
||||
if self.is_using_physical_capture()
|
||||
&& cur_seq > 0
|
||||
&& cur_seq == self.last_rendered_seq
|
||||
&& effects_key == self.last_effects_key
|
||||
{
|
||||
if let Some(ref th) = self.texture_handle {
|
||||
return th.clone();
|
||||
}
|
||||
}
|
||||
|
||||
let physical_opt = self.capture_frame.lock().ok().and_then(|guard| guard.clone());
|
||||
let is_physical_active = self.is_using_physical_capture() && physical_opt.is_some();
|
||||
let is_insta360_hw_stream = self
|
||||
.active_device
|
||||
.as_deref()
|
||||
.map(|d| d.to_lowercase().contains("insta360"))
|
||||
.unwrap_or(false);
|
||||
|
||||
let color_image = if is_physical_active {
|
||||
let (src_w, src_h, raw_rgb) = physical_opt.unwrap();
|
||||
self.last_rendered_seq = cur_seq;
|
||||
self.last_effects_key = effects_key;
|
||||
|
||||
if is_insta360_hw_stream && !state.makeup_enabled && state.filter_mode == FilterMode::None {
|
||||
// Zero-downscale native resolution fast path (360p, 720p, 1080p, 4K)
|
||||
ColorImage::from_rgb([src_w, src_h], &raw_rgb)
|
||||
} else {
|
||||
let (out_w, out_h) = if src_w > 1920 {
|
||||
(1920, (src_h * 1920) / src_w.max(1))
|
||||
} else {
|
||||
(src_w, src_h)
|
||||
};
|
||||
let mut frame_pixels = self.crop_physical_frame(
|
||||
&raw_rgb,
|
||||
src_w,
|
||||
src_h,
|
||||
out_w,
|
||||
out_h,
|
||||
state,
|
||||
is_insta360_hw_stream,
|
||||
);
|
||||
if is_insta360_hw_stream {
|
||||
self.apply_host_effects_only(&mut frame_pixels, state);
|
||||
} else {
|
||||
self.apply_image_pipeline(&mut frame_pixels, out_w, out_h, state);
|
||||
}
|
||||
let mut pixels = Vec::with_capacity(out_w * out_h);
|
||||
for p in frame_pixels {
|
||||
pixels.push(p.to_color32());
|
||||
}
|
||||
ColorImage {
|
||||
size: [out_w, out_h],
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (out_w, out_h) = if state.low_resolution_mode {
|
||||
if state.vertical_resolution_mode {
|
||||
(270, 480)
|
||||
@@ -486,47 +554,12 @@ impl VideoPreviewEngine {
|
||||
} else {
|
||||
(640, 360)
|
||||
};
|
||||
|
||||
// 2. Obtain raw RGB frame
|
||||
let physical_opt = self.capture_frame.lock().ok().and_then(|guard| guard.clone());
|
||||
let is_physical_active = self.is_using_physical_capture() && physical_opt.is_some();
|
||||
let is_insta360_hw_stream = self
|
||||
.active_device
|
||||
.as_deref()
|
||||
.map(|d| d.to_lowercase().contains("insta360"))
|
||||
.unwrap_or(false);
|
||||
|
||||
let mut frame_pixels = if is_physical_active {
|
||||
let (src_w, src_h, ref raw_rgb) = physical_opt.unwrap();
|
||||
self.crop_physical_frame(
|
||||
raw_rgb,
|
||||
src_w,
|
||||
src_h,
|
||||
out_w,
|
||||
out_h,
|
||||
state,
|
||||
is_insta360_hw_stream,
|
||||
)
|
||||
} else {
|
||||
self.render_virtual_studio_frame(out_w, out_h, elapsed, dt, state)
|
||||
};
|
||||
|
||||
// 3. Real-Time Image & Color Pipeline
|
||||
if is_physical_active && is_insta360_hw_stream {
|
||||
// Physical Insta360 camera applies PTZ, zoom, exposure, WB, brightness, contrast,
|
||||
// saturation, sharpness, and mirror in hardware; apply host-side effects (filters, makeup).
|
||||
self.apply_host_effects_only(&mut frame_pixels, state);
|
||||
} else {
|
||||
let mut frame_pixels = self.render_virtual_studio_frame(out_w, out_h, elapsed, dt, state);
|
||||
self.apply_image_pipeline(&mut frame_pixels, out_w, out_h, state);
|
||||
}
|
||||
|
||||
// 4. Convert to egui::ColorImage
|
||||
let mut pixels = Vec::with_capacity(out_w * out_h);
|
||||
for p in frame_pixels {
|
||||
pixels.push(p.to_color32());
|
||||
}
|
||||
|
||||
if !is_physical_active {
|
||||
if let Ok(mut rec_lock) = self.recording_stdin.lock() {
|
||||
if let Some((ref mut stdin, rw, rh)) = *rec_lock {
|
||||
let mut rgb24 = Vec::with_capacity(rw * rh * 3);
|
||||
@@ -543,14 +576,13 @@ impl VideoPreviewEngine {
|
||||
let _ = stdin.write_all(&rgb24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let color_image = ColorImage {
|
||||
ColorImage {
|
||||
size: [out_w, out_h],
|
||||
pixels,
|
||||
}
|
||||
};
|
||||
|
||||
// 5. Upload to TextureHandle
|
||||
// Upload to TextureHandle
|
||||
let texture_options = TextureOptions::LINEAR;
|
||||
let handle = if let Some(ref mut th) = self.texture_handle {
|
||||
th.set(color_image.clone(), texture_options);
|
||||
|
||||
Reference in new issue
Block a user