ai_imagesize/container/pvrtc.rs
1use no_std_io::io::{BufRead, Seek, SeekFrom};
2
3use crate::{
4 util::{read_u32, read_u64, Endian},
5 ImageResult, ImageSize,
6};
7
8/// Compression formats for PVRTC containers
9///
10/// PowerVR containers can contain different compression formats beyond just PVRTC.
11/// This enum identifies the specific compression algorithm used within the PowerVR container.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum PvrtcCompression {
14 /// PVRTC 2 bits per pixel RGB
15 Pvrtc2BppRgb,
16 /// PVRTC 2 bits per pixel RGBA
17 Pvrtc2BppRgba,
18 /// PVRTC 4 bits per pixel RGB
19 Pvrtc4BppRgb,
20 /// PVRTC 4 bits per pixel RGBA
21 Pvrtc4BppRgba,
22 /// ETC2 RGB compression
23 Etc2Rgb,
24 /// ETC2 RGBA compression
25 Etc2Rgba,
26 /// ETC2 RGB with 1-bit alpha
27 Etc2RgbA1,
28 /// EAC R11 (single channel)
29 EacR11,
30 /// EAC RG11 (dual channel)
31 EacRg11,
32 /// Other/Unknown format
33 Unknown,
34}
35
36pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
37 // Check if this is PVR v3 format or legacy format
38 reader.seek(SeekFrom::Start(0))?;
39 let mut magic = [0u8; 4];
40 reader.read_exact(&mut magic)?;
41
42 if &magic == b"PVR\x03" {
43 // PVR v3 format structure:
44 // 0-3: Magic "PVR\x03"
45 // 4-7: Flags
46 // 8-15: Pixel format (8 bytes)
47 // 16-19: Colour space
48 // 20-23: Channel type
49 // 24-27: Height
50 // 28-31: Width
51 // 32-35: Depth
52 // ... rest of header
53 reader.seek(SeekFrom::Start(24))?;
54 let height = read_u32(reader, &Endian::Little)? as usize;
55 let width = read_u32(reader, &Endian::Little)? as usize;
56
57 Ok(ImageSize { width, height })
58 } else {
59 // Legacy PVR format structure:
60 // Header size: 4 bytes (little-endian)
61 // Height: 4 bytes (little-endian)
62 // Width: 4 bytes (little-endian)
63 // ... rest of legacy header
64 reader.seek(SeekFrom::Start(4))?;
65 let height = read_u32(reader, &Endian::Little)? as usize;
66 let width = read_u32(reader, &Endian::Little)? as usize;
67
68 Ok(ImageSize { width, height })
69 }
70}
71
72pub fn matches(header: &[u8]) -> bool {
73 // PVRTC files can have different magic numbers:
74 // Legacy format starts with header length (usually 52 bytes = 0x34000000 in little endian)
75 // Modern format has "PVR!" magic at different offsets
76 if header.len() >= 4 {
77 // Check for legacy format (header size = 52)
78 let header_size = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
79 if header_size == 52 {
80 return true;
81 }
82 }
83
84 // Check for "PVR!" magic at various positions
85 if header.len() >= 48 {
86 let pvr_magic = &header[44..48];
87 if pvr_magic == b"PVR!" {
88 return true;
89 }
90 }
91
92 // Check for "PVR\x03" which is the modern PVRTC format
93 if header.len() >= 4 && header.starts_with(b"PVR\x03") {
94 return true;
95 }
96
97 false
98}
99
100pub fn detect_compression<R: BufRead + Seek>(reader: &mut R) -> ImageResult<PvrtcCompression> {
101 // Check if this is PVR v3 format or legacy format
102 reader.seek(SeekFrom::Start(0))?;
103 let mut magic = [0u8; 4];
104 reader.read_exact(&mut magic)?;
105
106 if &magic == b"PVR\x03" {
107 // PVR v3 format - read pixel format from offset 8-15
108 reader.seek(SeekFrom::Start(8))?;
109 let pixel_format = read_u64(reader, &Endian::Little)?;
110
111 let compression = match pixel_format {
112 0 => PvrtcCompression::Pvrtc2BppRgb, // PVRTCI_2BPP_RGB
113 1 => PvrtcCompression::Pvrtc2BppRgba, // PVRTCI_2BPP_RGBA
114 2 => PvrtcCompression::Pvrtc4BppRgb, // PVRTCI_4BPP_RGB
115 3 => PvrtcCompression::Pvrtc4BppRgba, // PVRTCI_4BPP_RGBA
116 22 => PvrtcCompression::Etc2Rgb, // ETC2_RGB
117 23 => PvrtcCompression::Etc2Rgba, // ETC2_RGBA
118 24 => PvrtcCompression::Etc2RgbA1, // ETC2_RGB_A1
119 25 => PvrtcCompression::EacR11, // EAC_R11
120 26 => PvrtcCompression::EacRg11, // EAC_RG11
121 _ => PvrtcCompression::Unknown,
122 };
123
124 Ok(compression)
125 } else {
126 // Legacy format - try to determine compression from other header fields
127 // For legacy format, we don't have the same pixel format field,
128 // so we'll need to infer from other data or default to Unknown
129 Ok(PvrtcCompression::Unknown)
130 }
131}