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;
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 }
21}
22use anyhow::Result;
23use image::RgbImage;
24use plotters::{
25 backend::BitMapBackend, chart::ChartBuilder, prelude::IntoDrawingArea, style::WHITE,
26};
27
28pub fn render_pixels(
37 pixels: Vec<RawPixelData>,
38 options: &DensityPlotOptions,
39 render_config: &mut RenderConfig,
40) -> Result<PlotBytes> {
41 use crate::options::PlotOptions;
42
43 let base = options.base();
44 let width = base.width;
45 let height = base.height;
46 let margin = base.margin;
47 let x_label_area_size = base.x_label_area_size;
48 let y_label_area_size = base.y_label_area_size;
49
50 let setup_start = std::time::Instant::now();
51 let mut pixel_buffer = vec![255; (width * height * 3) as usize];
53
54 let (plot_x_range, plot_y_range, x_spec, y_spec) = {
55 let backend = BitMapBackend::with_buffer(&mut pixel_buffer, (width, height));
56 let root = backend.into_drawing_area();
57 root.fill(&WHITE)
58 .map_err(|e| anyhow::anyhow!("failed to fill plot background: {e}"))?;
59
60 let (x_spec, y_spec) = create_axis_specs(
62 &options.x_axis.range,
63 &options.y_axis.range,
64 &options.x_axis.transform,
65 &options.y_axis.transform,
66 )?;
67
68 let mut chart = ChartBuilder::on(&root)
69 .margin(margin)
70 .x_label_area_size(x_label_area_size)
71 .y_label_area_size(y_label_area_size)
72 .build_cartesian_2d(x_spec.start..x_spec.end, y_spec.start..y_spec.end)?;
73
74 let x_transform_clone = options.x_axis.transform.clone();
76 let y_transform_clone = options.y_axis.transform.clone();
77
78 let x_formatter =
80 move |x: &f32| -> String { format_transform_value(&x_transform_clone, x) };
81 let y_formatter =
82 move |y: &f32| -> String { format_transform_value(&y_transform_clone, y) };
83
84 let mut mesh = chart.configure_mesh();
85 mesh.x_max_light_lines(4)
86 .y_max_light_lines(4)
87 .x_labels(10)
88 .y_labels(10)
89 .x_label_formatter(&x_formatter)
90 .y_label_formatter(&y_formatter);
91
92 if let Some(ref x_label) = options.x_axis.label {
94 mesh.x_desc(x_label);
95 }
96 if let Some(ref y_label) = options.y_axis.label {
97 mesh.y_desc(y_label);
98 }
99
100 let mesh_start = std::time::Instant::now();
101 mesh.draw()
102 .map_err(|e| anyhow::anyhow!("failed to draw plot mesh: {e}"))?;
103 eprintln!(" ├─ Mesh drawing: {:?}", mesh_start.elapsed());
104
105 let plotting_area = chart.plotting_area();
107 let (plot_x_range, plot_y_range) = plotting_area.get_pixel_range();
108
109 root.present()
110 .map_err(|e| anyhow::anyhow!("failed to present plotters buffer: {e}"))?;
111
112 (plot_x_range, plot_y_range, x_spec, y_spec)
113 }; let series_start = std::time::Instant::now();
118
119 let plot_x_start = plot_x_range.start as f32;
120 let plot_y_start = plot_y_range.start as f32;
121 let plot_width = (plot_x_range.end - plot_x_range.start) as f32;
122 let plot_height = (plot_y_range.end - plot_y_range.start) as f32;
123
124 let data_width = x_spec.end - x_spec.start;
126 let data_height = y_spec.end - y_spec.start;
127
128 let mut pixel_count = 0;
130 let total_pixels = pixels.len();
131 let chunk_size = 1000; for pixel in &pixels {
135 let data_x = pixel.x;
136 let data_y = pixel.y;
137
138 let rel_x = (data_x - x_spec.start) / data_width;
140 let rel_y = (y_spec.end - data_y) / data_height; let screen_x = (plot_x_start + rel_x * plot_width) as i32;
143 let screen_y = (plot_y_start + rel_y * plot_height) as i32;
144
145 if screen_x >= plot_x_range.start
147 && screen_x < plot_x_range.end
148 && screen_y >= plot_y_range.start
149 && screen_y < plot_y_range.end
150 {
151 let px = screen_x as u32;
152 let py = screen_y as u32;
153
154 let idx = ((py * width + px) * 3) as usize;
156
157 if idx + 2 < pixel_buffer.len() {
158 pixel_buffer[idx] = pixel.r;
159 pixel_buffer[idx + 1] = pixel.g;
160 pixel_buffer[idx + 2] = pixel.b;
161 }
162 }
163
164 pixel_count += 1;
165
166 if pixel_count % chunk_size == 0 || pixel_count == total_pixels {
168 let percent = (pixel_count as f32 / total_pixels as f32) * 100.0;
169
170 let chunk_start = (pixel_count - chunk_size.min(pixel_count)).max(0);
172 let chunk_end = pixel_count;
173 let chunk_pixels: Vec<RawPixelData> = pixels
174 .iter()
175 .skip(chunk_start)
176 .take(chunk_end - chunk_start)
177 .map(|p| RawPixelData {
178 x: p.x,
179 y: p.y,
180 r: p.r,
181 g: p.g,
182 b: p.b,
183 })
184 .collect();
185
186 render_config.report_progress(ProgressInfo {
187 pixels: chunk_pixels,
188 percent,
189 });
190 }
191 }
192
193 eprintln!(
194 " ├─ Direct pixel writing: {:?} ({} pixels)",
195 series_start.elapsed(),
196 pixels.len()
197 );
198 eprintln!(" ├─ Total plotting: {:?}", setup_start.elapsed());
199
200 let img_start = std::time::Instant::now();
201 let img: RgbImage = image::ImageBuffer::from_vec(width, height, pixel_buffer)
202 .ok_or_else(|| anyhow::anyhow!("plot image buffer had unexpected size"))?;
203 eprintln!(" ├─ Image buffer conversion: {:?}", img_start.elapsed());
204
205 let encode_start = std::time::Instant::now();
206
207 let raw_size = (width * height * 3) as usize;
211 let estimated_jpeg_size = raw_size / 8; let mut encoded_data = Vec::with_capacity(estimated_jpeg_size);
213
214 let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut encoded_data, 85);
217 encoder
218 .encode(img.as_raw(), width, height, image::ExtendedColorType::Rgb8)
219 .map_err(|e| anyhow::anyhow!("failed to JPEG encode plot: {e}"))?;
220 eprintln!(" └─ JPEG encoding: {:?}", encode_start.elapsed());
221
222 Ok(encoded_data)
224}