diff options
| -rw-r--r-- | Cargo.lock | 22 | ||||
| -rw-r--r-- | hittekaart-py/Cargo.toml | 2 | ||||
| -rw-r--r-- | hittekaart/src/renderer/heatmap.rs | 41 |
3 files changed, 40 insertions, 25 deletions
@@ -1118,11 +1118,10 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -1136,19 +1135,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" dependencies = [ "libc", "pyo3-build-config", @@ -1156,9 +1154,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1168,9 +1166,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" dependencies = [ "heck", "proc-macro2", diff --git a/hittekaart-py/Cargo.toml b/hittekaart-py/Cargo.toml index 7e69ef8..915f467 100644 --- a/hittekaart-py/Cargo.toml +++ b/hittekaart-py/Cargo.toml @@ -9,6 +9,6 @@ name = "hittekaart_py" crate-type = ["cdylib"] [dependencies] -pyo3 = "0.24.0" +pyo3 = "0.26.0" hittekaart = { path = "../hittekaart" } rayon = "1.10.0" diff --git a/hittekaart/src/renderer/heatmap.rs b/hittekaart/src/renderer/heatmap.rs index 187c8f3..d2c2452 100644 --- a/hittekaart/src/renderer/heatmap.rs +++ b/hittekaart/src/renderer/heatmap.rs @@ -7,9 +7,10 @@ //! We then render the colored heatmap tiles in `Renderer::colorize()`, which provides us with //! colorful PNG data. use crossbeam_channel::Sender; -use image::{ImageBuffer, Luma, Pixel, RgbaImage}; +use image::{ImageBuffer, Luma, Pixel, Rgba, RgbaImage}; use nalgebra::{vector, Vector2}; use rayon::iter::ParallelIterator; +use std::iter; use super::{ super::{ @@ -118,27 +119,40 @@ fn render_line<P: Pixel>( fn merge_heat_counter(base: &mut HeatCounter, overlay: &HeatCounter) { for (tx, ty, source) in overlay.enumerate_tiles() { let target = base.tile_mut(tx, ty); - for (x, y, source) in source.enumerate_pixels() { - let target = target.get_pixel_mut(x, y); - target[0] += source[0]; + for (t, s) in target.iter_mut().zip(source.iter()) { + *t += *s; } } } -fn colorize_tile(tile: &ImageBuffer<Luma<u8>, Vec<u8>>, max: u32) -> RgbaImage { - let gradient = colorgrad::yl_or_rd(); +fn colorize_tile(tile: &ImageBuffer<Luma<u8>, Vec<u8>>, lut: &[Rgba<u8>]) -> RgbaImage { let mut result = ImageBuffer::from_pixel(tile.width(), tile.height(), [0, 0, 0, 0].into()); - for (x, y, pixel) in tile.enumerate_pixels() { + for (pixel, target) in tile.pixels().zip(result.pixels_mut()) { if pixel[0] > 0 { - let alpha = pixel[0] as f64 / max as f64; - let color = gradient.at(1.0 - alpha); - let target = result.get_pixel_mut(x, y); - *target = color.to_rgba8().into(); + *target = lut[usize::from(pixel[0])]; } } result } +fn prepare_lut(max: u8) -> Vec<Rgba<u8>> { + let gradient = colorgrad::yl_or_rd(); + iter::once([0, 0, 0, 0].into()) + .chain((1..=max).map(|count| { + let alpha = count as f64 / max as f64; + // If we simply use alpha here, we get a linear mapping, and a jump from 1 to 2 + // repetitions is worth as much as a jump from 9 to 10. By transforming alpha via the + // cubic function given below, we give more weight to the "early" repetitions by having + // a steeper slope, and less weight to later repetitions. + // There is no science behind the power 3, others work as well (if you adjust the + // sign). It seemed like a decent trade-off though. + let alpha = (alpha - 1.0).powi(3) + 1.0; + let color = gradient.at(1.0 - alpha); + color.to_rgba8().into() + })) + .collect() +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Renderer; @@ -194,10 +208,13 @@ impl super::Renderer for Renderer { return Ok(()); } + // max is a u8, so at most we have 256 colors to compute. We can handle that. + let color_lut = prepare_lut(max); + layer .into_parallel_tiles() .try_for_each_with(tx, |tx, (tile_x, tile_y, tile)| { - let colorized = colorize_tile(&tile, max.into()); + let colorized = colorize_tile(&tile, &color_lut); let data = layer::compress_png_as_bytes(&colorized)?; tx.send(RenderedTile { x: tile_x, |
