ifc_lite_geometry/processors/texture/
raster.rs1pub fn decode_step_binary(s: &str) -> Vec<u8> {
17 let s = s.trim().trim_matches('"');
18 if s.len() < 3 {
19 return Vec::new();
20 }
21 let hex = s.as_bytes();
22 let mut out = Vec::with_capacity(hex.len() / 2);
23 let mut i = 1;
25 while i + 1 < hex.len() {
26 match (hex_val(hex[i]), hex_val(hex[i + 1])) {
27 (Some(h), Some(l)) => out.push((h << 4) | l),
28 _ => break,
29 }
30 i += 2;
31 }
32 out
33}
34
35#[inline]
36fn hex_val(b: u8) -> Option<u8> {
37 match b {
38 b'0'..=b'9' => Some(b - b'0'),
39 b'a'..=b'f' => Some(b - b'a' + 10),
40 b'A'..=b'F' => Some(b - b'A' + 10),
41 _ => None,
42 }
43}
44
45pub const MAX_TEX_DIM: u32 = 16384;
50
51fn decode_png(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
53 let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes));
56 decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
59 let mut reader = decoder.read_info().ok()?;
60 let png_info = reader.info();
62 if png_info.width == 0
63 || png_info.height == 0
64 || png_info.width > MAX_TEX_DIM
65 || png_info.height > MAX_TEX_DIM
66 {
67 return None;
68 }
69 let mut buf = vec![0u8; reader.output_buffer_size()?];
71 let info = reader.next_frame(&mut buf).ok()?;
72 let (w, h) = (info.width, info.height);
73 let px = (w as usize) * (h as usize);
74 let src = &buf[..info.buffer_size()];
75 let mut rgba = Vec::with_capacity(px * 4);
76 match info.color_type {
77 png::ColorType::Rgba => rgba.extend_from_slice(&src[..px * 4]),
78 png::ColorType::Rgb => {
79 for c in src.chunks_exact(3) {
80 rgba.extend_from_slice(&[c[0], c[1], c[2], 255]);
81 }
82 }
83 png::ColorType::Grayscale => {
84 for &g in src.iter() {
85 rgba.extend_from_slice(&[g, g, g, 255]);
86 }
87 }
88 png::ColorType::GrayscaleAlpha => {
89 for c in src.chunks_exact(2) {
90 rgba.extend_from_slice(&[c[0], c[0], c[0], c[1]]);
91 }
92 }
93 png::ColorType::Indexed => return None,
95 }
96 if rgba.len() != px * 4 {
97 return None;
98 }
99 Some((rgba, w, h))
100}
101
102fn decode_jpeg(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
104 let mut decoder = jpeg_decoder::Decoder::new(bytes);
105 decoder.read_info().ok()?;
108 let info = decoder.info()?;
109 if info.width == 0
110 || info.height == 0
111 || info.width as u32 > MAX_TEX_DIM
112 || info.height as u32 > MAX_TEX_DIM
113 {
114 return None;
115 }
116 let pixels = decoder.decode().ok()?;
117 let (w, h) = (info.width as usize, info.height as usize);
118 let px = w * h;
119 let mut rgba = Vec::with_capacity(px * 4);
120 match info.pixel_format {
121 jpeg_decoder::PixelFormat::RGB24 => {
122 for c in pixels.chunks_exact(3) {
123 rgba.extend_from_slice(&[c[0], c[1], c[2], 255]);
124 }
125 }
126 jpeg_decoder::PixelFormat::L8 => {
127 for &g in pixels.iter() {
128 rgba.extend_from_slice(&[g, g, g, 255]);
129 }
130 }
131 _ => return None,
133 }
134 if rgba.len() != px * 4 {
135 return None;
136 }
137 Some((rgba, w as u32, h as u32))
138}
139
140pub(super) fn decode_raster_image(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
143 const PNG_MAGIC: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
144 if bytes.len() >= 8 && bytes[..8] == PNG_MAGIC {
145 return decode_png(bytes);
146 }
147 if bytes.len() >= 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF {
149 return decode_jpeg(bytes);
150 }
151 None
152}
153
154pub fn embedded_raster_dimensions(step_binary: &str) -> Option<(u32, u32)> {
158 let hex = step_binary.trim().trim_matches('"').as_bytes();
159 if hex.first() != Some(&b'0') { return None; }
160 super::raster_header::dimensions(hex.len().saturating_sub(1) / 2, |i| {
161 Some((hex_val(*hex.get(1 + i * 2)?)? << 4) | hex_val(*hex.get(2 + i * 2)?)?)
162 })
163}
164
165#[cfg(test)]
166fn raster_image_dimensions(bytes: &[u8]) -> Option<(u32, u32)> {
167 super::raster_header::dimensions(bytes.len(), |i| bytes.get(i).copied())
168}
169
170#[cfg(test)]
171#[path = "raster_budget_tests.rs"]
172mod budget_tests;