use crate::camera_projection::CameraProjection;
use crate::layer::{Layer, LayerId, LayerKind};
use crate::layers::image_overlay_layer::ImageOverlayData;
use rustial_math::GeoCoord;
use std::any::Any;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct FrameData {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
pub trait FrameProvider: Send + Sync {
fn next_frame(&mut self) -> Option<FrameData>;
fn is_animating(&self) -> bool {
true
}
}
pub struct CallbackFrameProvider<F> {
callback: F,
animating: bool,
}
impl<F: FnMut() -> Option<FrameData> + Send + Sync> CallbackFrameProvider<F> {
pub fn new(callback: F) -> Self {
Self {
callback,
animating: true,
}
}
pub fn set_animating(&mut self, animating: bool) {
self.animating = animating;
}
}
impl<F: FnMut() -> Option<FrameData> + Send + Sync> FrameProvider for CallbackFrameProvider<F> {
fn next_frame(&mut self) -> Option<FrameData> {
(self.callback)()
}
fn is_animating(&self) -> bool {
self.animating
}
}
pub type FrameProviderFactory = Arc<dyn Fn() -> Box<dyn FrameProvider> + Send + Sync>;
pub struct DynamicImageOverlayLayer {
id: LayerId,
name: String,
visible: bool,
opacity: f32,
coordinates: [GeoCoord; 4],
width: u32,
height: u32,
data: Arc<Vec<u8>>,
generation: u64,
has_frame: bool,
provider: Box<dyn FrameProvider>,
}
impl std::fmt::Debug for DynamicImageOverlayLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynamicImageOverlayLayer")
.field("id", &self.id)
.field("name", &self.name)
.field("visible", &self.visible)
.field("opacity", &self.opacity)
.field("width", &self.width)
.field("height", &self.height)
.field("has_frame", &self.has_frame)
.field("generation", &self.generation)
.field("animating", &self.provider.is_animating())
.finish()
}
}
impl DynamicImageOverlayLayer {
pub fn new(
name: impl Into<String>,
coordinates: [GeoCoord; 4],
provider: Box<dyn FrameProvider>,
) -> Self {
Self {
id: LayerId::next(),
name: name.into(),
visible: true,
opacity: 1.0,
coordinates,
width: 0,
height: 0,
data: Arc::new(Vec::new()),
generation: 0,
has_frame: false,
provider,
}
}
#[inline]
pub fn coordinates(&self) -> &[GeoCoord; 4] {
&self.coordinates
}
pub fn set_coordinates(&mut self, coordinates: [GeoCoord; 4]) {
self.coordinates = coordinates;
self.generation = self.generation.wrapping_add(1);
}
#[inline]
pub fn generation(&self) -> u64 {
self.generation
}
#[inline]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[inline]
pub fn has_frame(&self) -> bool {
self.has_frame
}
pub fn poll_frame(&mut self) -> bool {
if !self.provider.is_animating() {
return false;
}
if let Some(frame) = self.provider.next_frame() {
debug_assert_eq!(
frame.data.len(),
(frame.width * frame.height * 4) as usize,
"FrameData RGBA8 length must equal width * height * 4"
);
self.width = frame.width;
self.height = frame.height;
self.data = Arc::new(frame.data);
self.generation = self.generation.wrapping_add(1);
self.has_frame = true;
true
} else {
false
}
}
pub fn to_overlay_data(&self, projection: CameraProjection) -> Option<ImageOverlayData> {
if !self.has_frame || self.width == 0 || self.height == 0 {
return None;
}
let corners = [
project_corner(&self.coordinates[0], projection),
project_corner(&self.coordinates[1], projection),
project_corner(&self.coordinates[2], projection),
project_corner(&self.coordinates[3], projection),
];
Some(ImageOverlayData {
layer_id: self.id,
corners,
width: self.width,
height: self.height,
data: Arc::clone(&self.data),
opacity: self.opacity,
})
}
pub fn provider(&self) -> &dyn FrameProvider {
&*self.provider
}
pub fn provider_mut(&mut self) -> &mut dyn FrameProvider {
&mut *self.provider
}
}
fn project_corner(coord: &GeoCoord, projection: CameraProjection) -> [f64; 3] {
let w = projection.project(coord);
[w.position.x, w.position.y, w.position.z]
}
impl Layer for DynamicImageOverlayLayer {
fn id(&self) -> LayerId {
self.id
}
fn name(&self) -> &str {
&self.name
}
fn kind(&self) -> LayerKind {
LayerKind::Custom
}
fn visible(&self) -> bool {
self.visible
}
fn set_visible(&mut self, visible: bool) {
self.visible = visible;
}
fn opacity(&self) -> f32 {
self.opacity
}
fn set_opacity(&mut self, opacity: f32) {
self.opacity = opacity.clamp(0.0, 1.0);
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
struct CountingProvider {
frame_count: u32,
width: u32,
height: u32,
}
impl CountingProvider {
fn new(width: u32, height: u32) -> Self {
Self {
frame_count: 0,
width,
height,
}
}
}
impl FrameProvider for CountingProvider {
fn next_frame(&mut self) -> Option<FrameData> {
self.frame_count += 1;
let fill = (self.frame_count % 256) as u8;
Some(FrameData {
width: self.width,
height: self.height,
data: vec![fill; (self.width * self.height * 4) as usize],
})
}
}
struct PausableProvider {
animating: bool,
}
impl FrameProvider for PausableProvider {
fn next_frame(&mut self) -> Option<FrameData> {
Some(FrameData {
width: 2,
height: 2,
data: vec![128; 16],
})
}
fn is_animating(&self) -> bool {
self.animating
}
}
struct OneShotProvider {
sent: bool,
}
impl FrameProvider for OneShotProvider {
fn next_frame(&mut self) -> Option<FrameData> {
if self.sent {
None
} else {
self.sent = true;
Some(FrameData {
width: 4,
height: 4,
data: vec![255; 64],
})
}
}
fn is_animating(&self) -> bool {
!self.sent
}
}
fn sample_corners() -> [GeoCoord; 4] {
[
GeoCoord::from_lat_lon(40.0, -74.0),
GeoCoord::from_lat_lon(40.0, -73.0),
GeoCoord::from_lat_lon(39.0, -73.0),
GeoCoord::from_lat_lon(39.0, -74.0),
]
}
#[test]
fn new_layer_starts_without_frame() {
let layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(8, 8)),
);
assert!(!layer.has_frame());
assert_eq!(layer.dimensions(), (0, 0));
assert_eq!(layer.generation(), 0);
}
#[test]
fn poll_frame_receives_first_frame() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(8, 8)),
);
assert!(layer.poll_frame());
assert!(layer.has_frame());
assert_eq!(layer.dimensions(), (8, 8));
assert_eq!(layer.generation(), 1);
}
#[test]
fn consecutive_polls_bump_generation() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(4, 4)),
);
layer.poll_frame();
layer.poll_frame();
layer.poll_frame();
assert_eq!(layer.generation(), 3);
}
#[test]
fn paused_provider_skips_poll() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(PausableProvider { animating: false }),
);
assert!(!layer.poll_frame());
assert!(!layer.has_frame());
assert_eq!(layer.generation(), 0);
}
#[test]
fn one_shot_provider_stops_after_first() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(OneShotProvider { sent: false }),
);
assert!(layer.poll_frame());
assert_eq!(layer.generation(), 1);
assert!(!layer.poll_frame());
assert_eq!(layer.generation(), 1);
}
#[test]
fn to_overlay_data_returns_none_without_frame() {
let layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(4, 4)),
);
assert!(layer
.to_overlay_data(CameraProjection::WebMercator)
.is_none());
}
#[test]
fn to_overlay_data_returns_some_after_poll() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(4, 4)),
);
layer.poll_frame();
let data = layer.to_overlay_data(CameraProjection::WebMercator);
assert!(data.is_some());
let data = data.unwrap();
assert_eq!(data.width, 4);
assert_eq!(data.height, 4);
assert_eq!(data.data.len(), 64);
}
#[test]
fn set_coordinates_bumps_generation() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(4, 4)),
);
let g0 = layer.generation();
layer.set_coordinates([
GeoCoord::from_lat_lon(50.0, -75.0),
GeoCoord::from_lat_lon(50.0, -74.0),
GeoCoord::from_lat_lon(49.0, -74.0),
GeoCoord::from_lat_lon(49.0, -75.0),
]);
assert_eq!(layer.generation(), g0 + 1);
}
#[test]
fn opacity_clamps_to_valid_range() {
let mut layer = DynamicImageOverlayLayer::new(
"test",
sample_corners(),
Box::new(CountingProvider::new(4, 4)),
);
layer.set_opacity(2.0);
assert_eq!(layer.opacity(), 1.0);
layer.set_opacity(-0.5);
assert_eq!(layer.opacity(), 0.0);
}
#[test]
fn callback_frame_provider_works() {
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let counter_clone = counter.clone();
let provider = CallbackFrameProvider::new(move || {
let n = counter_clone.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Some(FrameData {
width: 2,
height: 2,
data: vec![(n % 256) as u8; 16],
})
});
let mut layer = DynamicImageOverlayLayer::new("test", sample_corners(), Box::new(provider));
layer.poll_frame();
assert!(layer.has_frame());
assert_eq!(counter.load(std::sync::atomic::Ordering::Relaxed), 1);
layer.poll_frame();
assert_eq!(counter.load(std::sync::atomic::Ordering::Relaxed), 2);
}
}