Improve screen scaling behavior

This commit is contained in:
Ebu
2025-12-03 12:37:38 +01:00
parent d8e9e12515
commit 3cf22b7189
8 changed files with 451 additions and 199 deletions

View File

@@ -4,4 +4,6 @@ version = "0.1.0"
edition = "2024"
[dependencies]
femtovg = "0.19.3"
image = { version = "0.25.9", default-features = false, features = ["png"] }
nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", default-features = false }

187
ebu-dsp/src/gui/mod.rs Normal file
View File

@@ -0,0 +1,187 @@
use std::sync::{Arc, atomic::Ordering};
use femtovg::{Canvas, ImageFlags, ImageId, Paint, Path, renderer::OpenGl};
use nih_plug::prelude::AtomicF32;
use crate::{Rect, ScaledRect};
pub struct SpriteSheet {
scale_factor: Arc<AtomicF32>,
image: Result<ImageId, String>,
width: usize,
height: usize,
frame_width: usize,
frame_height: usize,
frames_x: usize,
}
impl SpriteSheet {
pub fn empty() -> Self {
Self {
scale_factor: Arc::new(AtomicF32::new(1.0)),
image: Err("No image loaded".to_owned()),
width: 0,
height: 0,
frame_width: 0,
frame_height: 0,
frames_x: 0,
}
}
pub fn new(
canvas: &mut Canvas<OpenGl>,
data: &[u8],
scale_factor: Arc<AtomicF32>,
frame_width: usize,
frame_height: usize,
frames: usize,
) -> Self {
let image = canvas
.load_image_mem(data, ImageFlags::empty())
.map_err(|e| format!("{e:?}"));
if let Ok(image) = image {
let (width, height) = canvas.image_size(image).unwrap_or_default();
Self {
scale_factor,
image: Ok(image),
width,
height,
frame_width,
frame_height,
frames_x: width / frame_width,
}
} else {
Self {
scale_factor,
image,
width: 0,
height: 0,
frame_width,
frame_height,
frames_x: 0,
}
}
}
pub fn draw(&self, canvas: &mut Canvas<OpenGl>, x: f32, y: f32, frame: usize) {
let factor = self.scale_factor.load(Ordering::Relaxed);
let frame_x = frame % self.frames_x;
let frame_y = frame / self.frames_x;
let screen_rect = Rect {
x: x * factor,
y: y * factor,
width: self.frame_width as f32 * factor,
height: self.frame_height as f32 * factor,
};
let image_rect = Rect {
x: screen_rect.x - (frame_x * self.frame_width) as f32 * factor,
y: screen_rect.y - (frame_y * self.frame_height) as f32 * factor,
width: self.width as f32 * factor,
height: self.height as f32 * factor,
};
let mut screen_path = Path::new();
screen_path.rect(
screen_rect.x,
screen_rect.y,
screen_rect.width,
screen_rect.height,
);
if let Ok(image) = self.image {
canvas.fill_path(
&screen_path,
&Paint::image(
image,
image_rect.x,
image_rect.y,
image_rect.width,
image_rect.height,
0.0,
1.0,
),
);
}
}
pub fn screen_bounds(&self, x: f32, y: f32) -> ScaledRect {
ScaledRect::new_from(
self.scale_factor.clone(),
(x, y, self.frame_width as f32, self.frame_height as f32),
)
}
}
pub struct Sprite {
scale_factor: Arc<AtomicF32>,
image: Result<ImageId, String>,
width: usize,
height: usize,
}
impl Sprite {
pub fn empty() -> Self {
Self {
scale_factor: Arc::new(AtomicF32::new(1.0)),
image: Err("No image loaded".to_owned()),
width: 0,
height: 0,
}
}
pub fn new(canvas: &mut Canvas<OpenGl>, data: &[u8], scale_factor: Arc<AtomicF32>) -> Self {
let image = canvas
.load_image_mem(data, ImageFlags::empty())
.map_err(|e| format!("{e:?}"));
if let Ok(image) = image {
let (width, height) = canvas.image_size(image).unwrap_or_default();
Self {
scale_factor,
image: Ok(image),
width,
height,
}
} else {
Self {
scale_factor,
image,
width: 0,
height: 0,
}
}
}
pub fn draw(&self, canvas: &mut Canvas<OpenGl>, x: f32, y: f32, alpha: f32) {
let factor = self.scale_factor.load(Ordering::Relaxed);
let screen_rect = Rect {
x: x * factor,
y: y * factor,
width: self.width as f32 * factor,
height: self.height as f32 * factor,
};
let mut screen_path = Path::new();
screen_path.rect(
screen_rect.x,
screen_rect.y,
screen_rect.width,
screen_rect.height,
);
if let Ok(image) = self.image {
canvas.fill_path(
&screen_path,
&Paint::image(
image,
screen_rect.x,
screen_rect.y,
screen_rect.width,
screen_rect.height,
0.0,
alpha,
),
);
}
}
pub fn screen_bounds(&self, x: f32, y: f32) -> ScaledRect {
ScaledRect::new_from(
self.scale_factor.clone(),
(x, y, self.width as f32, self.height as f32),
)
}
}

