ai_imagesize/container/pkm.rs
1use no_std_io::io::{BufRead, Seek, SeekFrom};
2
3use crate::{
4 util::{read_u16, Endian},
5 ImageResult, ImageSize,
6};
7
8/// Compression formats for PKM containers (ETC/EAC family)
9///
10/// PKM (PowerVR Texture Compression) format is used to store various ETC and EAC
11/// compressed textures. This enum identifies the specific compression variant.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum PkmCompression {
14 /// ETC1 RGB (original format)
15 Etc1,
16 /// ETC2 RGB (enhanced)
17 Etc2,
18 /// ETC2 with 1-bit punch-through alpha
19 Etc2A1,
20 /// ETC2 with 8-bit alpha channel
21 Etc2A8,
22 /// EAC single channel (R11)
23 EacR,
24 /// EAC two channel (RG11)
25 EacRg,
26 /// EAC single channel signed (R11_SIGNED)
27 EacRSigned,
28 /// EAC two channel signed (RG11_SIGNED)
29 EacRgSigned,
30 /// Other/Unknown PKM format
31 Unknown,
32}
33
34pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
35 // ETC/EAC files are typically in PKM format
36 // PKM header structure:
37 // Magic: "PKM " (4 bytes)
38 // Version: "10" or "20" (2 bytes)
39 // Data type: 2 bytes (big-endian)
40 // Extended width: 2 bytes (big-endian)
41 // Extended height: 2 bytes (big-endian)
42 // Original width: 2 bytes (big-endian)
43 // Original height: 2 bytes (big-endian)
44
45 reader.seek(SeekFrom::Start(8))?; // Skip magic + version + data type
46 let _extended_width = read_u16(reader, &Endian::Big)?;
47 let _extended_height = read_u16(reader, &Endian::Big)?;
48 let width = read_u16(reader, &Endian::Big)? as usize;
49 let height = read_u16(reader, &Endian::Big)? as usize;
50
51 Ok(ImageSize { width, height })
52}
53
54pub fn matches(header: &[u8]) -> bool {
55 // PKM format magic number followed by version
56 if header.len() >= 6 {
57 return header.starts_with(b"PKM ")
58 && (header[4..6] == [b'1', b'0'] || header[4..6] == [b'2', b'0']);
59 }
60 false
61}
62
63pub fn matches_eac(header: &[u8]) -> bool {
64 // PKM format with EAC-specific data types
65 if header.len() >= 8
66 && header.starts_with(b"PKM ")
67 && (header[4..6] == [b'1', b'0'] || header[4..6] == [b'2', b'0'])
68 {
69 // Check data type for EAC formats
70 let data_type = u16::from_be_bytes([header[6], header[7]]);
71 return matches!(data_type, 0x1608..=0x160B);
72 }
73 false
74}
75
76pub fn detect_compression<R: BufRead + Seek>(reader: &mut R) -> ImageResult<PkmCompression> {
77 // Read the data type from PKM header to determine compression format
78 reader.seek(SeekFrom::Start(6))?; // Skip magic and version
79 let data_type = read_u16(reader, &Endian::Big)?;
80
81 let compression = match data_type {
82 // ETC1 formats
83 0x0000 => PkmCompression::Etc1, // ETC1_RGB_NO_MIPMAPS
84
85 // ETC2 formats
86 0x0001 => PkmCompression::Etc2, // ETC2PACKAGE_RGB_NO_MIPMAPS
87 0x0002 => PkmCompression::Etc2A1, // ETC2PACKAGE_RGBA1_NO_MIPMAPS (alternative)
88 0x0003 => PkmCompression::Etc2A8, // ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD
89 0x0004 => PkmCompression::Etc2A1, // ETC2PACKAGE_RGBA1_NO_MIPMAPS (generated format)
90 0x0005 => PkmCompression::Etc2A8, // ETC2PACKAGE_RGBA_NO_MIPMAPS (generated format)
91
92 // Standard EAC formats
93 0x1608 => PkmCompression::EacRgSigned, // EAC_RG11_SIGNED_FORMAT
94 0x1609 => PkmCompression::EacRSigned, // EAC_R11_SIGNED_FORMAT
95 0x160A => PkmCompression::EacR, // EAC_R11_UNSIGNED_FORMAT
96 0x160B => PkmCompression::EacRg, // EAC_RG11_UNSIGNED_FORMAT
97
98 _ => PkmCompression::Unknown,
99 };
100
101 Ok(compression)
102}