dspfrivs/src/bomp.rs

250 lines
7.3 KiB
Rust

// Name: bomp
// Description: bitmap plotter utility
// Why: generate viewable plots from f32be data files
// Processor usage: low
// Memory usage: loads all input files into memory at once
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::process::exit;
static OUTER_PADDING:usize = 16;
static INTERPLOT_PADDING:usize = 16;
static SIZE_ROUNDOFF:usize = 16;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() < 4
{
eprintln!("usage: bomp <yscale> <output.bmp> <input.dat> [<optional additional inputs>...]");
exit(1);
}
/*
yscale (integer):
the vertical scale of each plot, equal to the distance in pixels from zero to the plot's max value.
plots are thus 2*yscale+1 pixels high, plus padding.
output.bmp (bitmap file):
a classic microsoft bitmap file to which to write the uncompressed plot image.
if you want to use it elsewhere, i recommend converting it via ffmpeg to png.
inputs.dat (raw big-endian f32 data file):
simply a sequence of arbitrarily many f32 values in big-endian byte order.
you can specify an arbitrary number of input files, each of which will produce
its own parallel plot below the previous one on the resulting image. parallel plots
will use the same horizontal scale (always exactly one pixel per data value) and
vertical scale (automatically adjusted so that the overall maximum value between all
data sets matches the specified yscale value in pixel height).
*/
let yscale = match argv[1].parse::<usize>()
{
Ok(n) => n,
Err(_) =>
{
eprintln!("bomp: the first command-line argument needs to be an integer (number of vertical pixels per plot.)");
exit(1);
},
};
let output_filename:String = argv[2].clone();
let mut output_file = match File::create(&output_filename)
{
Ok(file) => file,
Err(why) =>
{
eprintln!("bomp: could not open {} for writing: {}", output_filename, why);
exit(1);
},
};
let mut input_files:Vec<(File, String)> = Vec::new();
for i in 3..argv.len()
{
let new_filename:String = argv[i].clone();
let new_file = match File::open(&argv[i])
{
Err(reason) =>
{
eprintln!("bomp: failed to open {} for reading: {}", new_filename, reason);
exit(1);
},
Ok(file) => file,
};
input_files.push((new_file, new_filename));
}
let mut signals:Vec<Vec<f32>> = Vec::new();
for (file,filename) in input_files.iter_mut()
{
let mut file_buffer:Vec<u8> = Vec::new();
match file.read_to_end(&mut file_buffer)
{
Err(reason) =>
{
eprintln!("bomp: could not read from input file {}: {}", filename, reason);
exit(1);
},
Ok(_) => (),
};
let mut new_signal:Vec<f32> = Vec::with_capacity(file_buffer.len()/4);
for chunk in file_buffer.chunks_exact(4)
{
let mut sample_bytes:[u8;4] = [0x00; 4];
sample_bytes.copy_from_slice(chunk);
new_signal.push(f32::from_be_bytes(sample_bytes));
}
signals.push(new_signal);
}
let mut abs_max:f32 = 0.0;
let mut signals_maxlen:usize = 0;
for signal in signals.iter()
{
signals_maxlen = signals_maxlen.max(signal.len());
for point in signal.iter()
{
abs_max = abs_max.max(point.abs());
}
}
let scale_factor = (yscale as f32) / abs_max;
eprintln!("peak: {}", abs_max);
for signal in signals.iter_mut()
{
for point in signal.iter_mut()
{
*point *= scale_factor;
}
}
let plot_height:usize = (yscale*2 + 1);
let plot_width:usize = signals_maxlen;
let signal_count:usize = signals.len();
let intersignal_count:usize = signals.len()-1;
// adjust the padding to make the image size a nice round number.
// why? i forgot, honestly. seems pretty pointless.
let mut lpad:usize = OUTER_PADDING;
let mut rpad:usize = OUTER_PADDING;
while (plot_width + lpad + rpad) % SIZE_ROUNDOFF != 0
{
if lpad <= rpad
{
lpad += 1;
} else {
rpad += 1;
}
}
let mut tpad:usize = OUTER_PADDING;
let mut bpad:usize = OUTER_PADDING;
while (plot_height*signal_count + intersignal_count*INTERPLOT_PADDING + tpad + bpad) % SIZE_ROUNDOFF != 0
{
if tpad <= bpad
{
tpad += 1;
} else {
bpad += 1;
}
}
let color_count:usize = 256;
let header_size:usize = 54 + color_count*4;
let image_width:usize = plot_width + lpad + rpad;
let image_height:usize = signal_count*plot_height + intersignal_count*INTERPLOT_PADDING + tpad + bpad;
let file_size:usize = image_height * image_width + 1078;
eprintln!("plots: {}", signal_count);
eprintln!("wide: {}", image_width);
eprintln!("high: {}", image_height);
eprintln!("size: {}", file_size);
let mut header:Vec<u8> = Vec::with_capacity(header_size);
header.extend_from_slice(&"BM".as_bytes()); // "signature"
header.extend_from_slice(&(file_size as u32).to_le_bytes()); // filesize
header.extend_from_slice(&0u32.to_le_bytes()); // forbidden field
header.extend_from_slice(&(header_size as u32).to_le_bytes()); // data offset
header.extend_from_slice(&40u32.to_le_bytes()); // infoheader size (constant)
header.extend_from_slice(&(image_width as u32).to_le_bytes()); // pixel width
header.extend_from_slice(&(image_height as u32).to_le_bytes()); // pixel height
header.extend_from_slice(&1u16.to_le_bytes()); // number of planes
header.extend_from_slice(&8u16.to_le_bytes()); // bits per pixel
header.extend_from_slice(&0u32.to_le_bytes()); // compression type (none)
header.extend_from_slice(&0u32.to_le_bytes()); // compressed image size (not applicable)
header.extend_from_slice(&1024u32.to_le_bytes()); // horizontal pixels/meter (arbitrary)
header.extend_from_slice(&1024u32.to_le_bytes()); // vertical pixels/meter (arbitrary)
header.extend_from_slice(&256u32.to_le_bytes()); // colors used (all of them)
header.extend_from_slice(&0u32.to_le_bytes()); // number of important colors (what does that even mean)
assert_eq!(header.len(), 54);
for i in 0x00..=0xFF
{
header.push(i); // red
header.push(i); // green
header.push(i); // blue
header.push(0x00); // ?????
}
match output_file.write_all(&header)
{
Err(reason) =>
{
eprintln!("bomp: write error on file {}: {}", output_filename, reason);
exit(1);
},
Ok(_) => (),
};
output_file.write_all(&vec![0x00; bpad*image_width]).expect("write error (lower padding)");
for (signal_number, signal) in signals.iter().enumerate().rev() // reversed because bmps go from bottom to top, for some reason
{
for y in -(yscale as isize)..=(yscale as isize)
{
let mut row_buffer:Vec<u8> = Vec::with_capacity(image_width);
row_buffer.extend_from_slice(&vec![0x00; lpad]);
for x in 0..signal.len()
{
if
(
(
signal[x] >= (y as f32)
&&
y >= 0
) || (
signal[x] <= (y as f32)
&&
y <= 0
)
) {
row_buffer.push(0xFF);
} else {
row_buffer.push(0x00);
}
}
row_buffer.extend_from_slice(&vec![0x00; signals_maxlen - signal.len() + rpad]);
output_file.write_all(&row_buffer).expect("write error (row)");
}
if signal_number > 0
{
output_file.write_all(&vec![0x00; image_width*INTERPLOT_PADDING]).expect("write error (interplot padding)");
}
}
output_file.write_all(&vec![0x00; tpad*image_width]).expect("write error (upper padding)");
}