about summary refs log tree commit diff
path: root/prism/src/output.rs
diff options
context:
space:
mode:
Diffstat (limited to 'prism/src/output.rs')
-rw-r--r--prism/src/output.rs240
1 files changed, 240 insertions, 0 deletions
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();
+}