use std::io::{BufWriter, Write};
use std::path::Path;
use anyhow::{Context, Result};
use tiff::encoder::{Rational, SRational};
use tiff::encoder::TiffEncoder;
use tiff::tags::Tag;
use crate::file::{BayerPattern, McrawFileInfo};
const DC_BITS: [u8; 16] = [0, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const DC_HUFFVAL: [u8; 17] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
struct BitWriter {
buf: Vec<u8>,
acc: u32,
nbits: u32,
}
impl BitWriter {
fn new() -> Self {
BitWriter { buf: Vec::new(), acc: 0, nbits: 0 }
}
fn write_bits(&mut self, value: u32, count: u32) {
self.acc = (self.acc << count) | value;
self.nbits += count;
while self.nbits >= 8 {
let byte = (self.acc >> (self.nbits - 8)) as u8;
self.buf.push(byte);
self.nbits -= 8;
}
self.acc &= (1 << self.nbits) - 1;
}
fn flush(&mut self) {
if self.nbits > 0 {
let byte = (self.acc << (8 - self.nbits)) as u8;
self.buf.push(byte);
self.nbits = 0;
self.acc = 0;
}
}
fn byte_stuff(&mut self) {
let mut i = 0;
while i < self.buf.len() {
if self.buf[i] == 0xFF {
self.buf.insert(i + 1, 0x00);
i += 2;
} else {
i += 1;
}
}
}
fn into_bytes(mut self) -> Vec<u8> {
self.flush();
self.byte_stuff();
self.buf
}
}
fn build_huffman_table(bits: &[u8; 16], huffval: &[u8]) -> (Vec<u32>, Vec<u8>) {
let num_sym = huffval.len();
let mut codes = vec![0u32; num_sym];
let mut sizes = vec![0u8; num_sym];
let mut code: u32 = 0;
let mut si: usize = 0;
for i in 0..16 {
let count = bits[i] as usize;
for _ in 0..count {
let sym = huffval[si] as usize;
codes[sym] = code;
sizes[sym] = (i + 1) as u8;
code += 1;
si += 1;
}
code <<= 1;
}
(codes, sizes)
}
fn encode_jpeg_diff(diff: i32) -> (u32, u32) {
if diff == 0 {
return (0, 0);
}
let abs_diff = diff.unsigned_abs();
let cat = 32 - abs_diff.leading_zeros();
let extra = if diff > 0 {
diff as u32
} else {
(diff - 1) as u32
};
(cat, extra & ((1 << cat) - 1))
}
fn write_diff(bw: &mut BitWriter, diff: i32, codes: &[u32], sizes: &[u8]) {
let (cat, extra) = encode_jpeg_diff(diff);
let sym = cat as usize;
bw.write_bits(codes[sym], sizes[sym] as u32);
if cat > 0 {
bw.write_bits(extra, cat);
}
}
pub fn compress_lj92(bayer: &[u16], width: usize, height: usize) -> Vec<u8> {
let (codes, sizes) = build_huffman_table(&DC_BITS, &DC_HUFFVAL);
let mut bw = BitWriter::new();
for y in 0..height {
let row = y * width;
for x in 0..width {
let predictor = if x == 0 { 0u16 } else { bayer[row + x - 1] };
let diff = bayer[row + x] as i32 - predictor as i32;
write_diff(&mut bw, diff, &codes, &sizes);
}
}
let entropy = bw.into_bytes();
let mut jpeg = Vec::with_capacity(64 + entropy.len());
jpeg.extend_from_slice(&[0xFF, 0xD8]);
jpeg.extend_from_slice(&[0xFF, 0xC3, 0x00, 0x0B, 0x10]);
jpeg.extend_from_slice(&(height as u16).to_be_bytes());
jpeg.extend_from_slice(&(width as u16).to_be_bytes());
jpeg.extend_from_slice(&[0x01, 0x01, 0x11, 0x00]);
let dht_len: u16 = 2 + 1 + 16 + 17;
jpeg.extend_from_slice(&[0xFF, 0xC4]);
jpeg.extend_from_slice(&dht_len.to_be_bytes());
jpeg.push(0x00); jpeg.extend_from_slice(&DC_BITS);
jpeg.extend_from_slice(&DC_HUFFVAL);
jpeg.extend_from_slice(&[0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00]);
jpeg.extend_from_slice(&entropy);
jpeg.extend_from_slice(&[0xFF, 0xD9]);
jpeg
}
fn pattern_to_cfa(pattern: BayerPattern, offset_x: u32, offset_y: u32) -> [u8; 4] {
let mut cfa = match pattern {
BayerPattern::RGGB => [0u8, 1, 1, 2],
BayerPattern::GRBG => [1u8, 0, 2, 1],
BayerPattern::GBRG => [1u8, 2, 0, 1],
BayerPattern::BGGR => [2u8, 1, 1, 0],
_ => [0u8, 1, 1, 2],
};
if offset_x & 1 == 1 {
cfa = [cfa[1], cfa[0], cfa[3], cfa[2]];
}
if offset_y & 1 == 1 {
cfa = [cfa[2], cfa[3], cfa[0], cfa[1]];
}
cfa
}
fn stem_from_path(path: &str) -> String {
let base = std::path::Path::new(path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("clip")
.to_string();
if base.ends_with("-metadata") {
base.trim_end_matches("-metadata").to_string()
} else {
base
}
}
fn f64_to_srational(v: f64) -> SRational {
SRational { n: (v * 10000.0).round() as i32, d: 10000 }
}
fn f32_to_srational(v: f32) -> SRational {
SRational { n: (v * 10000.0).round() as i32, d: 10000 }
}
fn matrix9_as_srational_slice(m: &[f64; 9]) -> [SRational; 9] {
[
f64_to_srational(m[0]), f64_to_srational(m[1]), f64_to_srational(m[2]),
f64_to_srational(m[3]), f64_to_srational(m[4]), f64_to_srational(m[5]),
f64_to_srational(m[6]), f64_to_srational(m[7]), f64_to_srational(m[8]),
]
}
fn asn_as_srational_slice(asn: [f32; 3]) -> [SRational; 3] {
[f32_to_srational(asn[0]), f32_to_srational(asn[1]), f32_to_srational(asn[2])]
}
pub struct DngWriter<'a> {
info: &'a McrawFileInfo,
bayer: &'a [u16],
as_shot_neutral: [f32; 3],
frame_index: usize,
}
impl<'a> DngWriter<'a> {
pub fn new(
info: &'a McrawFileInfo,
bayer: &'a [u16],
as_shot_neutral: [f32; 3],
frame_index: usize,
) -> Self {
DngWriter { info, bayer, as_shot_neutral, frame_index }
}
pub fn filename(&self, output_dir: &str) -> String {
let stem = stem_from_path(&self.info.path);
std::path::Path::new(output_dir)
.join(format!("{}_{:06}.dng", stem, self.frame_index))
.to_string_lossy()
.to_string()
}
pub fn write_to_path<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let path = path.as_ref();
let width = self.info.width as usize;
let height = self.info.height as usize;
let pixel_count = width * height;
if self.bayer.len() < pixel_count {
anyhow::bail!(
"Bayer buffer too short for {}×{}: got {} pixels, need {}",
width, height, self.bayer.len(), pixel_count,
);
}
let compressed = compress_lj92(self.bayer, width, height);
let file = std::fs::File::create(path)
.with_context(|| format!("Failed to create DNG file: {}", path.display()))?;
let mut writer = BufWriter::new(file);
let mut tiff = TiffEncoder::new(&mut writer)
.context("Failed to create TIFF encoder")?;
let mut dir = tiff.new_directory()
.context("Failed to start TIFF directory")?;
let data_off = dir.write_data(&compressed[..])
.context("Failed to write LJ92 compressed data")?;
let aw = width as u16;
let ah = height as u16;
dir.write_tag(Tag::Unknown(256), aw)?; dir.write_tag(Tag::Unknown(257), ah)?; dir.write_tag(Tag::Unknown(258), 16u16)?; dir.write_tag(Tag::Unknown(259), 7u16)?; dir.write_tag(Tag::Unknown(262), 32803u16)?; dir.write_tag(Tag::Unknown(273), data_off as u32)?; dir.write_tag(Tag::Unknown(277), 1u16)?; dir.write_tag(Tag::Unknown(278), ah as u32)?; dir.write_tag(Tag::Unknown(279), compressed.len() as u32)?;
let cfa_off_x = self.info.active_offset_x as u32;
let cfa_off_y = self.info.active_offset_y as u32;
let cfa = pattern_to_cfa(self.info.bayer_pattern, cfa_off_x, cfa_off_y);
dir.write_tag(Tag::Unknown(33421), &[0u16, 1, 2][..])?;
dir.write_tag(Tag::Unknown(33422), &cfa[..])?;
dir.write_tag(Tag::Unknown(50706), &[1u8, 4, 0, 0][..])?;
dir.write_tag(Tag::Unknown(50707), &[1u8, 4, 0, 0][..])?;
let model = self.info.camera_metadata.camera_model
.as_deref()
.unwrap_or("Unknown");
dir.write_tag(Tag::Unknown(50708), model.as_bytes())?;
let cm1 = self.info.camera_metadata.color_matrix
.unwrap_or([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
let cm1_arr = matrix9_as_srational_slice(&cm1);
dir.write_tag(Tag::Unknown(50721), &cm1_arr[..])?;
let ill1 = self.info.camera_metadata.calibration_illuminant1.unwrap_or(21);
dir.write_tag(Tag::Unknown(50778), ill1 as u16)?;
if let Some(ill2) = self.info.camera_metadata.calibration_illuminant2 {
dir.write_tag(Tag::Unknown(50779), ill2 as u16)?;
}
if let Some(ref cm2) = self.info.camera_metadata.color_matrix2 {
let cm2_arr = matrix9_as_srational_slice(cm2);
dir.write_tag(Tag::Unknown(50722), &cm2_arr[..])?;
}
let asn_arr = asn_as_srational_slice(self.as_shot_neutral);
dir.write_tag(Tag::Unknown(50728), &asn_arr[..])?;
let bl = self.info.black_level as u16;
dir.write_tag(Tag::Unknown(50714), &[bl, bl, bl, bl][..])?;
dir.write_tag(Tag::Unknown(50717), self.info.white_level as u32)?;
dir.write_tag(Tag::Unknown(50718), &[
Rational { n: 1, d: 1 },
Rational { n: 1, d: 1 },
][..])?;
dir.write_tag(Tag::Unknown(50719), &[
Rational { n: self.info.active_offset_x as u32, d: 1 },
Rational { n: self.info.active_offset_y as u32, d: 1 },
][..])?;
dir.write_tag(Tag::Unknown(50720), &[
Rational { n: self.info.active_width as u32, d: 1 },
Rational { n: self.info.active_height as u32, d: 1 },
][..])?;
let at = self.info.active_offset_y as u32;
let al = self.info.active_offset_x as u32;
let ab = at + self.info.active_height as u32;
let ar = al + self.info.active_width as u32;
dir.write_tag(Tag::Unknown(0xC68E), &[at, al, ab, ar][..])?;
dir.finish().context("Failed to finalize TIFF directory")?;
writer.flush().context("Failed to flush DNG file")?;
Ok(())
}
}