1use std::fs::File;
7use std::io::{BufWriter, Seek, Write};
8use std::path::Path;
9
10use crate::core::image::RgbImage;
11use crate::core::metadata::ImageMetadata;
12use crate::error::RawResult;
13use crate::tiff::writer::{IfdEntry, TiffWriter};
14use crate::tiff::{ByteOrder, TiffTag};
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct DngExportConfig {
20 pub software: Option<String>,
22 pub embed_exif: bool,
24 pub embed_gps: bool,
26}
27
28impl DngExportConfig {
29 pub fn archival() -> Self {
31 Self {
32 software: Some(format!("rawshift {}", env!("CARGO_PKG_VERSION"))),
33 embed_exif: true,
34 embed_gps: true,
35 }
36 }
37}
38
39pub fn export_dng_to_writer<W: Write + Seek>(
50 writer: W,
51 image: &RgbImage,
52 metadata: &ImageMetadata,
53 config: &DngExportConfig,
54) -> RawResult<()> {
55 let mut writer = TiffWriter::new(writer, ByteOrder::LittleEndian);
56
57 writer.write_header()?;
59
60 let (strip_offset, strip_bytes) = writer.write_image_strip_rgb16(&image.data)?;
62
63 let mut entries = build_dng_ifd(image, metadata, config, strip_offset, strip_bytes);
65
66 let ifd_offset = writer.write_ifd(&mut entries, 0)?;
68
69 writer.update_ifd0_offset(ifd_offset as u32)?;
71
72 Ok(())
73}
74
75pub fn export_dng(
79 path: &Path,
80 image: &RgbImage,
81 metadata: &ImageMetadata,
82 config: &DngExportConfig,
83) -> RawResult<()> {
84 let file = File::create(path)?;
85 export_dng_to_writer(BufWriter::new(file), image, metadata, config)
86}
87
88fn build_dng_ifd(
90 image: &RgbImage,
91 metadata: &ImageMetadata,
92 config: &DngExportConfig,
93 strip_offset: u64,
94 strip_bytes: u64,
95) -> Vec<IfdEntry> {
96 let mut entries = vec![
97 IfdEntry::long(TiffTag::ImageWidth, image.width()),
99 IfdEntry::long(TiffTag::ImageLength, image.height()),
100 IfdEntry::shorts(TiffTag::BitsPerSample, &[16, 16, 16]),
101 IfdEntry::short(TiffTag::Compression, 1), IfdEntry::short(TiffTag::PhotometricInterpretation, 2), IfdEntry::short(TiffTag::SamplesPerPixel, 3),
104 IfdEntry::long(TiffTag::RowsPerStrip, image.height()),
105 IfdEntry::long(TiffTag::StripOffsets, strip_offset as u32),
106 IfdEntry::long(TiffTag::StripByteCounts, strip_bytes as u32),
107 IfdEntry::short(TiffTag::PlanarConfiguration, 1), IfdEntry::rational(TiffTag::XResolution, 72, 1),
110 IfdEntry::rational(TiffTag::YResolution, 72, 1),
111 IfdEntry::short(TiffTag::ResolutionUnit, 2), ];
113
114 if let Some(orient) = metadata.image.orientation {
116 entries.push(IfdEntry::short(TiffTag::Orientation, orient));
117 } else {
118 entries.push(IfdEntry::short(TiffTag::Orientation, 1)); }
120
121 entries.push(IfdEntry::bytes(TiffTag::DNGVersion, &[1, 7, 0, 0]));
123 entries.push(IfdEntry::bytes(TiffTag::DNGBackwardVersion, &[1, 7, 0, 0]));
124
125 if !metadata.camera.make.is_empty() {
127 entries.push(IfdEntry::ascii(TiffTag::Make, &metadata.camera.make));
128 }
129 if !metadata.camera.model.is_empty() {
130 entries.push(IfdEntry::ascii(TiffTag::Model, &metadata.camera.model));
131 }
132
133 let unique_model = metadata
135 .camera
136 .unique_camera_model
137 .as_deref()
138 .unwrap_or_else(|| {
139 if !metadata.camera.model.is_empty() {
140 &metadata.camera.model
141 } else {
142 "Unknown Camera"
143 }
144 });
145 entries.push(IfdEntry::ascii(TiffTag::UniqueCameraModel, unique_model));
146
147 if let Some(ref sw) = config.software {
149 entries.push(IfdEntry::ascii(TiffTag::Software, sw));
150 }
151
152 if let Some(cm1) = &metadata.dng_color.color_matrix_1 {
155 let rationals: Vec<(i32, i32)> = cm1
157 .iter()
158 .map(|&v| ((v * 10000.0).round() as i32, 10000))
159 .collect();
160 entries.push(IfdEntry::srationals(TiffTag::ColorMatrix1, &rationals));
161 } else {
162 let default_cm: Vec<(i32, i32)> = [1.0f64, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
164 .iter()
165 .map(|&v| ((v * 10000.0).round() as i32, 10000))
166 .collect();
167 entries.push(IfdEntry::srationals(TiffTag::ColorMatrix1, &default_cm));
168 }
169
170 if let Some(ill1) = metadata.dng_color.calibration_illuminant_1 {
172 entries.push(IfdEntry::short(TiffTag::CalibrationIlluminant1, ill1));
173 } else {
174 entries.push(IfdEntry::short(TiffTag::CalibrationIlluminant1, 21)); }
176
177 if let Some(cm2) = &metadata.dng_color.color_matrix_2 {
179 let rationals: Vec<(i32, i32)> = cm2
180 .iter()
181 .map(|&v| ((v * 10000.0).round() as i32, 10000))
182 .collect();
183 entries.push(IfdEntry::srationals(TiffTag::ColorMatrix2, &rationals));
184
185 if let Some(ill2) = metadata.dng_color.calibration_illuminant_2 {
186 entries.push(IfdEntry::short(TiffTag::CalibrationIlluminant2, ill2));
187 } else {
188 entries.push(IfdEntry::short(TiffTag::CalibrationIlluminant2, 17)); }
190 }
191
192 if let Some(neutral) = &metadata.dng_color.as_shot_neutral {
194 let rationals: Vec<(u32, u32)> = neutral
195 .iter()
196 .map(|&v| ((v * 1000000.0).round() as u32, 1000000))
197 .collect();
198 entries.push(IfdEntry::rationals(TiffTag::AsShotNeutral, &rationals));
199 }
200
201 if let Some(balance) = &metadata.dng_color.analog_balance {
203 let rationals: Vec<(u32, u32)> = balance
204 .iter()
205 .map(|&v| ((v * 1000000.0).round() as u32, 1000000))
206 .collect();
207 entries.push(IfdEntry::rationals(TiffTag::AnalogBalance, &rationals));
208 }
209
210 if let Some(be) = metadata.dng_calibration.baseline_exposure {
212 let int_part = (be * 100.0).round() as i32;
214 entries.push(IfdEntry::srational(
215 TiffTag::BaselineExposure,
216 int_part,
217 100,
218 ));
219 }
220
221 if !metadata.image.black_levels.is_empty() {
223 let black: Vec<(u32, u32)> = metadata
225 .image
226 .black_levels
227 .iter()
228 .take(3)
229 .map(|&v| (v, 1))
230 .collect();
231 if black.len() == 3 {
232 entries.push(IfdEntry::rationals(TiffTag::BlackLevel, &black));
233 }
234 }
235
236 if let Some(wl) = metadata.image.white_level {
237 entries.push(IfdEntry::longs(TiffTag::WhiteLevel, &[wl, wl, wl]));
238 } else {
239 entries.push(IfdEntry::longs(TiffTag::WhiteLevel, &[65535, 65535, 65535]));
241 }
242
243 entries
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::core::image::RgbImage;
250
251 #[test]
252 fn test_build_dng_ifd() {
253 let image = RgbImage::new(100, 50, vec![0u16; 100 * 50 * 3]);
254 let metadata = ImageMetadata::default();
255 let config = DngExportConfig::archival();
256
257 let entries = build_dng_ifd(&image, &metadata, &config, 1024, 30000);
258
259 assert!(
261 entries
262 .iter()
263 .any(|e| e.tag == TiffTag::ImageWidth.as_u16())
264 );
265 assert!(
266 entries
267 .iter()
268 .any(|e| e.tag == TiffTag::ImageLength.as_u16())
269 );
270 assert!(
271 entries
272 .iter()
273 .any(|e| e.tag == TiffTag::DNGVersion.as_u16())
274 );
275 assert!(
276 entries
277 .iter()
278 .any(|e| e.tag == TiffTag::UniqueCameraModel.as_u16())
279 );
280 assert!(
281 entries
282 .iter()
283 .any(|e| e.tag == TiffTag::ColorMatrix1.as_u16())
284 );
285 }
286}