1use crate::core::image::RgbImage;
23
24#[derive(Debug, Clone)]
33pub struct GainMap {
34 pub top: f64,
36 pub left: f64,
38 pub bottom: f64,
40 pub right: f64,
42 pub plane: u32,
44 pub planes: u32,
46 pub row_pitch: u32,
48 pub col_pitch: u32,
50 pub map_points_v: u32,
52 pub map_points_h: u32,
54 pub map_spacing_v: f64,
56 pub map_spacing_h: f64,
58 pub map_origin_v: f64,
60 pub map_origin_h: f64,
62 pub map_planes: u32,
64 pub gain: Vec<f32>,
66}
67
68impl GainMap {
69 pub fn apply_to_rgb(&self, image: &mut RgbImage) {
74 let img_w = image.width() as usize;
75 let img_h = image.height() as usize;
76 if img_w == 0 || img_h == 0 {
77 return;
78 }
79
80 let map_v = self.map_points_v as usize;
81 let map_h = self.map_points_h as usize;
82 let map_p = self.map_planes as usize;
83 if map_v == 0 || map_h == 0 || map_p == 0 || self.gain.is_empty() {
84 return;
85 }
86
87 let planes_start = self.plane as usize;
88 let planes_count = (self.planes as usize).min(3);
92
93 for y in 0..img_h {
94 let norm_v = if img_h > 1 {
96 y as f64 / (img_h - 1) as f64
97 } else {
98 0.5
99 };
100
101 let map_row_f =
103 ((norm_v - self.map_origin_v) / self.map_spacing_v).clamp(0.0, (map_v - 1) as f64);
104 let r0 = map_row_f.floor() as usize;
105 let r1 = (r0 + 1).min(map_v - 1);
106 let dr = map_row_f - r0 as f64;
107
108 for x in 0..img_w {
109 let norm_h = if img_w > 1 {
110 x as f64 / (img_w - 1) as f64
111 } else {
112 0.5
113 };
114
115 let map_col_f = ((norm_h - self.map_origin_h) / self.map_spacing_h)
116 .clamp(0.0, (map_h - 1) as f64);
117 let c0 = map_col_f.floor() as usize;
118 let c1 = (c0 + 1).min(map_h - 1);
119 let dc = map_col_f - c0 as f64;
120
121 let pixel_base = (y * img_w + x) * 3;
122
123 for p in 0..planes_count {
124 let channel = planes_start + p;
125 if channel >= 3 {
126 break;
127 }
128 let mp = if map_p == 1 { 0 } else { p.min(map_p - 1) };
130
131 let g00 = self.gain[r0 * map_h * map_p + c0 * map_p + mp] as f64;
132 let g01 = self.gain[r0 * map_h * map_p + c1 * map_p + mp] as f64;
133 let g10 = self.gain[r1 * map_h * map_p + c0 * map_p + mp] as f64;
134 let g11 = self.gain[r1 * map_h * map_p + c1 * map_p + mp] as f64;
135
136 let gain = g00 * (1.0 - dr) * (1.0 - dc)
137 + g01 * (1.0 - dr) * dc
138 + g10 * dr * (1.0 - dc)
139 + g11 * dr * dc;
140
141 let val = image.data[pixel_base + channel];
142 image.data[pixel_base + channel] =
143 (val as f64 * gain).clamp(0.0, 65535.0) as u16;
144 }
145 }
146 }
147 }
148}
149
150#[derive(Debug, Clone)]
152pub enum DngOpcode {
153 FixBadPixelsConstant {
155 planes: u32,
157 bad_point_value: u32,
159 },
160 FixBadPixelsList {
162 bad_points: Vec<(u32, u32)>,
164 bad_rects: Vec<(u32, u32, u32, u32)>,
166 },
167 GainMap(GainMap),
169 Unknown {
171 id: u32,
173 },
174}
175
176#[derive(Debug, Clone, Default)]
182pub struct OpcodeList {
183 pub opcodes: Vec<(DngOpcode, bool)>,
187}
188
189impl OpcodeList {
190 pub fn parse(data: &[u8]) -> Self {
194 if data.len() < 4 {
195 return OpcodeList::default();
196 }
197
198 let count = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
199 let mut pos = 4;
200 let mut opcodes = Vec::with_capacity(count);
201
202 for _ in 0..count {
203 if pos + 16 > data.len() {
204 break;
205 }
206
207 let opcode_id =
208 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
209 let flags =
211 u32::from_be_bytes([data[pos + 8], data[pos + 9], data[pos + 10], data[pos + 11]]);
212 let param_len = u32::from_be_bytes([
213 data[pos + 12],
214 data[pos + 13],
215 data[pos + 14],
216 data[pos + 15],
217 ]) as usize;
218 pos += 16;
219
220 let is_optional = (flags & 1) != 0;
221
222 if pos + param_len > data.len() {
223 break;
224 }
225 let param_data = &data[pos..pos + param_len];
226 pos += param_len;
227
228 let opcode = match opcode_id {
229 4 => Self::parse_fix_bad_pixels_constant(param_data),
230 5 => Self::parse_fix_bad_pixels_list(param_data),
231 9 => Self::parse_gain_map(param_data),
232 id => Some(DngOpcode::Unknown { id }),
233 };
234
235 if let Some(op) = opcode {
236 opcodes.push((op, is_optional));
237 }
238 }
239
240 OpcodeList { opcodes }
241 }
242
243 pub fn apply_to_rgb(&self, image: &mut RgbImage) {
249 for (opcode, is_optional) in &self.opcodes {
250 match opcode {
251 DngOpcode::GainMap(gm) => {
252 tracing::trace!(
253 "Applying GainMap: {}x{} grid, {} planes",
254 gm.map_points_h,
255 gm.map_points_v,
256 gm.map_planes
257 );
258 gm.apply_to_rgb(image);
259 }
260 DngOpcode::FixBadPixelsConstant { .. } => {
261 if !is_optional {
263 tracing::debug!("FixBadPixelsConstant on RGB not yet implemented");
264 }
265 }
266 DngOpcode::FixBadPixelsList { .. } => {
267 if !is_optional {
268 tracing::debug!("FixBadPixelsList on RGB not yet implemented");
269 }
270 }
271 DngOpcode::Unknown { id } => {
272 if !is_optional {
273 tracing::warn!("Skipping unknown required DNG opcode ID={}", id);
274 } else {
275 tracing::trace!("Skipping unknown optional DNG opcode ID={}", id);
276 }
277 }
278 }
279 }
280 }
281
282 fn read_u32_be(data: &[u8], offset: usize) -> Option<u32> {
287 data.get(offset..offset + 4)
288 .map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
289 }
290
291 fn read_f32_be(data: &[u8], offset: usize) -> Option<f32> {
292 data.get(offset..offset + 4)
293 .map(|b| f32::from_be_bytes([b[0], b[1], b[2], b[3]]))
294 }
295
296 fn read_f64_be(data: &[u8], offset: usize) -> Option<f64> {
297 data.get(offset..offset + 8)
298 .map(|b| f64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]))
299 }
300
301 fn parse_gain_map(data: &[u8]) -> Option<DngOpcode> {
302 if data.len() < 92 {
305 return None;
306 }
307
308 let top_n = Self::read_u32_be(data, 0)? as f64;
309 let top_d = Self::read_u32_be(data, 4)? as f64;
310 let left_n = Self::read_u32_be(data, 8)? as f64;
311 let left_d = Self::read_u32_be(data, 12)? as f64;
312 let bottom_n = Self::read_u32_be(data, 16)? as f64;
313 let bottom_d = Self::read_u32_be(data, 20)? as f64;
314 let right_n = Self::read_u32_be(data, 24)? as f64;
315 let right_d = Self::read_u32_be(data, 28)? as f64;
316
317 let plane = Self::read_u32_be(data, 32)?;
318 let planes = Self::read_u32_be(data, 36)?;
319 let row_pitch = Self::read_u32_be(data, 40)?;
320 let col_pitch = Self::read_u32_be(data, 44)?;
321 let map_points_v = Self::read_u32_be(data, 48)?;
322 let map_points_h = Self::read_u32_be(data, 52)?;
323 let map_spacing_v = Self::read_f64_be(data, 56)?;
324 let map_spacing_h = Self::read_f64_be(data, 64)?;
325 let map_origin_v = Self::read_f64_be(data, 72)?;
326 let map_origin_h = Self::read_f64_be(data, 80)?;
327 let map_planes = Self::read_u32_be(data, 88)?;
328
329 let gain_count = (map_points_v as usize) * (map_points_h as usize) * (map_planes as usize);
330 let gain_offset = 92;
331
332 if gain_offset + gain_count * 4 > data.len() {
333 return None;
334 }
335
336 let mut gain = Vec::with_capacity(gain_count);
337 for i in 0..gain_count {
338 gain.push(Self::read_f32_be(data, gain_offset + i * 4)?);
339 }
340
341 Some(DngOpcode::GainMap(GainMap {
342 top: if top_d != 0.0 { top_n / top_d } else { 0.0 },
343 left: if left_d != 0.0 { left_n / left_d } else { 0.0 },
344 bottom: if bottom_d != 0.0 {
345 bottom_n / bottom_d
346 } else {
347 1.0
348 },
349 right: if right_d != 0.0 {
350 right_n / right_d
351 } else {
352 1.0
353 },
354 plane,
355 planes,
356 row_pitch,
357 col_pitch,
358 map_points_v,
359 map_points_h,
360 map_spacing_v,
361 map_spacing_h,
362 map_origin_v,
363 map_origin_h,
364 map_planes,
365 gain,
366 }))
367 }
368
369 fn parse_fix_bad_pixels_constant(data: &[u8]) -> Option<DngOpcode> {
370 if data.len() < 8 {
371 return None;
372 }
373 let planes = Self::read_u32_be(data, 0)?;
374 let bad_point_value = Self::read_u32_be(data, 4)?;
375 Some(DngOpcode::FixBadPixelsConstant {
376 planes,
377 bad_point_value,
378 })
379 }
380
381 fn parse_fix_bad_pixels_list(data: &[u8]) -> Option<DngOpcode> {
382 if data.len() < 8 {
383 return None;
384 }
385 let bad_point_count = Self::read_u32_be(data, 0)? as usize;
386 let bad_rect_count = Self::read_u32_be(data, 4)? as usize;
387 let mut offset = 8;
388
389 let mut bad_points = Vec::with_capacity(bad_point_count);
390 for _ in 0..bad_point_count {
391 if offset + 8 > data.len() {
392 return None;
393 }
394 let row = Self::read_u32_be(data, offset)?;
395 let col = Self::read_u32_be(data, offset + 4)?;
396 bad_points.push((row, col));
397 offset += 8;
398 }
399
400 let mut bad_rects = Vec::with_capacity(bad_rect_count);
401 for _ in 0..bad_rect_count {
402 if offset + 16 > data.len() {
403 return None;
404 }
405 let top = Self::read_u32_be(data, offset)?;
406 let left = Self::read_u32_be(data, offset + 4)?;
407 let bottom = Self::read_u32_be(data, offset + 8)?;
408 let right = Self::read_u32_be(data, offset + 12)?;
409 bad_rects.push((top, left, bottom, right));
410 offset += 16;
411 }
412
413 Some(DngOpcode::FixBadPixelsList {
414 bad_points,
415 bad_rects,
416 })
417 }
418}
419
420#[cfg(test)]
425mod tests {
426 use super::*;
427
428 fn build_gain_map_opcode_list(gain_value: f32) -> Vec<u8> {
430 let mut data = Vec::new();
431
432 data.extend_from_slice(&1u32.to_be_bytes());
434
435 data.extend_from_slice(&9u32.to_be_bytes());
437 data.extend_from_slice(&0u32.to_be_bytes());
439 data.extend_from_slice(&1u32.to_be_bytes());
441
442 let mut params: Vec<u8> = Vec::new();
444 params.extend_from_slice(&0u32.to_be_bytes());
446 params.extend_from_slice(&1u32.to_be_bytes());
447 params.extend_from_slice(&0u32.to_be_bytes());
449 params.extend_from_slice(&1u32.to_be_bytes());
450 params.extend_from_slice(&1u32.to_be_bytes());
452 params.extend_from_slice(&1u32.to_be_bytes());
453 params.extend_from_slice(&1u32.to_be_bytes());
455 params.extend_from_slice(&1u32.to_be_bytes());
456 params.extend_from_slice(&0u32.to_be_bytes());
458 params.extend_from_slice(&3u32.to_be_bytes());
459 params.extend_from_slice(&1u32.to_be_bytes());
460 params.extend_from_slice(&1u32.to_be_bytes());
461 params.extend_from_slice(&2u32.to_be_bytes());
463 params.extend_from_slice(&2u32.to_be_bytes());
464 params.extend_from_slice(&1.0f64.to_be_bytes());
466 params.extend_from_slice(&1.0f64.to_be_bytes());
467 params.extend_from_slice(&0.0f64.to_be_bytes());
469 params.extend_from_slice(&0.0f64.to_be_bytes());
470 params.extend_from_slice(&1u32.to_be_bytes());
472 for _ in 0..4 {
474 params.extend_from_slice(&gain_value.to_be_bytes());
475 }
476
477 data.extend_from_slice(&(params.len() as u32).to_be_bytes());
479 data.extend_from_slice(¶ms);
480
481 data
482 }
483
484 #[test]
485 fn test_parse_empty() {
486 let list = OpcodeList::parse(&[]);
487 assert!(list.opcodes.is_empty());
488 }
489
490 #[test]
491 fn test_parse_gain_map_uniform() {
492 let data = build_gain_map_opcode_list(2.0);
493 let list = OpcodeList::parse(&data);
494 assert_eq!(list.opcodes.len(), 1);
495 let (op, is_optional) = &list.opcodes[0];
496 assert!(is_optional);
497 match op {
498 DngOpcode::GainMap(gm) => {
499 assert_eq!(gm.map_points_v, 2);
500 assert_eq!(gm.map_points_h, 2);
501 assert_eq!(gm.map_planes, 1);
502 assert_eq!(gm.gain.len(), 4);
503 assert!((gm.gain[0] - 2.0).abs() < 1e-6);
504 }
505 _ => panic!("Expected GainMap opcode"),
506 }
507 }
508
509 #[test]
510 fn test_apply_uniform_gain_doubles_pixel() {
511 let data = build_gain_map_opcode_list(2.0);
512 let list = OpcodeList::parse(&data);
513
514 let mut img = RgbImage::new(2, 2, vec![1000u16; 12]);
515 list.apply_to_rgb(&mut img);
516
517 for &v in &img.data {
519 assert_eq!(v, 2000, "Expected pixel value 2000, got {v}");
520 }
521 }
522
523 #[test]
524 fn test_parse_fix_bad_pixels_constant() {
525 let mut data = Vec::new();
526 data.extend_from_slice(&1u32.to_be_bytes()); data.extend_from_slice(&4u32.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); let mut params = Vec::new();
531 params.extend_from_slice(&3u32.to_be_bytes()); params.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&(params.len() as u32).to_be_bytes());
534 data.extend_from_slice(¶ms);
535
536 let list = OpcodeList::parse(&data);
537 assert_eq!(list.opcodes.len(), 1);
538 match &list.opcodes[0].0 {
539 DngOpcode::FixBadPixelsConstant {
540 planes,
541 bad_point_value,
542 } => {
543 assert_eq!(*planes, 3);
544 assert_eq!(*bad_point_value, 0);
545 }
546 _ => panic!("Expected FixBadPixelsConstant"),
547 }
548 }
549
550 #[test]
551 fn test_parse_fix_bad_pixels_list() {
552 let mut data = Vec::new();
553 data.extend_from_slice(&1u32.to_be_bytes()); data.extend_from_slice(&5u32.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&1u32.to_be_bytes()); let mut params = Vec::new();
558 params.extend_from_slice(&1u32.to_be_bytes()); params.extend_from_slice(&0u32.to_be_bytes()); params.extend_from_slice(&100u32.to_be_bytes()); params.extend_from_slice(&200u32.to_be_bytes()); data.extend_from_slice(&(params.len() as u32).to_be_bytes());
563 data.extend_from_slice(¶ms);
564
565 let list = OpcodeList::parse(&data);
566 assert_eq!(list.opcodes.len(), 1);
567 match &list.opcodes[0].0 {
568 DngOpcode::FixBadPixelsList {
569 bad_points,
570 bad_rects,
571 } => {
572 assert_eq!(bad_points.len(), 1);
573 assert_eq!(bad_points[0], (100, 200));
574 assert!(bad_rects.is_empty());
575 }
576 _ => panic!("Expected FixBadPixelsList"),
577 }
578 }
579
580 #[test]
581 fn test_parse_unknown_opcode() {
582 let mut data = Vec::new();
583 data.extend_from_slice(&1u32.to_be_bytes()); data.extend_from_slice(&42u32.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&1u32.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); let list = OpcodeList::parse(&data);
590 assert_eq!(list.opcodes.len(), 1);
591 match &list.opcodes[0].0 {
592 DngOpcode::Unknown { id } => assert_eq!(*id, 42),
593 _ => panic!("Expected Unknown opcode"),
594 }
595 }
596}