Skip to main content

ctt_etcpak/
lib.rs

1//! Safe Rust bindings to [etcpak](https://github.com/wolfpld/etcpak) — an extremely fast
2//! ETC/EAC/BC texture compressor and decompressor.
3//!
4//! # ISA dispatch
5//!
6//! etcpak uses compile-time SIMD ISA selection. This crate compiles multiple
7//! variants and selects the best one at runtime:
8//!
9//! - **x86_64**: SSE4.1 and AVX2 variants (selected via `std::is_x86_feature_detected!`)
10//! - **aarch64**: NEON (the only variant, always available on AArch64)
11//!
12//! # Pixel format
13//!
14//! All compression functions expect 4 bytes per pixel, packed as `uint32_t`.
15//! The **channel ordering within each pixel depends on the codec**:
16//!
17//! - **ETC1/ETC2/EAC codecs**: `B8 G8 R8 A8` (BGRA — blue in lowest byte)
18//! - **BC codecs**: `R8 G8 B8 A8` (RGBA — red in lowest byte)
19//!
20//! Input data must be **tightly packed** (stride = width × 4 bytes).
21//!
22//! Decompression functions output `uint32_t` per pixel in native etcpak layout.
23
24mod dispatch;
25
26use dispatch::dispatch;
27
28// ── Surface ───────────────────────────────────────────────────────────────────
29
30/// Describes a 2D image for compression.
31///
32/// All pixel data must be 4 bytes per pixel, tightly packed (stride = width × 4).
33/// The channel ordering depends on which codec consumes the surface — see the
34/// individual format modules for details.
35#[derive(Debug, Copy, Clone)]
36pub struct Surface<'a> {
37    /// The raw pixel data for the image (4 bytes per pixel, tightly packed).
38    pub data: &'a [u8],
39    /// The width of the image in texels. Must be a multiple of 4.
40    pub width: u32,
41    /// The height of the image in texels. Must be a multiple of 4.
42    pub height: u32,
43}
44
45impl<'a> Surface<'a> {
46    /// Creates a new surface, validating dimensions.
47    ///
48    /// # Panics
49    ///
50    /// Panics if:
51    /// - `width` or `height` is zero,
52    /// - `width` or `height` is not a multiple of 4 (etcpak processes whole
53    ///   4×4 blocks),
54    /// - `width * height * 4` overflows `usize`,
55    /// - `data.len()` is less than `width * height * 4`.
56    pub fn new(data: &'a [u8], width: u32, height: u32) -> Self {
57        assert!(width > 0 && height > 0, "width and height must be non-zero");
58        assert!(
59            width.is_multiple_of(4),
60            "width {width} must be a multiple of 4"
61        );
62        assert!(
63            height.is_multiple_of(4),
64            "height {height} must be a multiple of 4"
65        );
66        let required = (width as usize)
67            .checked_mul(height as usize)
68            .and_then(|wh| wh.checked_mul(4))
69            .expect("width * height * 4 overflows usize");
70        assert!(
71            data.len() >= required,
72            "data length {} is less than width * height * 4 ({required})",
73            data.len()
74        );
75        Self {
76            data,
77            width,
78            height,
79        }
80    }
81
82    fn blocks(&self) -> u32 {
83        (self.width / 4) * (self.height / 4)
84    }
85}
86
87// ── Output size helpers ───────────────────────────────────────────────────────
88
89/// Output size for formats using 64-bit (8-byte) blocks:
90/// ETC1, ETC2 RGB, EAC R, BC1, BC4.
91#[must_use]
92pub fn output_size_64bpb(width: u32, height: u32) -> usize {
93    let block_count = (width.div_ceil(4) * height.div_ceil(4)) as usize;
94    block_count * 8
95}
96
97/// Output size for formats using 128-bit (16-byte) blocks:
98/// ETC2 RGBA, EAC RG, BC3, BC5.
99#[must_use]
100pub fn output_size_128bpb(width: u32, height: u32) -> usize {
101    let block_count = (width.div_ceil(4) * height.div_ceil(4)) as usize;
102    block_count * 16
103}
104
105// ── ETC1 ──────────────────────────────────────────────────────────────────────
106
107/// ETC1 block compression — RGB, no alpha.
108///
109/// Expects BGRA pixel data (blue in lowest byte of each `uint32_t`).
110/// Each 4×4 block encodes to 8 bytes.
111pub mod etc1 {
112    use super::*;
113
114    #[must_use]
115    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
116        let size = output_size_64bpb(surface.width, surface.height);
117        let mut output = vec![0u8; size];
118        compress_blocks_into(surface, &mut output);
119        output
120    }
121
122    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
123        assert_eq!(
124            output.len(),
125            output_size_64bpb(surface.width, surface.height)
126        );
127        unsafe {
128            (dispatch().compress_etc1_rgb)(
129                surface.data.as_ptr().cast(),
130                output.as_mut_ptr().cast(),
131                surface.blocks(),
132                surface.width as usize,
133            );
134        }
135    }
136
137    #[must_use]
138    pub fn compress_blocks_dither(surface: &Surface) -> Vec<u8> {
139        let size = output_size_64bpb(surface.width, surface.height);
140        let mut output = vec![0u8; size];
141        compress_blocks_dither_into(surface, &mut output);
142        output
143    }
144
145    pub fn compress_blocks_dither_into(surface: &Surface, output: &mut [u8]) {
146        assert_eq!(
147            output.len(),
148            output_size_64bpb(surface.width, surface.height)
149        );
150        unsafe {
151            (dispatch().compress_etc1_rgb_dither)(
152                surface.data.as_ptr().cast(),
153                output.as_mut_ptr().cast(),
154                surface.blocks(),
155                surface.width as usize,
156            );
157        }
158    }
159}
160
161// ── ETC2 RGB ──────────────────────────────────────────────────────────────────
162
163/// ETC2 RGB block compression — RGB, no alpha.
164///
165/// Expects BGRA pixel data. Each 4×4 block encodes to 8 bytes.
166pub mod etc2_rgb {
167    use super::*;
168
169    #[must_use]
170    pub fn compress_blocks(surface: &Surface, use_heuristics: bool) -> Vec<u8> {
171        let size = output_size_64bpb(surface.width, surface.height);
172        let mut output = vec![0u8; size];
173        compress_blocks_into(surface, use_heuristics, &mut output);
174        output
175    }
176
177    pub fn compress_blocks_into(surface: &Surface, use_heuristics: bool, output: &mut [u8]) {
178        assert_eq!(
179            output.len(),
180            output_size_64bpb(surface.width, surface.height)
181        );
182        unsafe {
183            (dispatch().compress_etc2_rgb)(
184                surface.data.as_ptr().cast(),
185                output.as_mut_ptr().cast(),
186                surface.blocks(),
187                surface.width as usize,
188                use_heuristics,
189            );
190        }
191    }
192}
193
194// ── ETC2 RGBA ─────────────────────────────────────────────────────────────────
195
196/// ETC2 RGBA block compression — RGB with alpha.
197///
198/// Expects BGRA pixel data. Each 4×4 block encodes to 16 bytes.
199pub mod etc2_rgba {
200    use super::*;
201
202    #[must_use]
203    pub fn compress_blocks(surface: &Surface, use_heuristics: bool) -> Vec<u8> {
204        let size = output_size_128bpb(surface.width, surface.height);
205        let mut output = vec![0u8; size];
206        compress_blocks_into(surface, use_heuristics, &mut output);
207        output
208    }
209
210    pub fn compress_blocks_into(surface: &Surface, use_heuristics: bool, output: &mut [u8]) {
211        assert_eq!(
212            output.len(),
213            output_size_128bpb(surface.width, surface.height)
214        );
215        unsafe {
216            (dispatch().compress_etc2_rgba)(
217                surface.data.as_ptr().cast(),
218                output.as_mut_ptr().cast(),
219                surface.blocks(),
220                surface.width as usize,
221                use_heuristics,
222            );
223        }
224    }
225}
226
227// ── EAC R ─────────────────────────────────────────────────────────────────────
228
229/// EAC R11 block compression — single channel (red).
230///
231/// Expects BGRA pixel data. Each 4×4 block encodes to 8 bytes.
232pub mod eac_r {
233    use super::*;
234
235    #[must_use]
236    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
237        let size = output_size_64bpb(surface.width, surface.height);
238        let mut output = vec![0u8; size];
239        compress_blocks_into(surface, &mut output);
240        output
241    }
242
243    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
244        assert_eq!(
245            output.len(),
246            output_size_64bpb(surface.width, surface.height)
247        );
248        unsafe {
249            (dispatch().compress_eac_r)(
250                surface.data.as_ptr().cast(),
251                output.as_mut_ptr().cast(),
252                surface.blocks(),
253                surface.width as usize,
254            );
255        }
256    }
257}
258
259// ── EAC RG ────────────────────────────────────────────────────────────────────
260
261/// EAC RG11 block compression — dual channel (red + green).
262///
263/// Expects BGRA pixel data. Each 4×4 block encodes to 16 bytes.
264pub mod eac_rg {
265    use super::*;
266
267    #[must_use]
268    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
269        let size = output_size_128bpb(surface.width, surface.height);
270        let mut output = vec![0u8; size];
271        compress_blocks_into(surface, &mut output);
272        output
273    }
274
275    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
276        assert_eq!(
277            output.len(),
278            output_size_128bpb(surface.width, surface.height)
279        );
280        unsafe {
281            (dispatch().compress_eac_rg)(
282                surface.data.as_ptr().cast(),
283                output.as_mut_ptr().cast(),
284                surface.blocks(),
285                surface.width as usize,
286            );
287        }
288    }
289}
290
291// ── BC1 ───────────────────────────────────────────────────────────────────────
292
293/// BC1 (DXT1) block compression — RGB with optional 1-bit alpha.
294///
295/// Expects RGBA pixel data (red in lowest byte). Each 4×4 block encodes to 8 bytes.
296pub mod bc1 {
297    use super::*;
298
299    #[must_use]
300    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
301        let size = output_size_64bpb(surface.width, surface.height);
302        let mut output = vec![0u8; size];
303        compress_blocks_into(surface, &mut output);
304        output
305    }
306
307    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
308        assert_eq!(
309            output.len(),
310            output_size_64bpb(surface.width, surface.height)
311        );
312        unsafe {
313            (dispatch().compress_bc1)(
314                surface.data.as_ptr().cast(),
315                output.as_mut_ptr().cast(),
316                surface.blocks(),
317                surface.width as usize,
318            );
319        }
320    }
321
322    #[must_use]
323    pub fn compress_blocks_dither(surface: &Surface) -> Vec<u8> {
324        let size = output_size_64bpb(surface.width, surface.height);
325        let mut output = vec![0u8; size];
326        compress_blocks_dither_into(surface, &mut output);
327        output
328    }
329
330    pub fn compress_blocks_dither_into(surface: &Surface, output: &mut [u8]) {
331        assert_eq!(
332            output.len(),
333            output_size_64bpb(surface.width, surface.height)
334        );
335        unsafe {
336            (dispatch().compress_bc1_dither)(
337                surface.data.as_ptr().cast(),
338                output.as_mut_ptr().cast(),
339                surface.blocks(),
340                surface.width as usize,
341            );
342        }
343    }
344}
345
346// ── BC3 ───────────────────────────────────────────────────────────────────────
347
348/// BC3 (DXT5) block compression — RGBA.
349///
350/// Expects RGBA pixel data. Each 4×4 block encodes to 16 bytes.
351pub mod bc3 {
352    use super::*;
353
354    #[must_use]
355    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
356        let size = output_size_128bpb(surface.width, surface.height);
357        let mut output = vec![0u8; size];
358        compress_blocks_into(surface, &mut output);
359        output
360    }
361
362    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
363        assert_eq!(
364            output.len(),
365            output_size_128bpb(surface.width, surface.height)
366        );
367        unsafe {
368            (dispatch().compress_bc3)(
369                surface.data.as_ptr().cast(),
370                output.as_mut_ptr().cast(),
371                surface.blocks(),
372                surface.width as usize,
373            );
374        }
375    }
376}
377
378// ── BC4 ───────────────────────────────────────────────────────────────────────
379
380/// BC4 block compression — single channel (red).
381///
382/// Expects RGBA pixel data. Each 4×4 block encodes to 8 bytes.
383pub mod bc4 {
384    use super::*;
385
386    #[must_use]
387    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
388        let size = output_size_64bpb(surface.width, surface.height);
389        let mut output = vec![0u8; size];
390        compress_blocks_into(surface, &mut output);
391        output
392    }
393
394    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
395        assert_eq!(
396            output.len(),
397            output_size_64bpb(surface.width, surface.height)
398        );
399        unsafe {
400            (dispatch().compress_bc4)(
401                surface.data.as_ptr().cast(),
402                output.as_mut_ptr().cast(),
403                surface.blocks(),
404                surface.width as usize,
405            );
406        }
407    }
408}
409
410// ── BC5 ───────────────────────────────────────────────────────────────────────
411
412/// BC5 block compression — dual channel (red + green, typically normals).
413///
414/// Expects RGBA pixel data. Each 4×4 block encodes to 16 bytes.
415pub mod bc5 {
416    use super::*;
417
418    #[must_use]
419    pub fn compress_blocks(surface: &Surface) -> Vec<u8> {
420        let size = output_size_128bpb(surface.width, surface.height);
421        let mut output = vec![0u8; size];
422        compress_blocks_into(surface, &mut output);
423        output
424    }
425
426    pub fn compress_blocks_into(surface: &Surface, output: &mut [u8]) {
427        assert_eq!(
428            output.len(),
429            output_size_128bpb(surface.width, surface.height)
430        );
431        unsafe {
432            (dispatch().compress_bc5)(
433                surface.data.as_ptr().cast(),
434                output.as_mut_ptr().cast(),
435                surface.blocks(),
436                surface.width as usize,
437            );
438        }
439    }
440}
441
442// ── Decompression ─────────────────────────────────────────────────────────────
443
444/// Decompression functions for all supported formats.
445///
446/// Output is `width × height` packed `uint32_t` pixels. The channel ordering
447/// matches the format's native layout.
448pub mod decode {
449    use super::*;
450
451    fn output_pixel_size(width: u32, height: u32) -> usize {
452        (width as usize) * (height as usize) * 4
453    }
454
455    // ── ETC/EAC decode ──
456
457    /// Decode ETC1 or ETC2 RGB compressed data.
458    #[must_use]
459    pub fn decode_rgb(data: &[u8], width: u32, height: u32) -> Vec<u8> {
460        let size = output_pixel_size(width, height);
461        let mut output = vec![0u8; size];
462        decode_rgb_into(data, width, height, &mut output);
463        output
464    }
465
466    pub fn decode_rgb_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
467        assert_eq!(output.len(), output_pixel_size(width, height));
468        assert!(data.len() >= output_size_64bpb(width, height));
469        unsafe {
470            (dispatch().decode_rgb)(
471                data.as_ptr().cast(),
472                output.as_mut_ptr().cast(),
473                width as i32,
474                height as i32,
475            );
476        }
477    }
478
479    /// Decode ETC2 RGBA compressed data.
480    #[must_use]
481    pub fn decode_rgba(data: &[u8], width: u32, height: u32) -> Vec<u8> {
482        let size = output_pixel_size(width, height);
483        let mut output = vec![0u8; size];
484        decode_rgba_into(data, width, height, &mut output);
485        output
486    }
487
488    pub fn decode_rgba_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
489        assert_eq!(output.len(), output_pixel_size(width, height));
490        assert!(data.len() >= output_size_128bpb(width, height));
491        unsafe {
492            (dispatch().decode_rgba)(
493                data.as_ptr().cast(),
494                output.as_mut_ptr().cast(),
495                width as i32,
496                height as i32,
497            );
498        }
499    }
500
501    /// Decode EAC R11 compressed data.
502    #[must_use]
503    pub fn decode_r(data: &[u8], width: u32, height: u32, is_signed: bool) -> Vec<u8> {
504        let size = output_pixel_size(width, height);
505        let mut output = vec![0u8; size];
506        decode_r_into(data, width, height, is_signed, &mut output);
507        output
508    }
509
510    pub fn decode_r_into(data: &[u8], width: u32, height: u32, is_signed: bool, output: &mut [u8]) {
511        assert_eq!(output.len(), output_pixel_size(width, height));
512        assert!(data.len() >= output_size_64bpb(width, height));
513        unsafe {
514            (dispatch().decode_r)(
515                data.as_ptr().cast(),
516                output.as_mut_ptr().cast(),
517                width as i32,
518                height as i32,
519                is_signed,
520            );
521        }
522    }
523
524    /// Decode EAC RG11 compressed data.
525    #[must_use]
526    pub fn decode_rg(data: &[u8], width: u32, height: u32, is_signed: bool) -> Vec<u8> {
527        let size = output_pixel_size(width, height);
528        let mut output = vec![0u8; size];
529        decode_rg_into(data, width, height, is_signed, &mut output);
530        output
531    }
532
533    pub fn decode_rg_into(
534        data: &[u8],
535        width: u32,
536        height: u32,
537        is_signed: bool,
538        output: &mut [u8],
539    ) {
540        assert_eq!(output.len(), output_pixel_size(width, height));
541        assert!(data.len() >= output_size_128bpb(width, height));
542        unsafe {
543            (dispatch().decode_rg)(
544                data.as_ptr().cast(),
545                output.as_mut_ptr().cast(),
546                width as i32,
547                height as i32,
548                is_signed,
549            );
550        }
551    }
552
553    // ── BC decode ──
554
555    /// Decode BC1 (DXT1) compressed data.
556    #[must_use]
557    pub fn decode_bc1(data: &[u8], width: u32, height: u32) -> Vec<u8> {
558        let size = output_pixel_size(width, height);
559        let mut output = vec![0u8; size];
560        decode_bc1_into(data, width, height, &mut output);
561        output
562    }
563
564    pub fn decode_bc1_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
565        assert_eq!(output.len(), output_pixel_size(width, height));
566        assert!(data.len() >= output_size_64bpb(width, height));
567        unsafe {
568            (dispatch().decode_bc1)(
569                data.as_ptr().cast(),
570                output.as_mut_ptr().cast(),
571                width as i32,
572                height as i32,
573            );
574        }
575    }
576
577    /// Decode BC3 (DXT5) compressed data.
578    #[must_use]
579    pub fn decode_bc3(data: &[u8], width: u32, height: u32) -> Vec<u8> {
580        let size = output_pixel_size(width, height);
581        let mut output = vec![0u8; size];
582        decode_bc3_into(data, width, height, &mut output);
583        output
584    }
585
586    pub fn decode_bc3_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
587        assert_eq!(output.len(), output_pixel_size(width, height));
588        assert!(data.len() >= output_size_128bpb(width, height));
589        unsafe {
590            (dispatch().decode_bc3)(
591                data.as_ptr().cast(),
592                output.as_mut_ptr().cast(),
593                width as i32,
594                height as i32,
595            );
596        }
597    }
598
599    /// Decode BC4 compressed data.
600    #[must_use]
601    pub fn decode_bc4(data: &[u8], width: u32, height: u32) -> Vec<u8> {
602        let size = output_pixel_size(width, height);
603        let mut output = vec![0u8; size];
604        decode_bc4_into(data, width, height, &mut output);
605        output
606    }
607
608    pub fn decode_bc4_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
609        assert_eq!(output.len(), output_pixel_size(width, height));
610        assert!(data.len() >= output_size_64bpb(width, height));
611        unsafe {
612            (dispatch().decode_bc4)(
613                data.as_ptr().cast(),
614                output.as_mut_ptr().cast(),
615                width as i32,
616                height as i32,
617            );
618        }
619    }
620
621    /// Decode BC5 compressed data.
622    #[must_use]
623    pub fn decode_bc5(data: &[u8], width: u32, height: u32) -> Vec<u8> {
624        let size = output_pixel_size(width, height);
625        let mut output = vec![0u8; size];
626        decode_bc5_into(data, width, height, &mut output);
627        output
628    }
629
630    pub fn decode_bc5_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
631        assert_eq!(output.len(), output_pixel_size(width, height));
632        assert!(data.len() >= output_size_128bpb(width, height));
633        unsafe {
634            (dispatch().decode_bc5)(
635                data.as_ptr().cast(),
636                output.as_mut_ptr().cast(),
637                width as i32,
638                height as i32,
639            );
640        }
641    }
642
643    /// Decode BC7 compressed data.
644    #[must_use]
645    pub fn decode_bc7(data: &[u8], width: u32, height: u32) -> Vec<u8> {
646        let size = output_pixel_size(width, height);
647        let mut output = vec![0u8; size];
648        decode_bc7_into(data, width, height, &mut output);
649        output
650    }
651
652    pub fn decode_bc7_into(data: &[u8], width: u32, height: u32, output: &mut [u8]) {
653        assert_eq!(output.len(), output_pixel_size(width, height));
654        assert!(data.len() >= output_size_128bpb(width, height));
655        unsafe {
656            (dispatch().decode_bc7)(
657                data.as_ptr().cast(),
658                output.as_mut_ptr().cast(),
659                width as i32,
660                height as i32,
661            );
662        }
663    }
664}