image_meta/loader/
hdr.rs

1use std::io::{BufRead, Seek};
2
3use crate::errors::{ImageError, ImageResult};
4use crate::types::{Color, Dimensions, Format, ImageMeta};
5
6const SIGNATURE: [u8; 11] = [
7    0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x0a,
8];
9
10pub fn load<R: ?Sized + BufRead + Seek>(image: &mut R) -> ImageResult<ImageMeta> {
11    read_signature(image)?;
12
13    let (dimensions, color) = read_header(image)?;
14
15    Ok(ImageMeta {
16        animation_frames: None,
17        color,
18        dimensions,
19        format: Format::Hdr,
20    })
21}
22
23fn read_signature<R: ?Sized + BufRead + Seek>(image: &mut R) -> ImageResult {
24    let mut signature = [0u8; 11];
25    image.read_exact(&mut signature)?;
26    if SIGNATURE != signature {
27        return Err(ImageError::InvalidSignature);
28    }
29    Ok(())
30}
31
32fn read_header<R: ?Sized + BufRead + Seek>(image: &mut R) -> ImageResult<(Dimensions, Color)> {
33    use crate::types::ColorMode::*;
34
35    let color = Color {
36        mode: Rgb,
37        alpha_channel: false,
38        resolution: 32,
39    };
40    while let Some(Ok(line)) = image.lines().next() {
41        // Skip empty lines and comments
42        if line.starts_with('#') || line.is_empty() {
43            continue;
44        }
45
46        // Try to parse the line as a key-value pair
47        if let Some((key, value)) = line.split_once("=") {
48            match key {
49                "FORMAT" => match value {
50                    "32-bit_rle_rgbe" => {}
51                    "32-bit_rle_xyze" => {}
52                    _ => {
53                        return Err(ImageError::CorruptImage(
54                            format!("Unsupported format: {}", value).into(),
55                        ));
56                    }
57                },
58                "PRIMARIES" => {}
59                _ => {}
60            }
61        }
62        // Else, we have reached the resolution line
63        else {
64            let mut iter = line.split_whitespace();
65            let c1_tag = iter
66                .next()
67                .ok_or(ImageError::CorruptImage("Error parsing dimension".into()))?;
68            let c1_str = iter
69                .next()
70                .ok_or(ImageError::CorruptImage("Error parsing dimension".into()))?;
71            let c2_tag = iter
72                .next()
73                .ok_or(ImageError::CorruptImage("Error parsing dimension".into()))?;
74            let c2_str = iter
75                .next()
76                .ok_or(ImageError::CorruptImage("Error parsing dimension".into()))?;
77            match (c1_tag, c2_tag) {
78                ("-Y", "+X") => {
79                    // Common orientation (left-right, top-down)
80                    // c1_str is height, c2_str is width
81                    let height = c1_str.parse::<u32>().map_err(|err| {
82                        ImageError::CorruptImage(format!("Error parsing height: {err}").into())
83                    })?;
84                    let width = c2_str.parse::<u32>().map_err(|err| {
85                        ImageError::CorruptImage(format!("Error parsing width: {err}").into())
86                    })?;
87                    return Ok((Dimensions { width, height }, color));
88                }
89                _ => return Err(ImageError::Unsupported),
90            }
91        }
92    }
93    Err(ImageError::Unsupported)
94}