flow_plots/render/
plotters_backend.rs1use crate::PlotBytes;
2use crate::create_axis_specs;
3use crate::density_calc::RawPixelData;
4use crate::options::DensityPlotOptions;
5use crate::render::{ProgressInfo, RenderConfig};
6use flow_fcs::{TransformType, Transformable};
7
8fn format_transform_value(transform: &TransformType, value: &f32) -> String {
12 match transform {
13 TransformType::Linear => format!("{:.1e}", value),
14 TransformType::Arcsinh { cofactor } => {
15 let original_value = (value / cofactor).sinh() * cofactor;
17 format!("{:.1e}", original_value)
19 }
20 TransformType::Biexponential { .. } => {
21 let original_value = transform.inverse_transform(value);
23 format!("{:.1e}", original_value)
25 }
26 }
27}
28use anyhow::Result;
29use image::RgbImage;
30use plotters::{
31 backend::BitMapBackend, chart::ChartBuilder, prelude::IntoDrawingArea, style::WHITE,
32};
33
34pub fn render_pixels(
43 pixels: Vec<RawPixelData>,
44 options: &DensityPlotOptions,
45 render_config: &mut RenderConfig,
46) -> Result<PlotBytes> {
47 use crate::options::PlotOptions;
48
49 let base = options.base();
50 let width = base.width;
51 let height = base.height;
52 let margin = base.margin;
53 let x_label_area_size = base.x_label_area_size;
54 let y_label_area_size = base.y_label_area_size;
55
56 let setup_start = std::time::Instant::now();
57 let mut pixel_buffer = vec![255; (width * height * 3) as usize];
59
60 let (plot_x_range, plot_y_range, x_spec, y_spec) = {
61 let backend = BitMapBackend::with_buffer(&mut pixel_buffer, (width, height));
62 let root = backend.into_drawing_area();
63 root.fill(&WHITE)
64 .map_err(|e| anyhow::anyhow!("failed to fill plot background: {e}"))?;
65
66 let (x_spec, y_spec) = create_axis_specs(
68 &options.x_axis.range,
69 &options.y_axis.range,
70 &options.x_axis.transform,
71 &options.y_axis.transform,
72 )?;
73
74 let mut chart = ChartBuilder::on(&root)
75 .margin(margin)
76 .x_label_area_size(x_label_area_size)
77 .y_label_area_size(y_label_area_size)
78 .build_cartesian_2d(x_spec.start..x_spec.end, y_spec.start..y_spec.end)?;
79
80 let x_transform_clone = options.x_axis.transform.clone();
82 let y_transform_clone = options.y_axis.transform.clone();
83
84 let x_formatter =
86 move |x: &f32| -> String { format_transform_value(&x_transform_clone, x) };
87 let y_formatter =
88 move |y: &f32| -> String { format_transform_value(&y_transform_clone, y) };
89
90 let mut mesh = chart.configure_mesh();
91 mesh.x_max_light_lines(4)
92 .y_max_light_lines(4)
93 .x_labels(10)
94 .y_labels(10)
95 .x_label_formatter(&x_formatter)
96 .y_label_formatter(&y_formatter);
97
98 if let Some(ref x_label) = options.x_axis.label {
100 mesh.x_desc(x_label);
101 }
102 if let Some(ref y_label) = options.y_axis.label {
103 mesh.y_desc(y_label);
104 }
105
106 let mesh_start = std::time::Instant::now();
107 mesh.draw()
108 .map_err(|e| anyhow::anyhow!("failed to draw plot mesh: {e}"))?;
109 eprintln!(" ├─ Mesh drawing: {:?}", mesh_start.elapsed());
110
111 let plotting_area = chart.plotting_area();
113 let (plot_x_range, plot_y_range) = plotting_area.get_pixel_range();
114
115 root.present()
116 .map_err(|e| anyhow::anyhow!("failed to present plotters buffer: {e}"))?;
117
118 (plot_x_range, plot_y_range, x_spec, y_spec)
119 }; let series_start = std::time::Instant::now();
124
125 let plot_x_start = plot_x_range.start as f32;
126 let plot_y_start = plot_y_range.start as f32;
127 let plot_width = (plot_x_range.end - plot_x_range.start) as f32;
128 let plot_height = (plot_y_range.end - plot_y_range.start) as f32;
129
130 let data_width = x_spec.end - x_spec.start;
132 let data_height = y_spec.end - y_spec.start;
133
134 let mut pixel_count = 0;
136 let total_pixels = pixels.len();
137 let chunk_size = 1000; for pixel in &pixels {
141 let data_x = pixel.x;
142 let data_y = pixel.y;
143
144 let rel_x = (data_x - x_spec.start) / data_width;
146 let rel_y = (y_spec.end - data_y) / data_height; let screen_x = (plot_x_start + rel_x * plot_width) as i32;
149 let screen_y = (plot_y_start + rel_y * plot_height) as i32;
150
151 if screen_x >= plot_x_range.start
153 && screen_x < plot_x_range.end
154 && screen_y >= plot_y_range.start
155 && screen_y < plot_y_range.end
156 {
157 let px = screen_x as u32;
158 let py = screen_y as u32;
159
160 let idx = ((py * width + px) * 3) as usize;
162
163 if idx + 2 < pixel_buffer.len() {
164 pixel_buffer[idx] = pixel.r;
165 pixel_buffer[idx + 1] = pixel.g;
166 pixel_buffer[idx + 2] = pixel.b;
167 }
168 }
169
170 pixel_count += 1;
171
172 if pixel_count % chunk_size == 0 || pixel_count == total_pixels {
174 let percent = (pixel_count as f32 / total_pixels as f32) * 100.0;
175
176 let chunk_start = (pixel_count - chunk_size.min(pixel_count)).max(0);
178 let chunk_end = pixel_count;
179 let chunk_pixels: Vec<RawPixelData> = pixels
180 .iter()
181 .skip(chunk_start)
182 .take(chunk_end - chunk_start)
183 .map(|p| RawPixelData {
184 x: p.x,
185 y: p.y,
186 r: p.r,
187 g: p.g,
188 b: p.b,
189 })
190 .collect();
191
192 render_config.report_progress(ProgressInfo {
193 pixels: chunk_pixels,
194 percent,
195 });
196 }
197 }
198
199 eprintln!(
200 " ├─ Direct pixel writing: {:?} ({} pixels)",
201 series_start.elapsed(),
202 pixels.len()
203 );
204 eprintln!(" ├─ Total plotting: {:?}", setup_start.elapsed());
205
206 let img_start = std::time::Instant::now();
207 let img: RgbImage = image::ImageBuffer::from_vec(width, height, pixel_buffer)
208 .ok_or_else(|| anyhow::anyhow!("plot image buffer had unexpected size"))?;
209 eprintln!(" ├─ Image buffer conversion: {:?}", img_start.elapsed());
210
211 let encode_start = std::time::Instant::now();
212
213 let raw_size = (width * height * 3) as usize;
217 let estimated_jpeg_size = raw_size / 8; let mut encoded_data = Vec::with_capacity(estimated_jpeg_size);
219
220 let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut encoded_data, 85);
223 encoder
224 .encode(img.as_raw(), width, height, image::ExtendedColorType::Rgb8)
225 .map_err(|e| anyhow::anyhow!("failed to JPEG encode plot: {e}"))?;
226 eprintln!(" └─ JPEG encoding: {:?}", encode_start.elapsed());
227
228 Ok(encoded_data)
230}