View File

@@ -4,13 +4,16 @@ mod comp;
mod decibel;
mod eq;
mod freq_split;
mod gui;
mod meter;
mod ring_buffer;
mod smoother;
mod traits;
use std::{
fmt::Debug, ops::Add, time::Duration
fmt::Debug,
sync::{Arc, atomic::Ordering},
time::Duration,
};
pub use amplitude::Amplitude;
@@ -19,42 +22,159 @@ pub use comp::{Compressor, CompressorState};
pub use decibel::Decibel;
pub use eq::{Equalizer, EqualizerState};
pub use freq_split::{FreqSplitter, FreqSplitterState};
pub use gui::{SpriteSheet, Sprite};
use nih_plug::prelude::AtomicF32;
pub use traits::{FloatFormatter, IntFormatter, Lerp, Processor};
pub struct Rect<T> {
pub x: T,
pub y: T,
pub width: T,
pub height: T,
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Point {
x: f32,
y: f32,
}
impl<T: Debug> Debug for Rect<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Rect")
.field("x", &self.x)
.field("y", &self.y)
.field("width", &self.width)
.field("height", &self.height)
.finish()
impl Point {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
impl<T: PartialOrd + Add<Output = T> + Copy> Rect<T> {
pub fn contains(&self, value: (T, T)) -> bool {
value.0 >= self.x
&& value.1 >= self.y
&& value.0 < self.x + self.width
&& value.1 < self.y + self.height
impl From<(f32, f32)> for Point {
fn from(value: (f32, f32)) -> Self {
Point {
x: value.0,
y: value.1,
}
}
}
impl<T> Default for Rect<T>
where
T: Default,
{
fn default() -> Self {
#[derive(Clone, Debug, Default)]
pub struct ScaledPoint {
pub factor: Arc<AtomicF32>,
pub x: f32,
pub y: f32,
}
impl PartialEq for ScaledPoint {
fn eq(&self, other: &Self) -> bool {
self.factor.load(Ordering::Acquire) == other.factor.load(Ordering::Acquire)
&& self.x == other.x
&& self.y == other.y
}
}
impl ScaledPoint {
pub fn new(factor: Arc<AtomicF32>) -> Self {
Self {
x: Default::default(),
y: Default::default(),
width: Default::default(),
height: Default::default(),
factor,
..Default::default()
}
}
pub fn new_from<P>(factor: Arc<AtomicF32>, point: P) -> Self
where
P: Into<Point>,
{
let point = point.into();
Self {
factor,
x: point.x,
y: point.y,
}
}
pub fn set<P>(&mut self, point: P)
where
P: Into<Point>,
{
let point = point.into();
self.x = point.x;
self.y = point.y;
}
pub fn set_scaled<P>(&mut self, point: P)
where
P: Into<Point>,
{
let factor = self.factor.load(Ordering::Acquire);
let point = point.into();
self.x = point.x / factor;
self.y = point.y / factor;
}
pub fn as_point(&self) -> Point {
let factor = self.factor.load(Ordering::Acquire);
Point {
x: self.x * factor,
y: self.y * factor,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl Rect {
pub fn contains<P>(&self, value: P) -> bool
where
P: Into<Point>,
{
let value = value.into();
value.x >= self.x
&& value.y >= self.y
&& value.x < self.x + self.width
&& value.y < self.y + self.height
}
}
impl From<(f32, f32, f32, f32)> for Rect {
fn from(value: (f32, f32, f32, f32)) -> Self {
Rect {
x: value.0,
y: value.1,
width: value.2,
height: value.3,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ScaledRect {
pub factor: Arc<AtomicF32>,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl PartialEq for ScaledRect {
fn eq(&self, other: &Self) -> bool {
self.factor.load(Ordering::Acquire) == other.factor.load(Ordering::Acquire)
&& self.x == other.x
&& self.y == other.y
&& self.width == other.width
&& self.height == other.height
}
}
impl ScaledRect {
pub fn new(factor: Arc<AtomicF32>) -> Self {
Self {
factor,
..Default::default()
}
}
pub fn new_from<R>(factor: Arc<AtomicF32>, rect: R) -> Self
where
R: Into<Rect>,
{
let rect = rect.into();
Self {
factor,
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
}
}
pub fn as_rect(&self) -> Rect {
let factor = self.factor.load(Ordering::Acquire);
Rect {
x: self.x * factor,
y: self.y * factor,
width: self.width * factor,
height: self.height * factor,
}
}
}
@@ -194,4 +314,3 @@ pub fn rms<const N: usize>(sample_buffer: &RingBuffer<f64, N>, last_rms: f64) ->
last_rms.powf(2.0)
+ (1.0 / N as f64) * (sample_buffer[0].powf(2.0) - sample_buffer[N - 1].powf(2.0))
}