use crate::chart::{compose_with_annotations, ChartAnnotations, ChartImage};
use crate::color_scheme::{default_color_scheme_data, get_color_count};
pub struct Magnitude {
pub width: u32,
pub height: u32,
pub max_magnitude: f32,
pub min_magnitude: f32,
pub buffer: Vec<f32>,
}
impl Magnitude {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
max_magnitude: f32::NEG_INFINITY,
min_magnitude: f32::INFINITY,
buffer: vec![0.0; (width * height) as usize],
}
}
pub fn add_point(&mut self, x: u32, y: u32, magnitude_value: f32) {
if x >= self.width || y >= self.height {
return;
}
let idx = (y * self.width + x) as usize;
self.buffer[idx] = magnitude_value;
if magnitude_value > self.max_magnitude {
self.max_magnitude = magnitude_value;
}
if magnitude_value < self.min_magnitude {
self.min_magnitude = magnitude_value;
}
}
pub fn shift_buffer_to_non_negative(&mut self) {
if self.buffer.is_empty() {
return;
}
if self.min_magnitude < 0.0 {
let shift = -self.min_magnitude;
for val in &mut self.buffer {
*val += shift;
}
self.max_magnitude += shift;
self.min_magnitude = 0.0;
}
}
pub fn render(&mut self) -> Vec<u8> {
let colors = default_color_scheme_data();
self.render_with_colors(&colors)
}
pub fn render_with_colors(&mut self, colors: &[u8]) -> Vec<u8> {
self.shift_buffer_to_non_negative();
let saturation = if self.max_magnitude > 0.0 {
self.max_magnitude
} else {
1.0
};
self.render_saturated(colors, saturation)
}
pub fn render_saturated(&self, colors: &[u8], saturation: f32) -> Vec<u8> {
assert!(saturation > 0.0);
let total_pixels = (self.width * self.height) as usize;
let mut colorbuf = vec![0u8; total_pixels * 4];
let ncolors = get_color_count(colors);
if ncolors == 0 {
return colorbuf;
}
for idx in 0..total_pixels {
let val = self.buffer[idx];
let normalized = (val / saturation).clamp(0.0, 1.0);
let color_idx = ((ncolors - 1) as f32 * normalized + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let src_start = color_idx * 4;
let dst_start = idx * 4;
colorbuf[dst_start..dst_start + 4].copy_from_slice(&colors[src_start..src_start + 4]);
}
colorbuf
}
pub fn render_with_annotations(
&mut self,
colors: &[u8],
annotations: &ChartAnnotations,
) -> ChartImage {
let data = self.render_with_colors(colors);
compose_with_annotations(&data, self.width, self.height, annotations)
}
pub fn render_default_with_annotations(
&mut self,
annotations: &ChartAnnotations,
) -> ChartImage {
let colors = default_color_scheme_data();
self.render_with_annotations(&colors, annotations)
}
pub fn render_saturated_with_annotations(
&self,
colors: &[u8],
saturation: f32,
annotations: &ChartAnnotations,
) -> ChartImage {
let data = self.render_saturated(colors, saturation);
compose_with_annotations(&data, self.width, self.height, annotations)
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.max_magnitude = f32::NEG_INFINITY;
self.min_magnitude = f32::INFINITY;
}
}
pub struct MagnitudeMapped {
pub input_width: u32,
pub input_height: u32,
pub image_width: u32,
pub image_height: u32,
pub max_magnitude: f32,
pub min_magnitude: f32,
pub buffer: Vec<f32>,
}
impl MagnitudeMapped {
pub fn new(input_width: u32, input_height: u32, image_width: u32, image_height: u32) -> Self {
Self {
input_width,
input_height,
image_width,
image_height,
max_magnitude: f32::NEG_INFINITY,
min_magnitude: f32::INFINITY,
buffer: vec![0.0; (image_width * image_height) as usize],
}
}
pub fn map_coordinates(&self, input_x: u32, input_y: u32) -> Option<(u32, u32)> {
if self.input_width == 0 || self.input_height == 0 {
return None;
}
let scale_x = self.image_width as f32 / self.input_width as f32;
let scale_y = self.image_height as f32 / self.input_height as f32;
let mut image_x = (input_x as f32 * scale_x) as u32;
let mut image_y = (input_y as f32 * scale_y) as u32;
if image_x >= self.image_width {
image_x = self.image_width - 1;
}
if image_y >= self.image_height {
image_y = self.image_height - 1;
}
Some((image_x, image_y))
}
pub fn add_point(&mut self, input_x: u32, input_y: u32, magnitude_value: f32) {
if self.input_width == 0 || self.input_height == 0 {
return;
}
let scale_x = self.image_width as f32 / self.input_width as f32;
let scale_y = self.image_height as f32 / self.input_height as f32;
let start_x = (input_x as f32 * scale_x) as u32;
let start_y = (input_y as f32 * scale_y) as u32;
let end_x = ((input_x + 1) as f32 * scale_x) as u32;
let end_y = ((input_y + 1) as f32 * scale_y) as u32;
let end_x = end_x.min(self.image_width);
let end_y = end_y.min(self.image_height);
for img_y in start_y..end_y {
for img_x in start_x..end_x {
let idx = (img_y * self.image_width + img_x) as usize;
self.buffer[idx] += magnitude_value;
if self.buffer[idx] > self.max_magnitude {
self.max_magnitude = self.buffer[idx];
}
if self.buffer[idx] < self.min_magnitude {
self.min_magnitude = self.buffer[idx];
}
}
}
}
pub fn shift_buffer_to_non_negative(&mut self) {
if self.buffer.is_empty() {
return;
}
if self.min_magnitude < 0.0 {
let shift = -self.min_magnitude;
for val in &mut self.buffer {
*val += shift;
}
self.max_magnitude += shift;
self.min_magnitude = 0.0;
}
}
pub fn render(&mut self) -> Vec<u8> {
let colors = default_color_scheme_data();
self.render_with_colors(&colors)
}
pub fn render_with_colors(&mut self, colors: &[u8]) -> Vec<u8> {
self.shift_buffer_to_non_negative();
let saturation = if self.max_magnitude > 0.0 {
self.max_magnitude
} else {
1.0
};
self.render_saturated(colors, saturation)
}
pub fn render_saturated(&self, colors: &[u8], saturation: f32) -> Vec<u8> {
assert!(saturation > 0.0);
let total_pixels = (self.image_width * self.image_height) as usize;
let mut colorbuf = vec![0u8; total_pixels * 4];
let ncolors = get_color_count(colors);
if ncolors == 0 {
return colorbuf;
}
for idx in 0..total_pixels {
let val = self.buffer[idx];
let normalized = (val / saturation).clamp(0.0, 1.0);
let color_idx = ((ncolors - 1) as f32 * normalized + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let src_start = color_idx * 4;
let dst_start = idx * 4;
colorbuf[dst_start..dst_start + 4].copy_from_slice(&colors[src_start..src_start + 4]);
}
colorbuf
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.max_magnitude = f32::NEG_INFINITY;
self.min_magnitude = f32::INFINITY;
}
pub fn render_with_annotations(
&mut self,
colors: &[u8],
annotations: &ChartAnnotations,
) -> ChartImage {
let data = self.render_with_colors(colors);
compose_with_annotations(&data, self.image_width, self.image_height, annotations)
}
pub fn render_default_with_annotations(
&mut self,
annotations: &ChartAnnotations,
) -> ChartImage {
let colors = default_color_scheme_data();
self.render_with_annotations(&colors, annotations)
}
pub fn render_saturated_with_annotations(
&self,
colors: &[u8],
saturation: f32,
annotations: &ChartAnnotations,
) -> ChartImage {
let data = self.render_saturated(colors, saturation);
compose_with_annotations(&data, self.image_width, self.image_height, annotations)
}
}
pub struct MagnitudeMappedGrid {
grid_size: usize,
plot_width: u32,
plot_height: u32,
global_max_magnitude: f32,
global_min_magnitude: f32,
plots: Vec<MagnitudeMapped>,
}
impl MagnitudeMappedGrid {
pub fn new(
grid_size: usize,
input_width: u32,
input_height: u32,
plot_width: u32,
plot_height: u32,
) -> Self {
let plot_count = grid_size * grid_size;
let plots = (0..plot_count)
.map(|_| MagnitudeMapped::new(input_width, input_height, plot_width, plot_height))
.collect();
Self {
grid_size,
plot_width,
plot_height,
global_max_magnitude: f32::NEG_INFINITY,
global_min_magnitude: f32::INFINITY,
plots,
}
}
pub fn get_plot(&mut self, row: usize, col: usize) -> &mut MagnitudeMapped {
assert!(row < self.grid_size && col < self.grid_size);
&mut self.plots[row * self.grid_size + col]
}
pub fn set_plot(&mut self, row: usize, col: usize, plot: MagnitudeMapped) {
assert!(row < self.grid_size && col < self.grid_size);
self.plots[row * self.grid_size + col] = plot;
self.update_global_extrema();
}
pub fn add_point(
&mut self,
row: usize,
col: usize,
input_x: u32,
input_y: u32,
magnitude_value: f32,
) {
assert!(row < self.grid_size && col < self.grid_size);
let plot = &mut self.plots[row * self.grid_size + col];
plot.add_point(input_x, input_y, magnitude_value);
self.global_max_magnitude = self.global_max_magnitude.max(plot.max_magnitude);
self.global_min_magnitude = self.global_min_magnitude.min(plot.min_magnitude);
}
pub fn update_global_extrema(&mut self) {
self.global_max_magnitude = f32::NEG_INFINITY;
self.global_min_magnitude = f32::INFINITY;
for plot in &self.plots {
self.global_max_magnitude = self.global_max_magnitude.max(plot.max_magnitude);
self.global_min_magnitude = self.global_min_magnitude.min(plot.min_magnitude);
}
}
pub fn shift_all_buffers_to_non_negative(&mut self) {
if self.global_min_magnitude < 0.0 {
let shift = -self.global_min_magnitude;
for plot in &mut self.plots {
for val in &mut plot.buffer {
*val += shift;
}
plot.max_magnitude += shift;
plot.min_magnitude = 0.0;
}
self.global_max_magnitude += shift;
self.global_min_magnitude = 0.0;
}
}
pub fn render(&mut self, colors: &[u8]) -> Vec<u8> {
self.update_global_extrema();
self.shift_all_buffers_to_non_negative();
let total_width = self.plot_width * self.grid_size as u32;
let total_height = self.plot_height * self.grid_size as u32;
let mut combined_buffer = vec![0u8; (total_width * total_height * 4) as usize];
let saturation = if self.global_max_magnitude > 0.0 {
self.global_max_magnitude
} else {
1.0
};
for row in 0..self.grid_size {
for col in 0..self.grid_size {
let plot = &self.plots[row * self.grid_size + col];
let plot_buffer = plot.render_saturated(colors, saturation);
let start_x = col as u32 * self.plot_width;
let start_y = row as u32 * self.plot_height;
for y in 0..self.plot_height {
for x in 0..self.plot_width {
let src_idx = ((y * self.plot_width + x) * 4) as usize;
let dst_idx = (((start_y + y) * total_width + (start_x + x)) * 4) as usize;
combined_buffer[dst_idx..dst_idx + 4]
.copy_from_slice(&plot_buffer[src_idx..src_idx + 4]);
}
}
}
}
combined_buffer
}
pub fn reset(&mut self) {
for plot in &mut self.plots {
plot.reset();
}
self.global_max_magnitude = f32::NEG_INFINITY;
self.global_min_magnitude = f32::INFINITY;
}
}