1use crate::prelude::*;
19use crate::types::{BitPackedTensor, Tensor, VsfType, WorldCoord};
20use crate::vsf_builder::VsfBuilder;
21
22#[derive(Debug, Clone)]
29pub struct CfaPattern(VsfType);
30
31impl CfaPattern {
32 pub fn new(pattern: Vec<u8>) -> Result<Self, String> {
33 if pattern.is_empty() {
35 return Err("CFA pattern cannot be empty".to_string());
36 }
37
38 for &byte in &pattern {
40 match byte {
41 b'R' | b'G' | b'B' | b'C' | b'Y' | b'W' | b'E' => {}
42 _ => return Err(format!("Invalid CFA colour code: {}", byte as char)),
43 }
44 }
45
46 Ok(CfaPattern(VsfType::t_u3(Tensor {
47 shape: vec![pattern.len()],
48 data: pattern,
49 })))
50 }
51
52 pub fn to_vsf_type(self) -> VsfType {
53 self.0
54 }
55
56 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
57 match vsf {
58 VsfType::t_u3(ref tensor) => {
59 for &byte in &tensor.data {
61 match byte {
62 b'R' | b'G' | b'B' | b'C' | b'Y' | b'W' | b'E' => {}
63 _ => return Err(format!("Invalid CFA colour code: {}", byte as char)),
64 }
65 }
66 Ok(CfaPattern(vsf))
67 }
68 _ => Err("Expected t_u3 type for CFA pattern".to_string()),
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct BlackLevel(VsfType);
76
77impl BlackLevel {
78 pub fn new(level: f32) -> Result<Self, String> {
79 if level < 0.0 {
80 return Err("Black level cannot be negative".to_string());
81 }
82 Ok(BlackLevel(VsfType::f5(level)))
83 }
84
85 pub fn to_vsf_type(self) -> VsfType {
86 self.0
87 }
88
89 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
90 match vsf {
91 VsfType::f5(v) if v >= 0.0 => Ok(BlackLevel(vsf)),
92 VsfType::f5(_) => Err("Black level cannot be negative".to_string()),
93 _ => Err("Expected f5 type for black level".to_string()),
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct WhiteLevel(VsfType);
101
102impl WhiteLevel {
103 pub fn new(level: f32) -> Result<Self, String> {
104 if level <= 0.0 {
105 return Err("White level must be positive".to_string());
106 }
107 Ok(WhiteLevel(VsfType::f5(level)))
108 }
109
110 pub fn to_vsf_type(self) -> VsfType {
111 self.0
112 }
113
114 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
115 match vsf {
116 VsfType::f5(v) if v > 0.0 => Ok(WhiteLevel(vsf)),
117 VsfType::f5(_) => Err("White level must be positive".to_string()),
118 _ => Err("Expected f5 type for white level".to_string()),
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct CalibrationHash(VsfType);
126
127impl CalibrationHash {
128 pub fn new(algorithm: u8, hash: Vec<u8>) -> Result<Self, String> {
129 use crate::crypto_algorithms::{HASH_BLAKE3, HASH_SHA256, HASH_SHA512};
130
131 if hash.is_empty() {
132 return Err("Hash cannot be empty".to_string());
133 }
134
135 let vsf_type = match algorithm {
136 HASH_BLAKE3 => VsfType::hb(hash),
137 HASH_SHA256 | HASH_SHA512 => VsfType::hs(hash),
138 _ => return Err(format!("Unsupported hash algorithm: {}", algorithm as char)),
139 };
140
141 Ok(CalibrationHash(vsf_type))
142 }
143
144 pub fn to_vsf_type(self) -> VsfType {
145 self.0
146 }
147
148 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
149 match vsf {
150 VsfType::hb(ref hash) | VsfType::hs(ref hash) if !hash.is_empty() => {
151 Ok(CalibrationHash(vsf))
152 }
153 VsfType::hb(_) | VsfType::hs(_) => Err("Hash cannot be empty".to_string()),
154 _ => Err("Expected hash type for calibration hash".to_string()),
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
161pub struct Magic9(VsfType);
162
163impl Magic9 {
164 pub fn new(values: Vec<f32>) -> Result<Self, String> {
165 if values.len() != 9 {
166 return Err(format!(
167 "Magic 9 matrix must have exactly 9 elements, got {}",
168 values.len()
169 ));
170 }
171 Ok(Magic9(VsfType::t_f5(Tensor {
172 shape: vec![3, 3],
173 data: values,
174 })))
175 }
176
177 pub fn to_vsf_type(self) -> VsfType {
178 self.0
179 }
180
181 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
182 match vsf {
183 VsfType::t_f5(ref tensor) => {
184 if tensor.data.len() != 9 {
185 return Err(format!(
186 "Magic 9 matrix must have exactly 9 elements, got {}",
187 tensor.data.len()
188 ));
189 }
190 Ok(Magic9(vsf))
191 }
192 _ => Err("Expected t_f5 type for Magic 9 matrix".to_string()),
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
199pub struct IsoSpeed(VsfType);
200
201impl IsoSpeed {
202 pub fn new(iso: f32) -> Result<Self, String> {
203 if iso <= 0.0 {
204 return Err("ISO speed must be positive".to_string());
205 }
206 Ok(IsoSpeed(VsfType::f5(iso)))
207 }
208
209 pub fn to_vsf_type(self) -> VsfType {
210 self.0
211 }
212
213 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
214 match vsf {
215 VsfType::f5(v) if v > 0.0 => Ok(IsoSpeed(vsf)),
216 VsfType::f5(_) => Err("ISO speed must be positive".to_string()),
217 _ => Err("Expected f5 type for ISO speed".to_string()),
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct ShutterTime(VsfType);
225
226impl ShutterTime {
227 pub fn new(seconds: f32) -> Result<Self, String> {
228 if seconds <= 0.0 {
229 return Err("Shutter time must be positive".to_string());
230 }
231 Ok(ShutterTime(VsfType::f5(seconds)))
232 }
233
234 pub fn to_vsf_type(self) -> VsfType {
235 self.0
236 }
237
238 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
239 match vsf {
240 VsfType::f5(v) if v > 0.0 => Ok(ShutterTime(vsf)),
241 VsfType::f5(_) => Err("Shutter time must be positive".to_string()),
242 _ => Err("Expected f5 type for shutter time".to_string()),
243 }
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct Aperture(VsfType);
250
251impl Aperture {
252 pub fn new(f_number: f32) -> Result<Self, String> {
253 if f_number <= 0.0 {
254 return Err("Aperture f-number must be positive".to_string());
255 }
256 Ok(Aperture(VsfType::f5(f_number)))
257 }
258
259 pub fn to_vsf_type(self) -> VsfType {
260 self.0
261 }
262
263 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
264 match vsf {
265 VsfType::f5(v) if v > 0.0 => Ok(Aperture(vsf)),
266 VsfType::f5(_) => Err("Aperture f-number must be positive".to_string()),
267 _ => Err("Expected f5 type for aperture".to_string()),
268 }
269 }
270}
271
272#[derive(Debug, Clone)]
274pub struct FocalLength(VsfType);
275
276impl FocalLength {
277 pub fn new(meters: f32) -> Result<Self, String> {
278 if meters <= 0.0 {
279 return Err("Focal length must be positive".to_string());
280 }
281 Ok(FocalLength(VsfType::f5(meters)))
282 }
283
284 pub fn to_vsf_type(self) -> VsfType {
285 self.0
286 }
287
288 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
289 match vsf {
290 VsfType::f5(v) if v > 0.0 => Ok(FocalLength(vsf)),
291 VsfType::f5(_) => Err("Focal length must be positive".to_string()),
292 _ => Err("Expected f5 type for focal length".to_string()),
293 }
294 }
295}
296
297#[derive(Debug, Clone)]
299pub struct ExposureCompensation(VsfType);
300
301impl ExposureCompensation {
302 pub fn new(ev: f32) -> Result<Self, String> {
303 Ok(ExposureCompensation(VsfType::f5(ev))) }
305
306 pub fn to_vsf_type(self) -> VsfType {
307 self.0
308 }
309
310 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
311 match vsf {
312 VsfType::f5(_) => Ok(ExposureCompensation(vsf)),
313 _ => Err("Expected f5 type for exposure compensation".to_string()),
314 }
315 }
316}
317
318#[derive(Debug, Clone)]
320pub struct FocusDistance(VsfType);
321
322impl FocusDistance {
323 pub fn new(meters: f32) -> Result<Self, String> {
324 if meters < 0.0 {
325 return Err("Focus distance cannot be negative".to_string());
326 }
327 Ok(FocusDistance(VsfType::f5(meters)))
328 }
329
330 pub fn to_vsf_type(self) -> VsfType {
331 self.0
332 }
333
334 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
335 match vsf {
336 VsfType::f5(v) if v >= 0.0 => Ok(FocusDistance(vsf)),
337 VsfType::f5(_) => Err("Focus distance cannot be negative".to_string()),
338 _ => Err("Expected f5 type for focus distance".to_string()),
339 }
340 }
341}
342
343#[derive(Debug, Clone)]
345pub struct FlashFired(VsfType);
346
347impl FlashFired {
348 pub fn new(fired: bool) -> Result<Self, String> {
349 Ok(FlashFired(VsfType::u0(fired)))
350 }
351
352 pub fn to_vsf_type(self) -> VsfType {
353 self.0
354 }
355
356 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
357 match vsf {
358 VsfType::u0(_) => Ok(FlashFired(vsf)),
359 _ => Err("Expected u0 type for flash fired".to_string()),
360 }
361 }
362}
363
364#[derive(Debug, Clone)]
366pub struct MeteringMode(VsfType);
367
368impl MeteringMode {
369 pub fn new(mode: String) -> Result<Self, String> {
370 if mode.is_empty() {
371 return Err("Metering mode cannot be empty".to_string());
372 }
373 Ok(MeteringMode(VsfType::x(mode)))
374 }
375
376 pub fn to_vsf_type(self) -> VsfType {
377 self.0
378 }
379
380 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
381 match vsf {
382 VsfType::x(ref s) if !s.is_empty() => Ok(MeteringMode(vsf)),
383 VsfType::x(_) => Err("Metering mode cannot be empty".to_string()),
384 _ => Err("Expected x type for metering mode".to_string()),
385 }
386 }
387}
388
389#[derive(Debug, Clone)]
391pub struct Manufacturer(VsfType);
392
393impl Manufacturer {
394 pub fn new(name: String) -> Result<Self, String> {
395 if name.is_empty() {
396 return Err("Manufacturer name cannot be empty".to_string());
397 }
398 Ok(Manufacturer(VsfType::x(name)))
399 }
400
401 pub fn to_vsf_type(self) -> VsfType {
402 self.0
403 }
404
405 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
406 match vsf {
407 VsfType::x(ref s) if !s.is_empty() => Ok(Manufacturer(vsf)),
408 VsfType::x(_) => Err("Manufacturer name cannot be empty".to_string()),
409 _ => Err("Expected x type for manufacturer".to_string()),
410 }
411 }
412}
413
414#[derive(Debug, Clone)]
416pub struct ModelName(VsfType);
417
418impl ModelName {
419 pub fn new(name: String) -> Result<Self, String> {
420 if name.is_empty() {
421 return Err("Model name cannot be empty".to_string());
422 }
423 Ok(ModelName(VsfType::x(name)))
424 }
425
426 pub fn to_vsf_type(self) -> VsfType {
427 self.0
428 }
429
430 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
431 match vsf {
432 VsfType::x(ref s) if !s.is_empty() => Ok(ModelName(vsf)),
433 VsfType::x(_) => Err("Model name cannot be empty".to_string()),
434 _ => Err("Expected x type for model name".to_string()),
435 }
436 }
437}
438
439#[derive(Debug, Clone)]
441pub struct SerialNumber(VsfType);
442
443impl SerialNumber {
444 pub fn new(serial: String) -> Result<Self, String> {
445 if serial.is_empty() {
446 return Err("Serial number cannot be empty".to_string());
447 }
448 Ok(SerialNumber(VsfType::x(serial)))
449 }
450
451 pub fn to_vsf_type(self) -> VsfType {
452 self.0
453 }
454
455 pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
456 match vsf {
457 VsfType::x(ref s) if !s.is_empty() => Ok(SerialNumber(vsf)),
458 VsfType::x(_) => Err("Serial number cannot be empty".to_string()),
459 _ => Err("Expected x type for serial number".to_string()),
460 }
461 }
462}
463
464#[derive(Debug, Clone)]
468pub struct RawMetadata {
469 pub cfa_pattern: Option<CfaPattern>,
471 pub black_level: Option<BlackLevel>,
472 pub white_level: Option<WhiteLevel>,
473
474 pub dark_frame_hash: Option<CalibrationHash>,
476 pub flat_field_hash: Option<CalibrationHash>,
477 pub bias_frame_hash: Option<CalibrationHash>,
478 pub vignette_correction_hash: Option<CalibrationHash>,
479 pub distortion_correction_hash: Option<CalibrationHash>,
480
481 pub magic_9: Option<Magic9>,
483}
484
485#[derive(Debug, Clone)]
487pub struct CameraSettings {
488 pub make: Option<Manufacturer>,
489 pub model: Option<ModelName>,
490 pub serial_number: Option<SerialNumber>,
491 pub iso_speed: Option<IsoSpeed>,
492 pub shutter_time_s: Option<ShutterTime>,
493 pub aperture_f_number: Option<Aperture>,
494 pub focal_length_m: Option<FocalLength>,
495 pub exposure_compensation: Option<ExposureCompensation>,
496 pub focus_distance_m: Option<FocusDistance>,
497 pub flash_fired: Option<FlashFired>,
498 pub metering_mode: Option<MeteringMode>,
499 }
501
502#[derive(Debug, Clone)]
504pub struct LensInfo {
505 pub make: Option<Manufacturer>,
506 pub model: Option<ModelName>,
507 pub serial_number: Option<SerialNumber>,
508 pub min_focal_length_m: Option<FocalLength>,
509 pub max_focal_length_m: Option<FocalLength>,
510 pub min_aperture_f: Option<Aperture>, pub max_aperture_f: Option<Aperture>, }
513
514#[derive(Debug, Clone, Default)]
518pub struct RawMetadataBuilder {
519 pub cfa_pattern: Option<Vec<u8>>,
520 pub black_level: Option<f32>,
521 pub white_level: Option<f32>,
522 pub dark_frame_hash: Option<(u8, Vec<u8>)>,
523 pub flat_field_hash: Option<(u8, Vec<u8>)>,
524 pub bias_frame_hash: Option<(u8, Vec<u8>)>,
525 pub vignette_correction_hash: Option<(u8, Vec<u8>)>,
526 pub distortion_correction_hash: Option<(u8, Vec<u8>)>,
527 pub magic_9: Option<Vec<f32>>,
528}
529
530impl RawMetadataBuilder {
531 fn build(self) -> Result<Option<RawMetadata>, String> {
533 if self.cfa_pattern.is_none()
534 && self.black_level.is_none()
535 && self.white_level.is_none()
536 && self.dark_frame_hash.is_none()
537 && self.flat_field_hash.is_none()
538 && self.bias_frame_hash.is_none()
539 && self.vignette_correction_hash.is_none()
540 && self.distortion_correction_hash.is_none()
541 && self.magic_9.is_none()
542 {
543 return Ok(None);
544 }
545
546 Ok(Some(RawMetadata {
547 cfa_pattern: self.cfa_pattern.map(CfaPattern::new).transpose()?,
548 black_level: self.black_level.map(BlackLevel::new).transpose()?,
549 white_level: self.white_level.map(WhiteLevel::new).transpose()?,
550 dark_frame_hash: self
551 .dark_frame_hash
552 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
553 .transpose()?,
554 flat_field_hash: self
555 .flat_field_hash
556 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
557 .transpose()?,
558 bias_frame_hash: self
559 .bias_frame_hash
560 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
561 .transpose()?,
562 vignette_correction_hash: self
563 .vignette_correction_hash
564 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
565 .transpose()?,
566 distortion_correction_hash: self
567 .distortion_correction_hash
568 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
569 .transpose()?,
570 magic_9: self.magic_9.map(Magic9::new).transpose()?,
571 }))
572 }
573}
574
575#[derive(Debug, Clone, Default)]
577pub struct CameraBuilder {
578 pub make: Option<String>,
579 pub model: Option<String>,
580 pub serial_number: Option<String>,
581 pub iso_speed: Option<f32>,
582 pub shutter_time_s: Option<f32>,
583 pub aperture_f_number: Option<f32>,
584 pub focal_length_m: Option<f32>,
585 pub exposure_compensation: Option<f32>,
586 pub focus_distance_m: Option<f32>,
587 pub flash_fired: Option<bool>,
588 pub metering_mode: Option<String>,
589}
590
591impl CameraBuilder {
592 fn build(self) -> Result<Option<CameraSettings>, String> {
594 if self.make.is_none()
595 && self.model.is_none()
596 && self.serial_number.is_none()
597 && self.iso_speed.is_none()
598 && self.shutter_time_s.is_none()
599 && self.aperture_f_number.is_none()
600 && self.focal_length_m.is_none()
601 && self.exposure_compensation.is_none()
602 && self.focus_distance_m.is_none()
603 && self.flash_fired.is_none()
604 && self.metering_mode.is_none()
605 {
606 return Ok(None);
607 }
608
609 Ok(Some(CameraSettings {
610 make: self.make.map(Manufacturer::new).transpose()?,
611 model: self.model.map(ModelName::new).transpose()?,
612 serial_number: self.serial_number.map(SerialNumber::new).transpose()?,
613 iso_speed: self.iso_speed.map(IsoSpeed::new).transpose()?,
614 shutter_time_s: self.shutter_time_s.map(ShutterTime::new).transpose()?,
615 aperture_f_number: self.aperture_f_number.map(Aperture::new).transpose()?,
616 focal_length_m: self.focal_length_m.map(FocalLength::new).transpose()?,
617 exposure_compensation: self
618 .exposure_compensation
619 .map(ExposureCompensation::new)
620 .transpose()?,
621 focus_distance_m: self.focus_distance_m.map(FocusDistance::new).transpose()?,
622 flash_fired: self.flash_fired.map(FlashFired::new).transpose()?,
623 metering_mode: self.metering_mode.map(MeteringMode::new).transpose()?,
624 }))
625 }
626}
627
628#[derive(Debug, Clone, Default)]
630pub struct LensBuilder {
631 pub make: Option<String>,
632 pub model: Option<String>,
633 pub serial_number: Option<String>,
634 pub min_focal_length_m: Option<f32>,
635 pub max_focal_length_m: Option<f32>,
636 pub min_aperture_f: Option<f32>,
637 pub max_aperture_f: Option<f32>,
638}
639
640impl LensBuilder {
641 fn build(self) -> Result<Option<LensInfo>, String> {
643 if self.make.is_none()
644 && self.model.is_none()
645 && self.serial_number.is_none()
646 && self.min_focal_length_m.is_none()
647 && self.max_focal_length_m.is_none()
648 && self.min_aperture_f.is_none()
649 && self.max_aperture_f.is_none()
650 {
651 return Ok(None);
652 }
653
654 Ok(Some(LensInfo {
655 make: self.make.map(Manufacturer::new).transpose()?,
656 model: self.model.map(ModelName::new).transpose()?,
657 serial_number: self.serial_number.map(SerialNumber::new).transpose()?,
658 min_focal_length_m: self.min_focal_length_m.map(FocalLength::new).transpose()?,
659 max_focal_length_m: self.max_focal_length_m.map(FocalLength::new).transpose()?,
660 min_aperture_f: self.min_aperture_f.map(Aperture::new).transpose()?,
661 max_aperture_f: self.max_aperture_f.map(Aperture::new).transpose()?,
662 }))
663 }
664}
665
666#[derive(Debug, Clone)]
679pub struct RawImageBuilder {
680 image: BitPackedTensor,
681 pub raw: RawMetadataBuilder,
682 pub camera: CameraBuilder,
683 pub lens: LensBuilder,
684}
685
686impl RawImageBuilder {
687 pub fn new(image: BitPackedTensor) -> Self {
689 Self {
690 image,
691 raw: RawMetadataBuilder::default(),
692 camera: CameraBuilder::default(),
693 lens: LensBuilder::default(),
694 }
695 }
696
697 pub fn build(self) -> Result<Vec<u8>, String> {
699 let metadata = self.raw.build()?;
700 let camera = self.camera.build()?;
701 let lens = self.lens.build()?;
702
703 build_raw_image(self.image, metadata, camera, lens)
704 }
705}
706
707pub fn raw_image(bit_depth: u8, width: usize, height: usize, samples: Vec<u64>) -> VsfType {
721 let tensor = BitPackedTensor::pack(bit_depth, vec![width, height], &samples);
722 VsfType::p(tensor)
723}
724
725pub fn gps_track(coords: Vec<(f64, f64)>) -> Vec<WorldCoord> {
734 coords
735 .into_iter()
736 .map(|(lat, lon)| WorldCoord::from_lat_lon(lat, lon))
737 .collect()
738}
739
740pub fn gps_waypoint(lat: f64, lon: f64) -> WorldCoord {
747 WorldCoord::from_lat_lon(lat, lon)
748}
749
750pub fn geotagged_photo(
759 width: usize,
760 height: usize,
761 rgb_data: Vec<u8>,
762 lat: f64,
763 lon: f64,
764) -> (VsfType, WorldCoord) {
765 let tensor = Tensor::new(vec![width, height, 3], rgb_data);
766 let img = VsfType::t_u3(tensor);
767 let loc = WorldCoord::from_lat_lon(lat, lon);
768 (img, loc)
769}
770
771pub fn build_raw_image(
801 image: BitPackedTensor,
802 metadata: Option<RawMetadata>,
803 camera: Option<CameraSettings>,
804 lens: Option<LensInfo>,
805) -> Result<Vec<u8>, String> {
806 let mut builder = VsfBuilder::new();
807
808 let mut raw_items = vec![("image".to_string(), VsfType::p(image))];
810
811 if let Some(meta) = metadata {
813 if let Some(cfa) = meta.cfa_pattern {
814 raw_items.push(("cfa_pattern".to_string(), cfa.to_vsf_type()));
815 }
816
817 if let Some(black) = meta.black_level {
818 raw_items.push(("black_level".to_string(), black.to_vsf_type()));
819 }
820
821 if let Some(white) = meta.white_level {
822 raw_items.push(("white_level".to_string(), white.to_vsf_type()));
823 }
824
825 if let Some(hash) = meta.dark_frame_hash {
827 raw_items.push(("dark_frame_hash".to_string(), hash.to_vsf_type()));
828 }
829
830 if let Some(hash) = meta.flat_field_hash {
831 raw_items.push(("flat_field_hash".to_string(), hash.to_vsf_type()));
832 }
833
834 if let Some(hash) = meta.bias_frame_hash {
835 raw_items.push(("bias_frame_hash".to_string(), hash.to_vsf_type()));
836 }
837
838 if let Some(hash) = meta.vignette_correction_hash {
839 raw_items.push(("vignette_correction_hash".to_string(), hash.to_vsf_type()));
840 }
841
842 if let Some(hash) = meta.distortion_correction_hash {
843 raw_items.push(("distortion_correction_hash".to_string(), hash.to_vsf_type()));
844 }
845
846 if let Some(matrix) = meta.magic_9 {
848 raw_items.push(("magic_9".to_string(), matrix.to_vsf_type()));
849 }
850 }
851
852 if let Some(cam) = camera {
854 if let Some(make) = cam.make {
855 raw_items.push(("camera_make".to_string(), make.to_vsf_type()));
856 }
857
858 if let Some(model) = cam.model {
859 raw_items.push(("camera_model".to_string(), model.to_vsf_type()));
860 }
861
862 if let Some(serial) = cam.serial_number {
863 raw_items.push(("camera_serial".to_string(), serial.to_vsf_type()));
864 }
865
866 if let Some(iso) = cam.iso_speed {
867 raw_items.push(("iso_speed".to_string(), iso.to_vsf_type()));
868 }
869
870 if let Some(shutter) = cam.shutter_time_s {
871 raw_items.push(("shutter_time_s".to_string(), shutter.to_vsf_type()));
872 }
873
874 if let Some(aperture) = cam.aperture_f_number {
875 raw_items.push(("aperture_f_number".to_string(), aperture.to_vsf_type()));
876 }
877
878 if let Some(focal) = cam.focal_length_m {
879 raw_items.push(("focal_length_m".to_string(), focal.to_vsf_type()));
880 }
881
882 if let Some(comp) = cam.exposure_compensation {
883 raw_items.push(("exposure_compensation".to_string(), comp.to_vsf_type()));
884 }
885
886 if let Some(focus) = cam.focus_distance_m {
887 raw_items.push(("focus_distance_m".to_string(), focus.to_vsf_type()));
888 }
889
890 if let Some(flash) = cam.flash_fired {
891 raw_items.push(("flash_fired".to_string(), flash.to_vsf_type()));
892 }
893
894 if let Some(metering) = cam.metering_mode {
895 raw_items.push(("metering_mode".to_string(), metering.to_vsf_type()));
896 }
897 }
898
899 if let Some(l) = lens {
901 if let Some(make) = l.make {
902 raw_items.push(("lens_make".to_string(), make.to_vsf_type()));
903 }
904
905 if let Some(model) = l.model {
906 raw_items.push(("lens_model".to_string(), model.to_vsf_type()));
907 }
908
909 if let Some(serial) = l.serial_number {
910 raw_items.push(("lens_serial".to_string(), serial.to_vsf_type()));
911 }
912
913 if let Some(min_focal) = l.min_focal_length_m {
914 raw_items.push(("lens_min_focal_m".to_string(), min_focal.to_vsf_type()));
915 }
916
917 if let Some(max_focal) = l.max_focal_length_m {
918 raw_items.push(("lens_max_focal_m".to_string(), max_focal.to_vsf_type()));
919 }
920
921 if let Some(min_ap) = l.min_aperture_f {
922 raw_items.push(("lens_min_aperture".to_string(), min_ap.to_vsf_type()));
923 }
924
925 if let Some(max_ap) = l.max_aperture_f {
926 raw_items.push(("lens_max_aperture".to_string(), max_ap.to_vsf_type()));
927 }
928 }
929
930 builder = builder.add_section("raw", raw_items);
931
932 builder.build()
933}
934
935pub fn lumis_raw_capture(samples: Vec<u64>, iso: f32, shutter_s: f32) -> Result<Vec<u8>, String> {
957 let image = BitPackedTensor::pack(12, vec![4096, 3072], &samples);
959
960 build_raw_image(
961 image,
962 Some(RawMetadata {
963 cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B'])?), black_level: Some(BlackLevel::new(64.0)?),
965 white_level: Some(WhiteLevel::new(4095.0)?),
966 dark_frame_hash: None,
967 flat_field_hash: None,
968 bias_frame_hash: None,
969 vignette_correction_hash: None,
970 distortion_correction_hash: None,
971 magic_9: None,
972 }),
973 Some(CameraSettings {
974 make: None,
975 model: None,
976 serial_number: None,
977 iso_speed: Some(IsoSpeed::new(iso)?),
978 shutter_time_s: Some(ShutterTime::new(shutter_s)?),
979 aperture_f_number: None,
980 focal_length_m: None,
981 exposure_compensation: None,
982 focus_distance_m: None,
983 flash_fired: Some(FlashFired::new(false)?),
984 metering_mode: None,
985 }),
986 None, )
988}
989
990pub const ENCODING_AV1: u8 = b'a';
994
995pub fn compressed_image(av1_data: Vec<u8>) -> Result<Vec<u8>, String> {
1011 VsfBuilder::new()
1012 .provenance_only()
1013 .add_section(
1014 "image",
1015 vec![("pixels".to_string(), VsfType::v(ENCODING_AV1, av1_data))],
1016 )
1017 .build()
1018}
1019
1020pub struct ParsedCompressedImage {
1022 pub encoding: u8,
1023 pub data: Vec<u8>,
1024}
1025
1026pub fn parse_compressed_image(data: &[u8]) -> Result<ParsedCompressedImage, String> {
1036 use crate::file_format::{VsfHeader, VsfSection};
1037
1038 let (header, _) = VsfHeader::decode(data)?;
1040
1041 let image_field = header
1043 .fields
1044 .iter()
1045 .find(|f| f.name == "image")
1046 .ok_or("Required 'image' section not found")?;
1047
1048 let mut ptr = image_field.offset_bytes;
1050 let section = VsfSection::parse(data, &mut ptr)?;
1051
1052 let pixels_field = section
1054 .get_field("pixels")
1055 .ok_or("Missing 'pixels' field in image section")?;
1056
1057 let value = pixels_field.values.first().ok_or("Empty 'pixels' field")?;
1059
1060 match value {
1061 VsfType::v(encoding, pixel_data) => Ok(ParsedCompressedImage {
1062 encoding: *encoding,
1063 data: pixel_data.clone(),
1064 }),
1065 _ => Err("Expected v type for pixels field".to_string()),
1066 }
1067}
1068
1069pub struct ParsedRawImage {
1073 pub image: BitPackedTensor,
1074 pub metadata: Option<RawMetadata>,
1075 pub camera: Option<CameraSettings>,
1076 pub lens: Option<LensInfo>,
1077}
1078
1079fn to_usize(vsf_type: &VsfType) -> Option<usize> {
1081 match vsf_type {
1082 VsfType::u(v, _) => Some(*v), VsfType::u0(b) => Some(*b as usize), VsfType::u3(v) => Some(*v as usize), VsfType::u4(v) => Some(*v as usize), VsfType::u5(v) => Some(*v as usize), VsfType::u6(v) => Some(*v as usize), VsfType::u7(v) => Some(*v as usize), _ => None,
1090 }
1091}
1092
1093pub fn parse_raw_image(data: &[u8]) -> Result<ParsedRawImage, String> {
1103 use crate::crypto_algorithms::{HASH_BLAKE3, HASH_SHA256, HASH_SHA512};
1104 use crate::file_format::{VsfHeader, VsfSection};
1105
1106 let (header, _) = VsfHeader::decode(data)?;
1108
1109 let raw_field = header
1111 .fields
1112 .iter()
1113 .find(|f| f.name == "raw")
1114 .ok_or("Required 'raw' section not found")?;
1115
1116 let mut ptr = raw_field.offset_bytes;
1118 let section = VsfSection::parse(data, &mut ptr)?;
1119
1120 fn get_first_value<'a>(section: &'a VsfSection, name: &str) -> Option<&'a VsfType> {
1122 section.get_field(name)?.values.first()
1123 }
1124
1125 let image = match get_first_value(§ion, "image") {
1127 Some(VsfType::p(tensor)) => tensor.clone(),
1128 _ => return Err("Missing required 'image' field".to_string()),
1129 };
1130
1131 let mut cfa_pattern: Option<Vec<u8>> = None;
1133 let mut black_level: Option<f32> = None;
1134 let mut white_level: Option<f32> = None;
1135 let mut dark_frame_hash: Option<(u8, Vec<u8>)> = None;
1136 let mut flat_field_hash: Option<(u8, Vec<u8>)> = None;
1137 let mut bias_frame_hash: Option<(u8, Vec<u8>)> = None;
1138 let mut vignette_correction_hash: Option<(u8, Vec<u8>)> = None;
1139 let mut distortion_correction_hash: Option<(u8, Vec<u8>)> = None;
1140 let mut magic_9: Option<Vec<f32>> = None;
1141
1142 let mut camera_make: Option<String> = None;
1143 let mut camera_model: Option<String> = None;
1144 let mut camera_serial: Option<String> = None;
1145 let mut iso_speed: Option<f32> = None;
1146 let mut shutter_time_s: Option<f32> = None;
1147 let mut aperture_f_number: Option<f32> = None;
1148 let mut focal_length_m: Option<f32> = None;
1149 let mut exposure_compensation: Option<f32> = None;
1150 let mut focus_distance_m: Option<f32> = None;
1151 let mut flash_fired: Option<bool> = None;
1152 let mut metering_mode: Option<String> = None;
1153
1154 let mut lens_make: Option<String> = None;
1155 let mut lens_model: Option<String> = None;
1156 let mut lens_serial: Option<String> = None;
1157 let mut lens_min_focal_m: Option<f32> = None;
1158 let mut lens_max_focal_m: Option<f32> = None;
1159 let mut lens_min_aperture: Option<f32> = None;
1160 let mut lens_max_aperture: Option<f32> = None;
1161
1162 fn parse_hash(value: &VsfType) -> Option<(u8, Vec<u8>)> {
1164 match value {
1165 VsfType::hb(v) => Some((HASH_BLAKE3, v.clone())),
1166 VsfType::hs(v) => {
1167 let algo = if v.len() == 32 {
1168 HASH_SHA256
1169 } else {
1170 HASH_SHA512
1171 };
1172 Some((algo, v.clone()))
1173 }
1174 _ => None,
1175 }
1176 }
1177
1178 for field in §ion.fields {
1180 let value = match field.values.first() {
1181 Some(v) => v,
1182 None => continue,
1183 };
1184
1185 match field.name.as_str() {
1186 "cfa_pattern" => {
1188 if let VsfType::t_u3(tensor) = value {
1189 cfa_pattern = Some(tensor.data.clone());
1190 }
1191 }
1192 "black_level" => {
1193 if let VsfType::f5(v) = value {
1194 black_level = Some(*v);
1195 }
1196 }
1197 "white_level" => {
1198 if let VsfType::f5(v) = value {
1199 white_level = Some(*v);
1200 }
1201 }
1202 "dark_frame_hash" => dark_frame_hash = parse_hash(value),
1203 "flat_field_hash" => flat_field_hash = parse_hash(value),
1204 "bias_frame_hash" => bias_frame_hash = parse_hash(value),
1205 "vignette_correction_hash" => vignette_correction_hash = parse_hash(value),
1206 "distortion_correction_hash" => distortion_correction_hash = parse_hash(value),
1207 "magic_9" => {
1208 if let VsfType::t_f5(tensor) = value {
1209 magic_9 = Some(tensor.data.clone());
1210 }
1211 }
1212 "camera_make" => {
1214 if let VsfType::x(v) = value {
1215 camera_make = Some(v.clone());
1216 }
1217 }
1218 "camera_model" => {
1219 if let VsfType::x(v) = value {
1220 camera_model = Some(v.clone());
1221 }
1222 }
1223 "camera_serial" => {
1224 if let VsfType::x(v) = value {
1225 camera_serial = Some(v.clone());
1226 }
1227 }
1228 "iso_speed" => {
1229 if let VsfType::f5(v) = value {
1230 iso_speed = Some(*v);
1231 }
1232 }
1233 "shutter_time_s" => {
1234 if let VsfType::f5(v) = value {
1235 shutter_time_s = Some(*v);
1236 }
1237 }
1238 "aperture_f_number" => {
1239 if let VsfType::f5(v) = value {
1240 aperture_f_number = Some(*v);
1241 }
1242 }
1243 "focal_length_m" => {
1244 if let VsfType::f5(v) = value {
1245 focal_length_m = Some(*v);
1246 }
1247 }
1248 "exposure_compensation" => {
1249 if let VsfType::f5(v) = value {
1250 exposure_compensation = Some(*v);
1251 }
1252 }
1253 "focus_distance_m" => {
1254 if let VsfType::f5(v) = value {
1255 focus_distance_m = Some(*v);
1256 }
1257 }
1258 "flash_fired" => flash_fired = to_usize(value).map(|v| v != 0),
1259 "metering_mode" => {
1260 if let VsfType::x(v) = value {
1261 metering_mode = Some(v.clone());
1262 }
1263 }
1264 "lens_make" => {
1266 if let VsfType::x(v) = value {
1267 lens_make = Some(v.clone());
1268 }
1269 }
1270 "lens_model" => {
1271 if let VsfType::x(v) = value {
1272 lens_model = Some(v.clone());
1273 }
1274 }
1275 "lens_serial" => {
1276 if let VsfType::x(v) = value {
1277 lens_serial = Some(v.clone());
1278 }
1279 }
1280 "lens_min_focal_m" => {
1281 if let VsfType::f5(v) = value {
1282 lens_min_focal_m = Some(*v);
1283 }
1284 }
1285 "lens_max_focal_m" => {
1286 if let VsfType::f5(v) = value {
1287 lens_max_focal_m = Some(*v);
1288 }
1289 }
1290 "lens_min_aperture" => {
1291 if let VsfType::f5(v) = value {
1292 lens_min_aperture = Some(*v);
1293 }
1294 }
1295 "lens_max_aperture" => {
1296 if let VsfType::f5(v) = value {
1297 lens_max_aperture = Some(*v);
1298 }
1299 }
1300 _ => {} }
1302 }
1303
1304 let raw_metadata = if cfa_pattern.is_some()
1306 || black_level.is_some()
1307 || white_level.is_some()
1308 || dark_frame_hash.is_some()
1309 || flat_field_hash.is_some()
1310 || bias_frame_hash.is_some()
1311 || vignette_correction_hash.is_some()
1312 || distortion_correction_hash.is_some()
1313 || magic_9.is_some()
1314 {
1315 Some(RawMetadata {
1316 cfa_pattern: cfa_pattern.map(CfaPattern::new).transpose()?,
1317 black_level: black_level.map(BlackLevel::new).transpose()?,
1318 white_level: white_level.map(WhiteLevel::new).transpose()?,
1319 dark_frame_hash: dark_frame_hash
1320 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
1321 .transpose()?,
1322 flat_field_hash: flat_field_hash
1323 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
1324 .transpose()?,
1325 bias_frame_hash: bias_frame_hash
1326 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
1327 .transpose()?,
1328 vignette_correction_hash: vignette_correction_hash
1329 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
1330 .transpose()?,
1331 distortion_correction_hash: distortion_correction_hash
1332 .map(|(alg, hash)| CalibrationHash::new(alg, hash))
1333 .transpose()?,
1334 magic_9: magic_9.map(Magic9::new).transpose()?,
1335 })
1336 } else {
1337 None
1338 };
1339
1340 let camera_settings = if camera_make.is_some()
1341 || camera_model.is_some()
1342 || camera_serial.is_some()
1343 || iso_speed.is_some()
1344 || shutter_time_s.is_some()
1345 || aperture_f_number.is_some()
1346 || focal_length_m.is_some()
1347 || exposure_compensation.is_some()
1348 || focus_distance_m.is_some()
1349 || flash_fired.is_some()
1350 || metering_mode.is_some()
1351 {
1352 Some(CameraSettings {
1353 make: camera_make.map(Manufacturer::new).transpose()?,
1354 model: camera_model.map(ModelName::new).transpose()?,
1355 serial_number: camera_serial.map(SerialNumber::new).transpose()?,
1356 iso_speed: iso_speed.map(IsoSpeed::new).transpose()?,
1357 shutter_time_s: shutter_time_s.map(ShutterTime::new).transpose()?,
1358 aperture_f_number: aperture_f_number.map(Aperture::new).transpose()?,
1359 focal_length_m: focal_length_m.map(FocalLength::new).transpose()?,
1360 exposure_compensation: exposure_compensation
1361 .map(ExposureCompensation::new)
1362 .transpose()?,
1363 focus_distance_m: focus_distance_m.map(FocusDistance::new).transpose()?,
1364 flash_fired: flash_fired.map(FlashFired::new).transpose()?,
1365 metering_mode: metering_mode.map(MeteringMode::new).transpose()?,
1366 })
1367 } else {
1368 None
1369 };
1370
1371 let lens_info = if lens_make.is_some()
1372 || lens_model.is_some()
1373 || lens_serial.is_some()
1374 || lens_min_focal_m.is_some()
1375 || lens_max_focal_m.is_some()
1376 || lens_min_aperture.is_some()
1377 || lens_max_aperture.is_some()
1378 {
1379 Some(LensInfo {
1380 make: lens_make.map(Manufacturer::new).transpose()?,
1381 model: lens_model.map(ModelName::new).transpose()?,
1382 serial_number: lens_serial.map(SerialNumber::new).transpose()?,
1383 min_focal_length_m: lens_min_focal_m.map(FocalLength::new).transpose()?,
1384 max_focal_length_m: lens_max_focal_m.map(FocalLength::new).transpose()?,
1385 min_aperture_f: lens_min_aperture.map(Aperture::new).transpose()?,
1386 max_aperture_f: lens_max_aperture.map(Aperture::new).transpose()?,
1387 })
1388 } else {
1389 None
1390 };
1391
1392 Ok(ParsedRawImage {
1393 image,
1394 metadata: raw_metadata,
1395 camera: camera_settings,
1396 lens: lens_info,
1397 })
1398}
1399
1400#[cfg(test)]
1401mod tests {
1402 use super::*;
1403 use crate::crypto_algorithms::HASH_BLAKE3;
1404
1405 #[test]
1406 fn test_text_document() {
1407 let doc = VsfType::x("Hello, VSF!".to_string());
1408 if let VsfType::x(s) = doc {
1409 assert_eq!(s, "Hello, VSF!");
1410 } else {
1411 panic!("Expected string");
1412 }
1413 }
1414
1415 #[test]
1416 fn test_raw_image_12bit() {
1417 let samples = vec![2048u64; 100 * 50]; let img = raw_image(12, 100, 50, samples);
1419
1420 if let VsfType::p(tensor) = img {
1421 assert_eq!(tensor.bit_depth, 12);
1422 assert_eq!(tensor.shape, vec![100, 50]);
1423 assert_eq!(tensor.len(), 100 * 50);
1424 } else {
1425 panic!("Expected bitpacked tensor");
1426 }
1427 }
1428
1429 #[test]
1430 fn test_grayscale_image() {
1431 let data = vec![128u8; 64 * 48];
1432 let tensor = Tensor::new(vec![64, 48], data);
1433 let img = VsfType::t_u3(tensor);
1434
1435 if let VsfType::t_u3(tensor) = img {
1436 assert_eq!(tensor.shape, vec![64, 48]);
1437 assert_eq!(tensor.data.len(), 64 * 48);
1438 } else {
1439 panic!("Expected u8 tensor");
1440 }
1441 }
1442
1443 #[test]
1444 fn test_rgb_image() {
1445 let data = vec![255u8; 64 * 48 * 3];
1446 let tensor = Tensor::new(vec![64, 48, 3], data);
1447 let img = VsfType::t_u3(tensor);
1448
1449 if let VsfType::t_u3(tensor) = img {
1450 assert_eq!(tensor.shape, vec![64, 48, 3]);
1451 assert_eq!(tensor.data.len(), 64 * 48 * 3);
1452 } else {
1453 panic!("Expected u8 tensor");
1454 }
1455 }
1456
1457 #[test]
1458 fn test_gps_track() {
1459 let track = gps_track(vec![
1460 (40.7128, -74.0060), (51.5074, -0.1278), ]);
1463
1464 assert_eq!(track.len(), 2);
1465 }
1466
1467 #[test]
1468 fn test_gps_waypoint() {
1469 let point = gps_waypoint(0.0, 0.0);
1471 let (lat, lon) = point.to_lat_lon();
1472
1473 assert!(lat.abs() < 10.0, "Lat error: {}", lat.abs());
1475 assert!(lon.abs() < 10.0, "Lon error: {}", lon.abs());
1476 }
1477
1478 #[test]
1479 fn test_geotagged_photo() {
1480 let rgb_data = vec![0u8; 100 * 100 * 3];
1481 let (img, loc) = geotagged_photo(100, 100, rgb_data, 0.0, 0.0);
1483
1484 if let VsfType::t_u3(tensor) = img {
1485 assert_eq!(tensor.shape, vec![100, 100, 3]);
1486 } else {
1487 panic!("Expected RGB tensor");
1488 }
1489
1490 let (lat, lon) = loc.to_lat_lon();
1491 assert!(lat.abs() < 10.0);
1492 assert!(lon.abs() < 10.0);
1493 }
1494
1495 #[test]
1496 fn test_complete_raw_image_minimal() {
1497 let samples: Vec<u64> = vec![255; 64]; let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
1500
1501 let result = build_raw_image(image, None, None, None);
1502
1503 assert!(result.is_ok());
1504 let bytes = result.unwrap();
1505
1506 assert_eq!(&bytes[0..3], "RÅ".as_bytes());
1508 assert_eq!(bytes[3], b'<');
1509
1510 assert!(bytes.len() > 50); }
1513
1514 #[cfg(feature = "text-encode")]
1515 #[test]
1516 fn test_complete_raw_image_with_metadata() {
1517 let samples: Vec<u64> = vec![255; 64]; let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
1519
1520 let result = build_raw_image(
1521 image,
1522 Some(RawMetadata {
1523 cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B']).unwrap()),
1524 black_level: Some(BlackLevel::new(64.0).unwrap()),
1525 white_level: Some(WhiteLevel::new(255.0).unwrap()),
1526 dark_frame_hash: Some(CalibrationHash::new(HASH_BLAKE3, vec![0xAB; 32]).unwrap()),
1527 flat_field_hash: None,
1528 bias_frame_hash: None,
1529 vignette_correction_hash: None,
1530 distortion_correction_hash: None,
1531 magic_9: Some(
1532 Magic9::new(vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).unwrap(),
1533 ),
1534 }),
1535 Some(CameraSettings {
1536 make: None,
1537 model: None,
1538 serial_number: None,
1539 iso_speed: Some(IsoSpeed::new(800.0).unwrap()),
1540 shutter_time_s: Some(ShutterTime::new(1. / 60.).unwrap()), aperture_f_number: Some(Aperture::new(2.8).unwrap()),
1542 focal_length_m: Some(FocalLength::new(0.024).unwrap()), exposure_compensation: None,
1544 focus_distance_m: None,
1545 flash_fired: Some(FlashFired::new(false).unwrap()),
1546 metering_mode: Some(MeteringMode::new("matrix".to_string()).unwrap()),
1547 }),
1548 None,
1549 );
1550
1551 assert!(result.is_ok());
1552 let bytes = result.unwrap();
1553
1554 assert_eq!(&bytes[0..3], "RÅ".as_bytes());
1556
1557 assert!(bytes.contains(&b'z'));
1559 assert!(bytes.contains(&b'y'));
1560
1561 assert!(bytes.contains(&b'['));
1563 assert!(bytes.contains(&b']'));
1564 }
1565
1566 #[test]
1567 fn test_lumis_raw_capture() {
1568 let pixel_count = 4096 * 3072;
1570 let samples: Vec<u64> = vec![2048; pixel_count]; let result = lumis_raw_capture(
1573 samples,
1574 800.0,
1575 1. / 60., );
1577
1578 assert!(result.is_ok());
1579 let bytes = result.unwrap();
1580
1581 assert_eq!(&bytes[0..3], "RÅ".as_bytes());
1583
1584 assert!(
1586 bytes.len() > 18_000_000,
1587 "File should be > 18MB with bitpacked pixels"
1588 );
1589 }
1590
1591 #[test]
1592 fn test_roundtrip_minimal_raw() {
1593 let samples: Vec<u64> = (0..16).collect(); let original_image = BitPackedTensor::pack(8, vec![4, 4], &samples);
1596
1597 let raw_bytes = build_raw_image(original_image.clone(), None, None, None).unwrap();
1599
1600 let parsed = parse_raw_image(&raw_bytes).unwrap();
1602
1603 assert_eq!(parsed.image.bit_depth, 8);
1605 assert_eq!(parsed.image.shape, vec![4, 4]);
1606
1607 let original_samples = original_image.unpack().into_u64();
1609 let parsed_samples = parsed.image.unpack().into_u64();
1610 assert_eq!(parsed_samples, original_samples);
1611 assert_eq!(parsed_samples, samples);
1612
1613 assert!(parsed.metadata.is_none());
1615 assert!(parsed.camera.is_none());
1616 assert!(parsed.lens.is_none());
1617 }
1618
1619 #[cfg(feature = "text-encode")]
1620 #[test]
1621 fn test_roundtrip_full_metadata() {
1622 let samples: Vec<u64> = vec![200; 64]; let original_image = BitPackedTensor::pack(8, vec![8, 8], &samples);
1625
1626 let original_metadata = RawMetadata {
1627 cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B']).unwrap()), black_level: Some(BlackLevel::new(64.0).unwrap()),
1629 white_level: Some(WhiteLevel::new(255.0).unwrap()),
1630 dark_frame_hash: Some(CalibrationHash::new(HASH_BLAKE3, vec![0xAB; 32]).unwrap()),
1631 flat_field_hash: Some(CalibrationHash::new(HASH_BLAKE3, vec![0xCD; 32]).unwrap()),
1632 bias_frame_hash: None,
1633 vignette_correction_hash: None,
1634 distortion_correction_hash: None,
1635 magic_9: Some(Magic9::new(vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).unwrap()),
1636 };
1637
1638 let original_camera = CameraSettings {
1639 make: Some(Manufacturer::new("TestCam".to_string()).unwrap()),
1640 model: Some(ModelName::new("Model X".to_string()).unwrap()),
1641 serial_number: Some(SerialNumber::new("CAM123456".to_string()).unwrap()),
1642 iso_speed: Some(IsoSpeed::new(800.0).unwrap()),
1643 shutter_time_s: Some(ShutterTime::new(1. / 60.).unwrap()), aperture_f_number: Some(Aperture::new(2.8).unwrap()),
1645 focal_length_m: Some(FocalLength::new(0.050).unwrap()), exposure_compensation: Some(ExposureCompensation::new(-0.5).unwrap()),
1647 focus_distance_m: Some(FocusDistance::new(3.5).unwrap()),
1648 flash_fired: Some(FlashFired::new(false).unwrap()),
1649 metering_mode: Some(MeteringMode::new("matrix".to_string()).unwrap()),
1650 };
1651
1652 let raw_bytes = build_raw_image(
1654 original_image.clone(),
1655 Some(original_metadata.clone()),
1656 Some(original_camera.clone()),
1657 None, )
1659 .unwrap();
1660
1661 let parsed = parse_raw_image(&raw_bytes).unwrap();
1663
1664 assert_eq!(parsed.image.bit_depth, 8);
1666 assert_eq!(parsed.image.shape, vec![8, 8]);
1667 let parsed_samples = parsed.image.unpack().into_u64();
1668 assert_eq!(parsed_samples, samples);
1669
1670 assert!(parsed.metadata.is_some());
1672 let _meta = parsed.metadata.unwrap();
1673 assert!(parsed.camera.is_some());
1677 let _cam = parsed.camera.unwrap();
1678 assert!(parsed.lens.is_none());
1682 }
1683
1684 #[test]
1685 fn test_preamble_structure() {
1686 let samples: Vec<u64> = vec![100; 16]; let image = BitPackedTensor::pack(8, vec![4, 4], &samples);
1689
1690 let raw_bytes = build_raw_image(
1691 image,
1692 Some(RawMetadata {
1693 cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B']).unwrap()),
1694 black_level: Some(BlackLevel::new(64.0).unwrap()),
1695 white_level: Some(WhiteLevel::new(255.0).unwrap()),
1696 dark_frame_hash: None,
1697 flat_field_hash: None,
1698 bias_frame_hash: None,
1699 vignette_correction_hash: None,
1700 distortion_correction_hash: None,
1701 magic_9: None,
1702 }),
1703 None,
1704 None,
1705 )
1706 .unwrap();
1707
1708 assert_eq!(&raw_bytes[0..3], "RÅ".as_bytes()); assert_eq!(raw_bytes[3], b'<'); let header_end = raw_bytes.iter().position(|&b| b == b'>').unwrap();
1714
1715 let section_start = raw_bytes[header_end..]
1717 .iter()
1718 .position(|&b| b == b'[')
1719 .expect("Expected to find section start '['");
1720 assert!(
1721 section_start < 200,
1722 "Section should start soon after header"
1723 );
1724 }
1725
1726 #[test]
1727 fn test_builder_pattern_minimal() {
1728 let samples: Vec<u64> = (0..16).collect();
1730 let image = BitPackedTensor::pack(8, vec![4, 4], &samples);
1731
1732 let raw = RawImageBuilder::new(image);
1733 let result = raw.build();
1734
1735 assert!(result.is_ok());
1736 let bytes = result.unwrap();
1737
1738 assert_eq!(&bytes[0..3], "RÅ".as_bytes());
1740
1741 let parsed = parse_raw_image(&bytes).unwrap();
1743 assert_eq!(parsed.image.bit_depth, 8);
1744 assert_eq!(parsed.image.shape, vec![4, 4]);
1745 }
1746
1747 #[cfg(feature = "text-encode")]
1748 #[test]
1749 fn test_builder_pattern_camera_settings() {
1750 let samples: Vec<u64> = vec![100; 64];
1752 let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
1753
1754 let mut raw = RawImageBuilder::new(image);
1755 raw.camera.iso_speed = Some(800.0);
1756 raw.camera.shutter_time_s = Some(1.0 / 60.0);
1757 raw.camera.aperture_f_number = Some(2.8);
1758 raw.camera.flash_fired = Some(false);
1759 raw.camera.metering_mode = Some("matrix".to_string());
1760
1761 let result = raw.build();
1762 assert!(result.is_ok());
1763 let bytes = result.unwrap();
1764
1765 let parsed = parse_raw_image(&bytes).unwrap();
1767 assert!(parsed.camera.is_some());
1768 }
1770
1771 #[test]
1772 fn test_builder_pattern_raw_metadata() {
1773 let samples: Vec<u64> = vec![100; 64];
1775 let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
1776
1777 let mut raw = RawImageBuilder::new(image);
1778 raw.raw.cfa_pattern = Some(vec![b'R', b'G', b'G', b'B']);
1779 raw.raw.black_level = Some(64.0);
1780 raw.raw.white_level = Some(4095.0);
1781 raw.raw.dark_frame_hash = Some((HASH_BLAKE3, vec![0xAB; 32]));
1782
1783 let result = raw.build();
1784 assert!(result.is_ok());
1785 let bytes = result.unwrap();
1786
1787 let parsed = parse_raw_image(&bytes).unwrap();
1789 assert!(parsed.metadata.is_some());
1790 }
1792
1793 #[cfg(feature = "text-encode")]
1794 #[test]
1795 fn test_builder_pattern_lens_info() {
1796 let samples: Vec<u64> = vec![100; 64];
1798 let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
1799
1800 let mut raw = RawImageBuilder::new(image);
1801 raw.lens.make = Some("Sony".to_string());
1802 raw.lens.model = Some("FE 24-70mm F2.8 GM II".to_string());
1803 raw.lens.serial_number = Some("ABC123456".to_string());
1804 raw.lens.min_focal_length_m = Some(0.024); raw.lens.max_focal_length_m = Some(0.070); raw.lens.min_aperture_f = Some(22.0);
1807 raw.lens.max_aperture_f = Some(2.8);
1808
1809 let result = raw.build();
1810 assert!(result.is_ok());
1811 let bytes = result.unwrap();
1812
1813 let parsed = parse_raw_image(&bytes).unwrap();
1815 assert!(parsed.lens.is_some());
1816 }
1818
1819 #[cfg(feature = "text-encode")]
1820 #[test]
1821 fn test_builder_pattern_full() {
1822 let samples: Vec<u64> = vec![2048; 64];
1824 let image = BitPackedTensor::pack(12, vec![8, 8], &samples);
1825
1826 let mut raw = RawImageBuilder::new(image);
1827
1828 raw.raw.cfa_pattern = Some(vec![b'R', b'G', b'G', b'B']);
1830 raw.raw.black_level = Some(64.0);
1831 raw.raw.white_level = Some(4095.0);
1832 raw.raw.magic_9 = Some(vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
1833
1834 raw.camera.iso_speed = Some(800.0);
1836 raw.camera.shutter_time_s = Some(1.0 / 125.0);
1837 raw.camera.aperture_f_number = Some(2.8);
1838 raw.camera.focal_length_m = Some(0.050); raw.camera.exposure_compensation = Some(-0.5);
1840 raw.camera.focus_distance_m = Some(3.5);
1841 raw.camera.flash_fired = Some(false);
1842 raw.camera.metering_mode = Some("spot".to_string());
1843
1844 raw.lens.make = Some("Sony".to_string());
1846 raw.lens.model = Some("FE 50mm F1.2 GM".to_string());
1847
1848 let result = raw.build();
1849 assert!(result.is_ok());
1850 let bytes = result.unwrap();
1851
1852 let parsed = parse_raw_image(&bytes).unwrap();
1854
1855 assert_eq!(parsed.image.bit_depth, 12);
1857 assert_eq!(parsed.image.shape, vec![8, 8]);
1858
1859 assert!(parsed.metadata.is_some());
1861 assert!(parsed.camera.is_some());
1862 assert!(parsed.lens.is_some());
1863 }
1865
1866 #[test]
1867 fn test_cfa_pattern_validation() {
1868 let samples: Vec<u64> = vec![100; 16];
1869 let image = BitPackedTensor::pack(8, vec![4, 4], &samples);
1870
1871 let valid_patterns = vec![
1873 vec![b'R', b'G', b'G', b'B'], vec![b'G', b'R', b'B', b'G'], vec![b'B', b'G', b'G', b'R'], vec![b'C', b'Y', b'Y', b'G'], vec![b'R', b'G', b'B', b'E', b'W', b'C', b'Y', b'R', b'G'], ];
1879
1880 for cfa in valid_patterns {
1881 let result = build_raw_image(
1882 image.clone(),
1883 Some(RawMetadata {
1884 cfa_pattern: Some(CfaPattern::new(cfa.clone()).unwrap()),
1885 black_level: None,
1886 white_level: None,
1887 dark_frame_hash: None,
1888 flat_field_hash: None,
1889 bias_frame_hash: None,
1890 vignette_correction_hash: None,
1891 distortion_correction_hash: None,
1892 magic_9: None,
1893 }),
1894 None,
1895 None,
1896 );
1897 assert!(
1898 result.is_ok(),
1899 "Valid CFA pattern {:?} should be accepted",
1900 cfa
1901 );
1902 }
1903
1904 let invalid_patterns = vec![
1906 vec![b'R', b'G', b'X', b'B'], vec![0, 1, 1, 2], vec![b'r', b'g', b'g', b'b'], ];
1910
1911 for cfa in invalid_patterns {
1912 let cfa_result = CfaPattern::new(cfa.clone());
1914 assert!(
1915 cfa_result.is_err(),
1916 "Invalid CFA pattern {:?} should be rejected",
1917 cfa
1918 );
1919 }
1920 }
1921}