diff options
Diffstat (limited to 'prism')
-rw-r--r-- | prism/Cargo.toml | 2 | ||||
-rw-r--r-- | prism/src/main.rs | 220 | ||||
-rw-r--r-- | prism/src/output.rs | 240 | ||||
-rw-r--r-- | prism/src/unpack.rs | 61 |
4 files changed, 244 insertions, 279 deletions
diff --git a/prism/Cargo.toml b/prism/Cargo.toml index 6861a34..407596c 100644 --- a/prism/Cargo.toml +++ b/prism/Cargo.toml @@ -11,6 +11,6 @@ png = "0.17.10" rawproc = { git = "https://github.com/eclecticnybles/gaze" } rawloader = "0.37.1" nalgebra = "0.31.4" -mozjpeg = "0.10.1" +#mozjpeg = "0.10.1" zune-jpeg = "0.3.17" camino = "1.1.6" diff --git a/prism/src/main.rs b/prism/src/main.rs index 43278a6..8a52e0f 100644 --- a/prism/src/main.rs +++ b/prism/src/main.rs @@ -4,8 +4,8 @@ use camino::Utf8PathBuf; use lri_rs::{AwbGain, CameraId, LriFile, RawData, RawImage, SensorModel, Whitepoint}; use nalgebra::{Matrix3, Matrix3x1}; +mod output; mod rotate; -mod unpack; pub struct Entry { sensor: SensorModel, @@ -34,7 +34,7 @@ fn main() { println!("{} images", lri.image_count()); if let Some(refimg) = lri.reference_image() { - make(refimg, directory.join("reference.png"), gain); + output::make_png(refimg, directory.join("reference.png"), gain); } let mut set: HashMap<CameraId, Entry> = HashMap::new(); @@ -53,220 +53,6 @@ fn main() { });*/ for (idx, img) in lri.images().enumerate() { - make(img, directory.join(format!("image_{idx}.png")), gain); + output::make_png(img, directory.join(format!("image_{idx}.png")), gain); } } - -fn make(img: &RawImage, path: Utf8PathBuf, awb_gain: AwbGain) { - use rawproc::image::RawMetadata; - use rawproc::{colorspace::BayerRgb, image::Image}; - - let RawImage { - camera, - sensor, - width, - height, - format, - data, - sbro, - color, - } = img; - - println!( - "{camera} {sensor:?} [{}:{}] {width}x{height} {format}", - sbro.0, sbro.1 - ); - - let bayered = bayer(data, *width, *height); - - let (mut rgb, color_format) = match img.cfa_string() { - Some(cfa_string) => { - let rawimg: Image<u8, BayerRgb> = Image::from_raw_parts( - 4160, - 3120, - // We only care about CFA here because all we're doing is debayering - RawMetadata { - whitebalance: [1.0; 3], - whitelevels: [1024; 3], - crop: None, - // ugh CFA isn't exposed, so we pulled in rawloader for now - cfa: rawloader::CFA::new(cfa_string), - cam_to_xyz: nalgebra::Matrix3::zeros(), - }, - bayered, - ); - - (rawimg.debayer().data, png::ColorType::Rgb) - //(bayered, png::ColorType::Grayscale) - } - None => (bayered, png::ColorType::Grayscale), - }; - - rotate::rotate_180(rgb.as_mut_slice()); - let mut floats: Vec<f32> = rgb.into_iter().map(|p| p as f32 / 255.0).collect(); - - if !color.is_empty() { - print!("\tAvailable whitepoints: "); - color.iter().for_each(|c| print!("{:?} ", c.whitepoint)); - println!(); - } - - match img.color_info(Whitepoint::D65) { - Some(c) => { - println!("\tUsing D65"); - let to_xyz = Matrix3::from_row_slice(&c.forward_matrix); - // We're using Whitepoint::D65, but there is no D50 profile. - // If we use the BRUCE_XYZ_RGB_D65 matrix the image - // comes out too warm. - let to_srgb = Matrix3::from_row_slice(&BRUCE_XYZ_RGB_D50); - - let premul = to_xyz * to_srgb; - - for chnk in floats.chunks_mut(3) { - /*let r = chnk[0] * (1.0 / c.rg); - let g = chnk[1]; - let b = chnk[2] * (1.0 / c.bg);*/ - let r = chnk[0] * awb_gain.r; - let g = chnk[1]; - let b = chnk[2] * awb_gain.b; - - let px = Matrix3x1::new(r, g, b); - let rgb = premul * px; - - chnk[0] = srgb_gamma(rgb[0]) * 255.0; - chnk[1] = srgb_gamma(rgb[1]) * 255.0; - chnk[2] = srgb_gamma(rgb[2]) * 255.0; - } - } - None => { - println!("\tColor profile for D65 not found. Doing gamma and nothing else!"); - floats.iter_mut().for_each(|f| *f = srgb_gamma(*f) * 255.0); - } - } - - let bytes: Vec<u8> = floats.into_iter().map(|f| f as u8).collect(); - - println!("\tWriting {}", &path); - make_png(path, *width, *height, &bytes, color_format) -} - -#[rustfmt::skip] -#[allow(dead_code)] -const BRUCE_XYZ_RGB_D50: [f32; 9] = [ - 3.1338561, -1.6168667, -0.4906146, - -0.9787684, 1.9161415, 0.0334540, - 0.0719453, -0.2289914, 1.4052427 -]; - -#[rustfmt::skip] -const BRUCE_XYZ_RGB_D65: [f32; 9] = [ - 3.2404542, -1.5371385, -0.4985314, - -0.9692660, 1.8760108, 0.0415560, - 0.0556434, -0.2040259, 1.0572252 -]; - -#[rustfmt::skip] -const BRADFORD_D50_D65: [f32; 9] = [ - 0.9555766, -0.0230393, 0.0631636, - -0.0282895, 1.0099416, 0.0210077, - 0.0122982, -0.0204830, 1.3299098, -]; - -#[inline] -pub fn srgb_gamma(mut float: f32) -> f32 { - if float <= 0.0031308 { - float *= 12.92; - } else { - float = float.powf(1.0 / 2.4) * 1.055 - 0.055; - } - - float.clamp(0.0, 1.0) -} - -fn bayer(data: &RawData<'_>, width: usize, height: usize) -> Vec<u8> { - match data { - RawData::Packed10bpp { data } => { - let size = width * height; - let mut ten_data = vec![0; size]; - unpack::tenbit(data, width * height, ten_data.as_mut_slice()); - - // I've only seen it on one color defintion or - // something, but there's a black level of 42, so subtract it. - // without it the image is entirely too red. - //ten_data.iter_mut().for_each(|p| *p = p.saturating_sub(42)); - - ten_data - .into_iter() - .map(|p| ((p.saturating_sub(42)) >> 2) as u8) - .collect() - } - RawData::BayerJpeg { - header: _, - format, - jpeg0, - jpeg1, - jpeg2, - jpeg3, - } => { - let mut bayered = vec![0; width * height]; - - match format { - 0 => { - let mut into = vec![0; (width * height) / 4]; - - let mut channel = |jpeg: &[u8], offset: usize| { - zune_jpeg::JpegDecoder::new(jpeg) - .decode_into(&mut into) - .unwrap(); - - for idx in 0..into.len() { - let ww = width / 2; - let in_x = idx % ww; - let in_y = idx / ww; - - let bayer_x = (in_x * 2) + (offset % 2); - let bayer_y = (in_y * 2) + (offset / 2); - - let bayer_idx = bayer_y * width + bayer_x; - bayered[bayer_idx] = into[idx]; - } - }; - - //BGGR - //RGGB - //GRBG - channel(jpeg0, 0); - channel(jpeg1, 1); - channel(jpeg2, 2); - channel(jpeg3, 3); - } - 1 => { - zune_jpeg::JpegDecoder::new(jpeg0) - .decode_into(&mut bayered) - .unwrap(); - } - _ => unreachable!(), - } - - bayered - } - } -} - -fn make_png<P: AsRef<std::path::Path>>( - path: P, - width: usize, - height: usize, - data: &[u8], - color_format: png::ColorType, -) { - //return; - use std::fs::File; - - let file = File::create(path).unwrap(); - let mut enc = png::Encoder::new(file, width as u32, height as u32); - enc.set_color(color_format); - enc.set_depth(png::BitDepth::Eight); - let mut writer = enc.write_header().unwrap(); - writer.write_image_data(data).unwrap(); -} diff --git a/prism/src/output.rs b/prism/src/output.rs new file mode 100644 index 0000000..043f23d --- /dev/null +++ b/prism/src/output.rs @@ -0,0 +1,240 @@ +use camino::Utf8PathBuf; +use lri_rs::{AwbGain, RawData, RawImage, Whitepoint}; +use nalgebra::{Matrix3, Matrix3x1}; + +use crate::rotate; + +pub fn make_png(img: &RawImage, path: Utf8PathBuf, awb_gain: AwbGain) { + use rawproc::image::RawMetadata; + use rawproc::{colorspace::BayerRgb, image::Image}; + + let RawImage { + camera, + sensor, + width, + height, + format, + data: _data, + sbro, + color, + } = img; + + println!( + "{camera} {sensor:?} [{}:{}] {width}x{height} {format}", + sbro.0, sbro.1 + ); + + let bayered = img.unpack().unwrap(); + + let (mut rgb, color_format) = match img.cfa_string() { + Some(cfa_string) => { + let rawimg: Image<u16, BayerRgb> = Image::from_raw_parts( + 4160, + 3120, + // We only care about CFA here because all we're doing is debayering + RawMetadata { + whitebalance: [1.0; 3], + whitelevels: [1024; 3], + crop: None, + // ugh CFA isn't exposed, so we pulled in rawloader for now + cfa: rawloader::CFA::new(cfa_string), + cam_to_xyz: nalgebra::Matrix3::zeros(), + }, + bayered, + ); + + (rawimg.debayer().data, png::ColorType::Rgb) + //(bayered, png::ColorType::Grayscale) + } + None => (bayered, png::ColorType::Grayscale), + }; + + rotate::rotate_180(rgb.as_mut_slice()); + let mut floats: Vec<f32> = rgb + .into_iter() + .map(|p| ((p.saturating_sub(42) as f32) / 1023.0)) + .collect(); + + if !color.is_empty() { + print!("\tAvailable whitepoints: "); + color.iter().for_each(|c| print!("{:?} ", c.whitepoint)); + println!(); + } + + match img.color_info(Whitepoint::D65) { + Some(c) => { + println!("\tUsing D65"); + let to_xyz = Matrix3::from_row_slice(&c.forward_matrix); + // We're using Whitepoint::D65, but there is no D50 profile. + // If we use the BRUCE_XYZ_RGB_D65 matrix the image + // comes out too warm. + let to_srgb = Matrix3::from_row_slice(&BRUCE_XYZ_RGB_D50); + + let premul = to_xyz * to_srgb; + + let wb_max = awb_gain.r.max(awb_gain.b); + + let wb_r = awb_gain.r; // / wb_max; + let wb_g = 1.0; // / wb_max; + let wb_b = awb_gain.b; // / wb_max; + + for (idx, chnk) in floats.chunks_mut(3).enumerate() { + /*let r = chnk[0] * (1.0 / c.rg); + let g = chnk[1]; + let b = chnk[2] * (1.0 / c.bg);*/ + let r = chnk[0] * wb_r; + let g = chnk[1] * wb_g; + let b = chnk[2] * wb_b; + + if idx == 4 { + println!( + "R: {:.2},{:.2},{:.2} - W: {:.2},{:.2},{:.2}", + chnk[0], chnk[1], chnk[2], r, g, b + ); + } + + let px = Matrix3x1::new(r, g, b); + let rgb = premul * px; + + chnk[0] = srgb_gamma(rgb[0]); + chnk[1] = srgb_gamma(rgb[1]); + chnk[2] = srgb_gamma(rgb[2]); + + if idx == 4 { + println!( + "S: {:.2},{:.2},{:.2} - G: {:.2},{:.2},{:.2}", + rgb[0], rgb[1], rgb[2], chnk[0], chnk[1], chnk[2] + ); + } + } + } + None => { + println!("\tColor profile for D65 not found. Doing gamma and nothing else!"); + floats.iter_mut().for_each(|f| *f = srgb_gamma(*f)); + } + } + + let bytes: Vec<u8> = floats.into_iter().map(|f| (f * 255.0) as u8).collect(); + + println!("\tWriting {}", &path); + write_png(path, *width, *height, &bytes, color_format) +} + +#[rustfmt::skip] +#[allow(dead_code)] +const BRUCE_XYZ_RGB_D50: [f32; 9] = [ + 3.1338561, -1.6168667, -0.4906146, + -0.9787684, 1.9161415, 0.0334540, + 0.0719453, -0.2289914, 1.4052427 +]; + +#[rustfmt::skip] +const BRUCE_XYZ_RGB_D65: [f32; 9] = [ + 3.2404542, -1.5371385, -0.4985314, + -0.9692660, 1.8760108, 0.0415560, + 0.0556434, -0.2040259, 1.0572252 +]; + +#[rustfmt::skip] +const BRADFORD_D50_D65: [f32; 9] = [ + 0.9555766, -0.0230393, 0.0631636, + -0.0282895, 1.0099416, 0.0210077, + 0.0122982, -0.0204830, 1.3299098, +]; + +#[inline] +pub fn srgb_gamma(mut float: f32) -> f32 { + if float <= 0.0031308 { + float *= 12.92; + } else { + float = float.powf(1.0 / 2.4) * 1.055 - 0.055; + } + + float.clamp(0.0, 1.0) +} + +fn bayer(img: &RawImage, width: usize, height: usize) -> Vec<u8> { + match img.data { + RawData::Packed10bpp { data } => { + let unpacked = img.unpack().unwrap(); + + // I've only seen it on one color defintion or + // something, but there's a black level of 42, so subtract it. + // without it the image is entirely too red. + //ten_data.iter_mut().for_each(|p| *p = p.saturating_sub(42)); + + unpacked + .into_iter() + .map(|p| ((p.saturating_sub(42)) >> 2) as u8) + .collect() + } + RawData::BayerJpeg { + header: _, + format, + jpeg0, + jpeg1, + jpeg2, + jpeg3, + } => { + let mut bayered = vec![0; width * height]; + + match format { + 0 => { + let mut into = vec![0; (width * height) / 4]; + + let mut channel = |jpeg: &[u8], offset: usize| { + zune_jpeg::JpegDecoder::new(jpeg) + .decode_into(&mut into) + .unwrap(); + + for idx in 0..into.len() { + let ww = width / 2; + let in_x = idx % ww; + let in_y = idx / ww; + + let bayer_x = (in_x * 2) + (offset % 2); + let bayer_y = (in_y * 2) + (offset / 2); + + let bayer_idx = bayer_y * width + bayer_x; + bayered[bayer_idx] = into[idx]; + } + }; + + //BGGR + //RGGB + //GRBG + channel(jpeg0, 0); + channel(jpeg1, 1); + channel(jpeg2, 2); + channel(jpeg3, 3); + } + 1 => { + zune_jpeg::JpegDecoder::new(jpeg0) + .decode_into(&mut bayered) + .unwrap(); + } + _ => unreachable!(), + } + + bayered + } + } +} + +fn write_png<P: AsRef<std::path::Path>>( + path: P, + width: usize, + height: usize, + data: &[u8], + color_format: png::ColorType, +) { + //return; + use std::fs::File; + + let file = File::create(path).unwrap(); + let mut enc = png::Encoder::new(file, width as u32, height as u32); + enc.set_color(color_format); + enc.set_depth(png::BitDepth::Eight); + let mut writer = enc.write_header().unwrap(); + writer.write_image_data(data).unwrap(); +} diff --git a/prism/src/unpack.rs b/prism/src/unpack.rs deleted file mode 100644 index bc761f3..0000000 --- a/prism/src/unpack.rs +++ /dev/null @@ -1,61 +0,0 @@ -const TEN_MASK: u64 = 1023; // ten bits - -pub fn tenbit(packd: &[u8], count: usize, upack: &mut [u16]) { - let required_len_packd = (count as f32 * (10.0 / 8.0)).ceil() as usize; - - if count > upack.len() { - panic!( - "expected output buffer to be {count} bytes, got {} bytes", - upack.len() - ) - } - - if required_len_packd > packd.len() { - panic!( - "expected input to be at least {required_len_packd} bytes, it was {}", - packd.len() - ) - } - - let mut packd = packd[..required_len_packd].to_vec(); - packd.reverse(); - let chunker = packd.chunks_exact(5); - let remain = chunker.remainder(); - - for (idx, chnk) in chunker.enumerate() { - let long = u64::from_be_bytes([ - 0x00, 0x00, 0x00, chnk[0], chnk[1], chnk[2], chnk[3], chnk[4], - ]); - - let b4 = long & TEN_MASK; - let b3 = (long >> 10) & TEN_MASK; - let b2 = (long >> 20) & TEN_MASK; - let b1 = (long >> 30) & TEN_MASK; - - let idx = idx * 4; - upack[idx] = b1 as u16; - upack[idx + 1] = b2 as u16; - upack[idx + 2] = b3 as u16; - upack[idx + 3] = b4 as u16; - } - - if remain.len() > 0 { - let mut long_bytes = [0x00; 8]; - - for (idx, byte) in remain.iter().enumerate() { - long_bytes[idx] = *byte; - } - - let long = u64::from_le_bytes(long_bytes); - - let count_remain = count % 4; - let start = count - count_remain; - for idx in 0..count_remain { - upack[start + idx] = ((long >> (10 * idx)) & TEN_MASK) as u16; - } - } -} - -//pub fn twelvebit(packed: &[u8]) { -// 3 bytes per 2 12-bits -//} |