app
Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
main
John Lauer
the fields app's own source at 0.3.0, restoring what a mistaken push overwrote
73161b7
//! Field grids as PNG textures: a colour map over the copper, transparent elsewhere.
use crate::raster::Grid;
const INFERNO: [[f32; 3]; 9] = [[0.001, 0.0, 0.014], [0.16, 0.04, 0.33], [0.38, 0.09, 0.44], [0.59, 0.19, 0.38], [0.79, 0.30, 0.26], [0.93, 0.47, 0.11], [0.98, 0.68, 0.06], [0.97, 0.87, 0.30], [0.99, 1.0, 0.64]];
const MAGMA: [[f32; 3]; 9] = [[0.001, 0.0, 0.014], [0.11, 0.06, 0.28], [0.29, 0.06, 0.48], [0.47, 0.11, 0.51], [0.66, 0.18, 0.47], [0.83, 0.28, 0.40], [0.96, 0.44, 0.36], [0.99, 0.65, 0.47], [0.99, 0.99, 0.75]];
fn map(t: f32, cm: &[[f32; 3]; 9]) -> [u8; 3] {
let t = t.clamp(0.0, 1.0) * 8.0; let i = (t.floor() as usize).min(7); let f = t - i as f32;
let (a, b) = (cm[i], cm[i + 1]);
[((a[0] + (b[0] - a[0]) * f) * 255.0) as u8, ((a[1] + (b[1] - a[1]) * f) * 255.0) as u8, ((a[2] + (b[2] - a[2]) * f) * 255.0) as u8]
}
pub enum Scale { Log { lo: f32, hi: f32 }, Linear { hi: f32 } }
/// Write `g` as an RGBA PNG: colour where `mask` (or the value) says copper, transparent elsewhere.
pub fn write(path: &str, g: &Grid, mask: Option<&Grid>, scale: &Scale, magma: bool) -> Result<(), String> {
let mut px = vec![0u8; g.w * g.h * 4];
for i in 0..g.w * g.h {
let on = mask.map(|m| m.v[i] > 0.05).unwrap_or(true);
if !on { continue; }
let v = g.v[i];
let t = match scale { Scale::Log { lo, hi } => if v <= 0.0 { 0.0 } else { ((v.max(*lo) / lo).ln() / (hi / lo).ln()).clamp(0.0, 1.0) }, Scale::Linear { hi } => (v / hi.max(1e-9)).clamp(0.0, 1.0) };
let c = map(t, if magma { &MAGMA } else { &INFERNO });
px[i * 4] = c[0]; px[i * 4 + 1] = c[1]; px[i * 4 + 2] = c[2]; px[i * 4 + 3] = 255;
}
save(path, g.w, g.h, &px)
}
/// The copper itself: a flat tint where copper is, transparent elsewhere.
pub fn write_mask(path: &str, m: &Grid, rgb: [u8; 3]) -> Result<(), String> {
let mut px = vec![0u8; m.w * m.h * 4];
for i in 0..m.w * m.h { if m.v[i] > 0.05 { px[i * 4] = rgb[0]; px[i * 4 + 1] = rgb[1]; px[i * 4 + 2] = rgb[2]; px[i * 4 + 3] = (60.0 + 195.0 * m.v[i]) as u8; } }
save(path, m.w, m.h, &px)
}
/// Raw values as a 16-bit grey PNG (value / hi × 65535), so the app can read the number under the cursor.
pub fn write_raw(path: &str, g: &Grid, hi: f32) -> Result<(), String> {
let mut px = vec![0u8; g.w * g.h * 2];
for i in 0..g.w * g.h { let v = ((g.v[i] / hi.max(1e-9)).clamp(0.0, 1.0) * 65535.0) as u16; px[i * 2] = (v >> 8) as u8; px[i * 2 + 1] = (v & 255) as u8; }
let file = std::fs::File::create(path).map_err(|e| format!("{path}: {e}"))?;
let mut enc = png::Encoder::new(std::io::BufWriter::new(file), g.w as u32, g.h as u32);
enc.set_color(png::ColorType::Grayscale); enc.set_depth(png::BitDepth::Sixteen);
let mut wr = enc.write_header().map_err(|e| e.to_string())?;
wr.write_image_data(&px).map_err(|e| e.to_string())
}
fn save(path: &str, w: usize, h: usize, px: &[u8]) -> Result<(), String> {
let file = std::fs::File::create(path).map_err(|e| format!("{path}: {e}"))?;
let mut enc = png::Encoder::new(std::io::BufWriter::new(file), w as u32, h as u32);
enc.set_color(png::ColorType::Rgba); enc.set_depth(png::BitDepth::Eight);
let mut wr = enc.write_header().map_err(|e| e.to_string())?;
wr.write_image_data(px).map_err(|e| e.to_string())
}