Repository: Sewer56/dxt-lossless-transform
Pull request #39: Support: In-place update for BC1/BC2/BC3 Normalize Blocks
Author: login: Sewer56
Created: 2025-05-17T06:20:45Z | Merged: 2025-05-17T07:01:34Z

<!-- Please explain the changes you made -->

<!--
Please, make sure:
- you have read the contributing guidelines:
  https://github.com/Sewer56/dxt-lossless-transform/blob/main/docs/CONTRIBUTING.md
-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added support for in-place normalization, allowing input and output buffers to be the same for BC1, BC2, and BC3 block normalization.
- **Bug Fixes**
  - Improved memory handling to safely support in-place operations by updating copy logic to use unaligned reads and writes.
- **Tests**
  - Introduced new tests to verify correct behavior when normalizing in place for various modes.
- **Documentation**
  - Updated safety and usage documentation to clarify support for in-place normalization and disallow partial buffer overlaps.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs b/projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs
index a1efc3aa..91b238d4 100644
--- a/projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs
+++ b/projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs
@@ -80,7 +80,7 @@
 //! 4. Write the normalized block to the output
 
 use crate::util::decode_bc1_block;
-use core::ptr::copy_nonoverlapping;
+use core::ptr::{copy_nonoverlapping, eq, null_mut, read_unaligned, write_bytes};
 use dxt_lossless_transform_common::{color_565::Color565, decoded_4x4_block::Decoded4x4Block};
 use likely_stable::unlikely;
 
@@ -98,7 +98,8 @@ use likely_stable::unlikely;
 /// - input_ptr must be valid for reads of len bytes
 /// - output_ptr must be valid for writes of len bytes
 /// - len must be divisible by 8
-/// - input_ptr and output_ptr must not overlap
+/// - The implementation supports `input_ptr` == `output_ptr` (in-place transformation)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
 /// # Remarks
 ///
@@ -118,13 +119,23 @@ pub unsafe fn normalize_blocks(
     len: usize,
     color_mode: ColorNormalizationMode,
 ) {
+    // Assert that buffers either don't overlap or are identical (in-place transformation)
     debug_assert!(
-        input_ptr.add(len) <= output_ptr || output_ptr.add(len) <= input_ptr as *mut u8,
-        "Input and output memory regions must not overlap"
+        eq(input_ptr, output_ptr as *const u8) || 
+        input_ptr.add(len) <= output_ptr || 
+        output_ptr.add(len) <= input_ptr as *mut u8,
+        "normalize_blocks: overlapping buffers are not supported (must be either completely separate or identical)"
     );
 
     // Skip normalization if mode is None
     if color_mode == ColorNormalizationMode::None {
+        // No need to copy if buffers are identical
+        if eq(input_ptr, output_ptr) {
+            return;
+        }
+
+        // This can hit the case where pointers overlap at runtime.
+        // That is caught by the copy call.
         copy_nonoverlapping(input_ptr, output_ptr, len);
         return;
     }
@@ -138,7 +149,7 @@ pub unsafe fn normalize_blocks(
             match block_case {
                 BlockCase::Transparent => {
                     // Case 2: Fully transparent block - fill with 0xFF
-                    core::ptr::write_bytes(dst_block_ptr, 0xFF, 8);
+                    write_bytes(dst_block_ptr, 0xFF, 8);
                 }
                 BlockCase::SolidColorRoundtrippable => {
                     // Can be normalized - use the helper function to write the block
@@ -151,7 +162,7 @@ pub unsafe fn normalize_blocks(
                 }
                 BlockCase::CannotNormalize => {
                     // Cannot normalize, copy source block as-is
-                    copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                    (dst_block_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
                 }
             }
 
@@ -296,7 +307,7 @@ unsafe fn write_normalized_solid_color_block(
     match color_mode {
         ColorNormalizationMode::None => {
             // For None mode, copy the original source block
-            copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+            (dst_block_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
         }
         ColorNormalizationMode::Color0Only => {
             // Write Color1 = 0
@@ -337,7 +348,8 @@ unsafe fn write_normalized_solid_color_block(
 /// - input_ptr must be valid for reads of len bytes
 /// - each pointer in output_ptrs must be valid for writes of len bytes
 /// - len must be divisible by 8
-/// - input_ptr and output_ptrs must not overlap
+/// - The implementation supports in-place transformation (input_ptr == output_ptr)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
 /// # Remarks
 ///
@@ -357,15 +369,18 @@ pub unsafe fn normalize_blocks_all_modes(
 ) {
     debug_assert!(len % 8 == 0);
     debug_assert!(output_ptrs.len() == ColorNormalizationMode::all_values().len());
+    
+    // Assert that no output buffer partially overlaps with the input
     debug_assert!(
-        output_ptrs.iter().all(|&out_ptr| {
-            input_ptr.add(len) <= out_ptr || out_ptr.add(len) <= input_ptr as *mut _
-        }),
-        "Input and output memory regions must not overlap"
+        output_ptrs.iter().all(|&output_ptr| 
+            eq(input_ptr, output_ptr as *const u8) || 
+            input_ptr.add(len) <= output_ptr || 
+            output_ptr.add(len) <= input_ptr as *mut u8
+        ),
+        "normalize_blocks_all_modes: overlapping buffers are not supported (must be either completely separate or identical)"
     );
 
-    let mut dst_block_ptrs =
-        [core::ptr::null_mut::<u8>(); ColorNormalizationMode::all_values().len()];
+    let mut dst_block_ptrs = [null_mut::<u8>(); ColorNormalizationMode::all_values().len()];
     for (x, dst_ptr) in dst_block_ptrs.iter_mut().enumerate() {
         *dst_ptr = output_ptrs[x];
     }
@@ -378,7 +393,7 @@ pub unsafe fn normalize_blocks_all_modes(
                 BlockCase::Transparent => {
                     // Case 2: Fully transparent block - fill with 0xFF in all output buffers
                     for dst_ptr in dst_block_ptrs.iter_mut() {
-                        core::ptr::write_bytes(*dst_ptr, 0xFF, 8);
+                        write_bytes(*dst_ptr, 0xFF, 8);
                     }
                 }
                 BlockCase::SolidColorRoundtrippable => {
@@ -391,7 +406,7 @@ pub unsafe fn normalize_blocks_all_modes(
                 BlockCase::CannotNormalize => {
                     // Cannot normalize, copy source block as-is to all output buffers
                     for dst_ptr in dst_block_ptrs.iter_mut() {
-                        copy_nonoverlapping(src_block_ptr, *dst_ptr, 8);
+                        (*dst_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
                     }
                 }
             }
@@ -773,4 +788,66 @@ mod tests {
             }
         }
     }
+
+    #[test]
+    fn can_normalize_in_place() {
+        // Create test data with a solid color block
+        let mut test_data = [0u8; 8];
+
+        // Set up color endpoints (0xF800 = bright red in RGB565)
+        test_data[0] = 0x00;
+        test_data[1] = 0xF8;
+        test_data[2] = 0x00;
+        test_data[3] = 0xF8;
+
+        // Set indices to all zeros (all pixels use color0)
+        test_data[4] = 0x00;
+        test_data[5] = 0x00;
+        test_data[6] = 0x00;
+        test_data[7] = 0x00;
+
+        // Get a pointer to the test data for reading and writing
+        let ptr = test_data.as_mut_ptr();
+
+        // Call normalize_blocks with the same buffer for input and output
+        unsafe {
+            normalize_blocks(ptr, ptr, 8, ColorNormalizationMode::Color0Only);
+        }
+
+        // The block should be normalized (Color0 = red, Color1 = 0, indices = 0)
+        assert_eq!(test_data[0], 0x00);
+        assert_eq!(test_data[1], 0xF8);
+        assert_eq!(test_data[2], 0x00);
+        assert_eq!(test_data[3], 0x00);
+        assert_eq!(test_data[4], 0x00);
+        assert_eq!(test_data[5], 0x00);
+        assert_eq!(test_data[6], 0x00);
+        assert_eq!(test_data[7], 0x00);
+
+        // Test transparent block normalization
+        let mut transparent_data = [0u8; 8];
+
+        // Set up endpoints to indicate alpha mode (Color0 <= Color1)
+        transparent_data[0] = 0x00;
+        transparent_data[1] = 0x00;
+        transparent_data[2] = 0x01;
+        transparent_data[3] = 0x00;
+
+        // Set indices to use transparent pixels (11 pattern for all indices)
+        transparent_data[4] = 0xFF;
+        transparent_data[5] = 0xFF;
+        transparent_data[6] = 0xFF;
+        transparent_data[7] = 0xFF;
+
+        let ptr = transparent_data.as_mut_ptr();
+
+        unsafe {
+            normalize_blocks(ptr, ptr, 8, ColorNormalizationMode::Color0Only);
+        }
+
+        // The block should be normalized to all 0xFF for fully transparent blocks
+        for (x, byte) in transparent_data.iter().enumerate() {
+            assert_eq!(*byte, 0xFF, "Byte {x} should be 0xFF");
+        }
+    }
 }
diff --git a/projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs b/projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs
index 890de5ac..bbf1c5e8 100644
--- a/projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs
+++ b/projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs
@@ -66,7 +66,7 @@
 //! 5. Write the normalized block to the output
 
 use crate::util::decode_bc2_block;
-use core::ptr::copy_nonoverlapping;
+use core::ptr::{copy_nonoverlapping, eq, null_mut, read_unaligned};
 use derive_enum_all_values::AllValues;
 use dxt_lossless_transform_common::{color_565::Color565, color_8888::Color8888};
 use likely_stable::unlikely;
@@ -85,7 +85,8 @@ use likely_stable::unlikely;
 /// - input_ptr must be valid for reads of len bytes
 /// - output_ptr must be valid for writes of len bytes
 /// - len must be divisible by 16 (BC2 block size)
-/// - input_ptr and output_ptr must not overlap
+/// - The implementation supports `input_ptr` == `output_ptr` (in-place transformation)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
 /// # Remarks
 ///
@@ -105,13 +106,23 @@ pub unsafe fn normalize_blocks(
     color_mode: ColorNormalizationMode,
 ) {
     debug_assert!(len % 16 == 0);
+    // Assert that buffers either don't overlap or are identical (in-place transformation)
     debug_assert!(
-        input_ptr.add(len) <= output_ptr || output_ptr.add(len) <= input_ptr as *mut u8,
-        "Input and output memory regions must not overlap"
+        eq(input_ptr, output_ptr as *const u8) || 
+        input_ptr.add(len) <= output_ptr || 
+        output_ptr.add(len) <= input_ptr as *mut u8,
+        "normalize_blocks: overlapping buffers are not supported (must be either completely separate or identical)"
     );
 
     // Skip normalization if mode is None
     if color_mode == ColorNormalizationMode::None {
+        // No need to copy if buffers are identical
+        if eq(input_ptr, output_ptr as *const u8) {
+            return;
+        }
+
+        // This can hit the case where pointers overlap at runtime.
+        // That is caught by the copy call.
         copy_nonoverlapping(input_ptr, output_ptr, len);
         return;
     }
@@ -122,7 +133,7 @@ pub unsafe fn normalize_blocks(
         match block_case {
             BlockCase::SolidColorRoundtrippable => {
                 // Copy alpha values (first 8 bytes) unchanged
-                copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                (dst_block_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
 
                 // Write normalized color data (bytes 8-15)
                 write_normalized_solid_color_block(
@@ -134,7 +145,8 @@ pub unsafe fn normalize_blocks(
             }
             BlockCase::CannotNormalize => {
                 // Cannot normalize, copy source block as-is
-                copy_nonoverlapping(src_block_ptr, dst_block_ptr, 16);
+                (dst_block_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
+                (dst_block_ptr.add(8) as *mut u64).write_unaligned(read_unaligned(src_block_ptr.add(8) as *const u64));
             }
         }
 
@@ -228,7 +240,8 @@ where
 /// - input_ptr must be valid for reads of len bytes
 /// - each pointer in output_ptrs must be valid for writes of len bytes
 /// - len must be divisible by 16 (BC2 block size)
-/// - input_ptr and output_ptrs must not overlap
+/// - The implementation supports in-place transformation (input_ptr == output_ptr)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
 /// # Remarks
 ///
@@ -250,13 +263,17 @@ pub unsafe fn normalize_blocks_all_modes(
     debug_assert!(output_ptrs.len() == ColorNormalizationMode::all_values().len());
     debug_assert!(
         output_ptrs.iter().all(|&out_ptr| {
-            input_ptr.add(len) <= out_ptr || out_ptr.add(len) <= input_ptr as *mut _
+            // Allow case where input_ptr == out_ptr (in-place transform)
+            eq(input_ptr, out_ptr as *const u8) ||
+            // Otherwise no partial overlap
+            input_ptr.add(len) <= out_ptr || 
+            out_ptr.add(len) <= input_ptr as *mut _
         }),
-        "Input and output memory regions must not overlap"
+        "normalize_blocks_all_modes: overlapping buffers are not supported (must be either completely separate or identical)"
     );
 
     let mut dst_block_ptrs =
-        [core::ptr::null_mut::<u8>(); ColorNormalizationMode::all_values().len()];
+        [null_mut::<u8>(); ColorNormalizationMode::all_values().len()];
 
     // Initialize destination pointers
     for (c_idx, dst_ptr) in dst_block_ptrs.iter_mut().enumerate() {
@@ -272,7 +289,7 @@ pub unsafe fn normalize_blocks_all_modes(
                     let dst_block_ptr = dst_block_ptrs[x];
 
                     // Copy alpha values (first 8 bytes) unchanged
-                    copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                    (dst_block_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
 
                     // Write normalized color data (bytes 8-15)
                     write_normalized_solid_color_block(
@@ -292,7 +309,8 @@ pub unsafe fn normalize_blocks_all_modes(
                     let dst_block_ptr = dst_block_ptrs[x];
 
                     // Cannot normalize, copy source block as-is for all modes
-                    copy_nonoverlapping(src_block_ptr, dst_block_ptr, 16);
+                    (dst_block_ptr as *mut u64).write_unaligned(read_unaligned(src_block_ptr as *const u64));
+                    (dst_block_ptr.add(8) as *mut u64).write_unaligned(read_unaligned(src_block_ptr.add(8) as *const u64));
 
                     // Advance this mode's destination pointer
                     dst_block_ptrs[x] = dst_block_ptr.add(16);
@@ -339,7 +357,7 @@ unsafe fn write_normalized_solid_color_block(
     // Write Color1 = 0 or repeat
     match color_mode {
         ColorNormalizationMode::None => {
-            copy_nonoverlapping(src_block_ptr.add(8), dst_block_ptr.add(8), 8);
+            (dst_block_ptr.add(8) as *mut u64).write_unaligned(read_unaligned(src_block_ptr.add(8) as *const u64));
         }
         ColorNormalizationMode::Color0Only => {
             // Write Color0 (the solid color)
@@ -398,7 +416,6 @@ pub enum ColorNormalizationMode {
 #[allow(clippy::needless_range_loop)]
 mod tests {
     use rstest::rstest;
-
     use super::*;
 
     /// Test normalizing a solid color block with uniform alpha
@@ -647,7 +664,7 @@ mod tests {
         source[25] = red565[1]; // Color0 (high byte)
         source[26] = blue565[0]; // Color1 (low byte)
         source[27] = blue565[1]; // Color1 (high byte)
-                                 // Mix of indices pointing to both colors
+        // Mix of indices pointing to both colors
         source[28] = 0b00010001; // 00010001 (alternating indices)
         source[29] = 0b00010001;
         source[30] = 0b00010001;
@@ -758,4 +775,51 @@ mod tests {
             );
         }
     }
+
+    /// Test that in-place transformation works correctly
+    #[rstest]
+    #[case::color0_only(ColorNormalizationMode::Color0Only)]
+    #[case::replicate_color(ColorNormalizationMode::ReplicateColor)]
+    #[case::none(ColorNormalizationMode::None)]
+    fn can_perform_inplace_transformation(#[case] color_mode: ColorNormalizationMode) {
+        // Red in RGB565: (31, 0, 0) -> 0xF800
+        // This cleanly round trips into (255, 0, 0) and back to (31, 0, 0).
+        let red565 = 0xF800u16.to_le_bytes(); // Little endian: [0x00, 0xF8]
+
+        // Create a BC2 block with solid red color and uniform alpha
+        let mut block = [0u8; 16];
+
+        // Fill alpha values with 0xFF (fully opaque)
+        for x in 0..8 {
+            block[x] = 0xFF;
+        }
+
+        // Color part
+        block[8] = red565[0]; // Color0 (low byte)
+        block[9] = red565[1]; // Color0 (high byte)
+        block[10] = 0x01; // Color1 (low byte)
+        block[11] = 0x01; // Color1 (high byte)
+
+        // All indices = 0, pointing to Color0
+        block[12] = 0x00;
+        block[13] = 0x00;
+        block[14] = 0x00;
+        block[15] = 0x00;
+        
+        // Create a copy for the expected result
+        let mut expected = [0u8; 16];
+        
+        // Normalize to a separate buffer to get the expected result
+        unsafe {
+            normalize_blocks(block.as_ptr(), expected.as_mut_ptr(), block.len(), color_mode);
+        }
+        
+        // Now perform in-place transformation
+        unsafe {
+            normalize_blocks(block.as_ptr(), block.as_mut_ptr(), block.len(), color_mode);
+        }
+        
+        // The in-place transformation should produce the same result as the separate buffer transformation
+        assert_eq!(block, expected, "In-place transformation result does not match expected result");
+    }
 }
diff --git a/projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs b/projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs
index 0aed8fb4..c2592f34 100644
--- a/projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs
+++ b/projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs
@@ -58,7 +58,7 @@
 //! 4. Writes the normalized block to the output
 
 use crate::util::decode_bc3_block;
-use core::ptr::copy_nonoverlapping;
+use core::ptr::{copy_nonoverlapping, eq, null_mut, read_unaligned, write_bytes};
 use derive_enum_all_values::AllValues;
 use dxt_lossless_transform_common::color_565::Color565;
 use dxt_lossless_transform_common::color_8888::Color8888;
@@ -79,7 +79,8 @@ use likely_stable::unlikely;
 /// - input_ptr must be valid for reads of len bytes
 /// - output_ptr must be valid for writes of len bytes
 /// - len must be divisible by 16 (BC3 block size)
-/// - input_ptr and output_ptr must not overlap
+/// - The implementation supports `input_ptr` == `output_ptr` (in-place transformation)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
 /// # Remarks
 ///
@@ -100,12 +101,21 @@ pub unsafe fn normalize_blocks(
 ) {
     debug_assert!(len % 16 == 0);
     debug_assert!(
-        input_ptr.add(len) <= output_ptr || output_ptr.add(len) <= input_ptr as *mut u8,
-        "Input and output memory regions must not overlap"
+        input_ptr == output_ptr
+            || input_ptr.add(len) <= output_ptr
+            || output_ptr.add(len) <= input_ptr as *mut u8,
+        "Input and output memory regions must either be the same (in-place) or not overlap"
     );
 
     // Skip normalization if both modes are None
     if alpha_mode == AlphaNormalizationMode::None && color_mode == ColorNormalizationMode::None {
+        // No need to copy if buffers are identical
+        if eq(input_ptr, output_ptr as *const u8) {
+            return;
+        }
+
+        // This can hit the case where pointers overlap at runtime.
+        // That is caught by the copy call.
         copy_nonoverlapping(input_ptr, output_ptr, len);
         return;
     }
@@ -123,12 +133,14 @@ pub unsafe fn normalize_blocks(
                         normalize_alpha(src_block_ptr, dst_block_ptr, alpha_value, alpha_mode);
                     } else {
                         // Copy alpha data as-is (first 8 bytes)
-                        copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                        (dst_block_ptr as *mut u64)
+                            .write_unaligned(read_unaligned(src_block_ptr as *const u64));
                     }
                 }
                 AlphaBlockCase::CannotNormalize => {
-                    // Copy alpha data as-is (first 8 bytes)
-                    copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                    // Copy alpha data as-is (first 8 bytes) using write_unaligned
+                    (dst_block_ptr as *mut u64)
+                        .write_unaligned(read_unaligned(src_block_ptr as *const u64));
                 }
             }
 
@@ -139,12 +151,14 @@ pub unsafe fn normalize_blocks(
                         normalize_color(color565.raw_value(), dst_block_ptr, color_mode);
                     } else {
                         // Copy color data as-is (last 8 bytes)
-                        copy_nonoverlapping(src_block_ptr.add(8), dst_block_ptr.add(8), 8);
+                        (dst_block_ptr.add(8) as *mut u64)
+                            .write_unaligned(read_unaligned(src_block_ptr.add(8) as *const u64));
                     }
                 }
                 ColorBlockCase::CannotNormalize => {
-                    // Copy color data as-is (last 8 bytes)
-                    copy_nonoverlapping(src_block_ptr.add(8), dst_block_ptr.add(8), 8);
+                    // Copy color data as-is (last 8 bytes) using write_unaligned
+                    (dst_block_ptr.add(8) as *mut u64)
+                        .write_unaligned(read_unaligned(src_block_ptr.add(8) as *const u64));
                 }
             }
 
@@ -226,8 +240,9 @@ unsafe fn normalize_alpha(
 ) {
     match mode {
         AlphaNormalizationMode::None => {
-            // Copy alpha data as-is (first 8 bytes)
-            copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+            // Copy alpha data as-is (first 8 bytes) using write_unaligned
+            (dst_block_ptr as *mut u64)
+                .write_unaligned(read_unaligned(src_block_ptr as *const u64));
         }
         AlphaNormalizationMode::UniformAlphaZeroIndices => {
             // Set A0 to the alpha value, everything else to 0
@@ -235,12 +250,12 @@ unsafe fn normalize_alpha(
             *dst_block_ptr.add(1) = 0;
 
             // Zero all index bytes
-            core::ptr::write_bytes(dst_block_ptr.add(2), 0, 6);
+            write_bytes(dst_block_ptr.add(2), 0, 6);
         }
         AlphaNormalizationMode::OpaqueFillAll => {
             if alpha_value == 255 {
                 // Fill all alpha bytes with 0xFF
-                core::ptr::write_bytes(dst_block_ptr, 0xFF, 8);
+                write_bytes(dst_block_ptr, 0xFF, 8);
             } else {
                 // For non-opaque, use the same approach as UniformAlphaZeroIndices
                 normalize_alpha(
@@ -258,7 +273,7 @@ unsafe fn normalize_alpha(
                 *dst_block_ptr.add(1) = 0;
 
                 // Set all indices to max value (0xFF)
-                core::ptr::write_bytes(dst_block_ptr.add(2), 0xFF, 6);
+                write_bytes(dst_block_ptr.add(2), 0xFF, 6);
             } else {
                 // For non-opaque, use the same approach as UniformAlphaZeroIndices
                 normalize_alpha(
@@ -447,7 +462,8 @@ where
 /// - input_ptr must be valid for reads of len bytes
 /// - each pointer in output_ptrs must be valid for writes of len bytes
 /// - len must be divisible by 16
-/// - input_ptr and output_ptrs must not overlap
+/// - The implementation supports in-place transformation (input_ptr can match any output_ptr)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
 /// # Remarks
 ///
@@ -474,8 +490,7 @@ pub unsafe fn normalize_blocks_all_modes(
     );
 
     // Setup arrays to track current position in each output buffer
-    let mut dst_block_ptrs = [[core::ptr::null_mut::<u8>();
-        ColorNormalizationMode::all_values().len()];
+    let mut dst_block_ptrs = [[null_mut::<u8>(); ColorNormalizationMode::all_values().len()];
         AlphaNormalizationMode::all_values().len()];
 
     // Initialize destination pointers
@@ -506,12 +521,14 @@ pub unsafe fn normalize_blocks_all_modes(
                                 );
                             } else {
                                 // Copy alpha data as-is (first 8 bytes)
-                                copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                                (dst_block_ptr as *mut u64)
+                                    .write_unaligned(read_unaligned(src_block_ptr as *const u64));
                             }
                         }
                         AlphaBlockCase::CannotNormalize => {
                             // Copy alpha data as-is (first 8 bytes)
-                            copy_nonoverlapping(src_block_ptr, dst_block_ptr, 8);
+                            (dst_block_ptr as *mut u64)
+                                .write_unaligned(read_unaligned(src_block_ptr as *const u64));
                         }
                     }
 
@@ -522,12 +539,16 @@ pub unsafe fn normalize_blocks_all_modes(
                                 normalize_color(color565.raw_value(), dst_block_ptr, *color_mode);
                             } else {
                                 // Copy color data as-is (last 8 bytes)
-                                copy_nonoverlapping(src_block_ptr.add(8), dst_block_ptr.add(8), 8);
+                                (dst_block_ptr.add(8) as *mut u64).write_unaligned(read_unaligned(
+                                    src_block_ptr.add(8) as *const u64,
+                                ));
                             }
                         }
                         ColorBlockCase::CannotNormalize => {
                             // Copy color data as-is (last 8 bytes)
-                            copy_nonoverlapping(src_block_ptr.add(8), dst_block_ptr.add(8), 8);
+                            (dst_block_ptr.add(8) as *mut u64).write_unaligned(read_unaligned(
+                                src_block_ptr.add(8) as *const u64,
+                            ));
                         }
                     }
 
@@ -542,11 +563,9 @@ pub unsafe fn normalize_blocks_all_modes(
 #[cfg(test)]
 #[allow(clippy::needless_range_loop)]
 mod tests {
-    use core::ptr;
-
-    use rstest::rstest;
-
     use super::*;
+    use core::ptr::null_mut;
+    use rstest::rstest;
 
     /// Test normalizing a solid color block with uniform alpha
     #[rstest]
@@ -1127,7 +1146,7 @@ mod tests {
         const COLOR_MODE_COUNT: usize = ColorNormalizationMode::all_values().len();
 
         let mut output_buffers = vec![vec![0u8; 16]; ALPHA_MODE_COUNT * COLOR_MODE_COUNT];
-        let mut output_ptrs = [[ptr::null_mut::<u8>(); COLOR_MODE_COUNT]; ALPHA_MODE_COUNT];
+        let mut output_ptrs = [[null_mut::<u8>(); COLOR_MODE_COUNT]; ALPHA_MODE_COUNT];
 
         // Set up output pointers
         for a_idx in 0..ALPHA_MODE_COUNT {
@@ -1165,9 +1184,91 @@ mod tests {
                     assert_eq!(
                         output[x], reference_output[x],
                         "Output for mode combination [{a_mode:?}][{c_mode:?}] at byte {x} does not match expected value"
-                    );
+                );
                 }
             }
         }
     }
+
+    #[rstest]
+    #[case(
+        AlphaNormalizationMode::OpaqueFillAll,
+        ColorNormalizationMode::ReplicateColor
+    )]
+    #[case(
+        AlphaNormalizationMode::UniformAlphaZeroIndices,
+        ColorNormalizationMode::Color0Only
+    )]
+    #[case(
+        AlphaNormalizationMode::OpaqueZeroAlphaMaxIndices,
+        ColorNormalizationMode::None
+    )]
+    #[test]
+    fn can_normalize_blocks_inplace(
+        #[case] alpha_mode: AlphaNormalizationMode,
+        #[case] color_mode: ColorNormalizationMode,
+    ) {
+        // Create sample blocks - we'll use solid color with opaque alpha for simplicity
+        let mut input_blocks = [0u8; 32]; // Two BC3 blocks
+
+        // First block: solid red
+        input_blocks[0] = 0xFF; // A0 = 255 (opaque)
+        input_blocks[1] = 0xFF; // A1 = 255
+                                // alpha indices set to pattern that's not all zeros
+        for x in 2..8 {
+            input_blocks[x] = 0xAA;
+        }
+
+        input_blocks[8] = 0x00; // C0 low byte (RGB565: 0xF800 = pure red)
+        input_blocks[9] = 0xF8; // C0 high byte
+        input_blocks[10] = 0x00; // C1 low byte (zero)
+        input_blocks[11] = 0x00; // C1 high byte
+                                 // No need to set color indices as they'll be set to zero (default)
+
+        // Second block: solid green
+        input_blocks[16] = 0xFF; // A0 = 255 (opaque)
+        input_blocks[17] = 0xFF; // A1 = 255
+                                 // alpha indices set to pattern that's not all zeros
+        for x in 18..24 {
+            input_blocks[x] = 0x55;
+        }
+
+        input_blocks[24] = 0xE0; // C0 low byte (RGB565: 0x07E0 = pure green)
+        input_blocks[25] = 0x07; // C0 high byte
+        input_blocks[26] = 0x00; // C1 low byte (zero)
+        input_blocks[27] = 0x00; // C1 high byte
+                                 // No need to set color indices as they'll be set to zero (default)
+
+        // Create a copy for reference output
+        let mut reference_output = input_blocks;
+
+        // Normalize using separate buffers for reference
+        unsafe {
+            normalize_blocks(
+                input_blocks.as_ptr(),
+                reference_output.as_mut_ptr(),
+                32,
+                alpha_mode,
+                color_mode,
+            );
+        }
+
+        // Now normalize in-place
+        unsafe {
+            normalize_blocks(
+                input_blocks.as_ptr(),
+                input_blocks.as_mut_ptr(), // Same pointer for in-place operation
+                32,
+                alpha_mode,
+                color_mode,
+            );
+        }
+
+        // Verify the in-place normalization matches the reference
+        assert_eq!(
+            reference_output,
+            input_blocks,
+            "In-place normalization result doesn't match reference output for alpha_mode={alpha_mode:?}, color_mode={color_mode:?}"
+        );
+    }
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs (1)</summary><blockquote>

`150-153`: **(Nit) Consider using a 64-bit store for the transparent pattern**

`write_bytes(dst_block_ptr, 0xFF, 8)` writes one byte at a time.  
Writing the constant `0xFFFF_FFFF_FFFF_FFFFu64` with `write_unaligned` avoids the loop in `write_bytes` and may be measurably faster inside tight image loops.

```diff
-                    write_bytes(dst_block_ptr, 0xFF, 8);
+                    (dst_block_ptr as *mut u64)
+                        .write_unaligned(0xFFFF_FFFF_FFFF_FFFFu64);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7cdedc08dd8de8eaf81c9b2eef6f3ffb6502d9dc and a18d7b4754b5e9476663805939956b92ca0edc2a.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs` (11 hunks)
* `projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs` (12 hunks)
* `projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs` (14 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs (3)</summary><blockquote>

<details>
<summary>projects/dxt-lossless-transform-bc3/benches/normalize_blocks/main.rs (1)</summary>

* `core` (123-123)

</details>
<details>
<summary>projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs (2)</summary>

* `null_mut` (276-276)
* `normalize_blocks` (102-156)

</details>
<details>
<summary>projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs (3)</summary>

* `null_mut` (493-493)
* `null_mut` (1149-1149)
* `normalize_blocks` (95-169)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (14)</summary>

* GitHub Check: build-binaries (ubuntu-latest, armv7-unknown-linux-gnueabihf, true)
* GitHub Check: build-binaries (ubuntu-latest, powerpc64-unknown-linux-gnu, true)
* GitHub Check: build-binaries (windows-latest, i686-pc-windows-msvc, false)
* GitHub Check: build-binaries (ubuntu-latest, powerpc-unknown-linux-gnu, true)
* GitHub Check: build-binaries (macos-14, aarch64-apple-darwin, false)
* GitHub Check: build-binaries (windows-latest, x86_64-pc-windows-msvc, false)
* GitHub Check: build-binaries (macos-13, x86_64-apple-darwin, false)
* GitHub Check: build-binaries (ubuntu-latest, i686-unknown-linux-gnu, false)
* GitHub Check: test (ubuntu-latest, i686-unknown-linux-gnu, false, nightly, nightly)
* GitHub Check: build-binaries (ubuntu-latest, aarch64-unknown-linux-gnu, true)
* GitHub Check: test (macos-13, x86_64-apple-darwin, false, stable)
* GitHub Check: test (ubuntu-latest, x86_64-unknown-linux-gnu, false, nightly, nightly)
* GitHub Check: test (windows-latest, x86_64-pc-windows-msvc, false, stable)
* GitHub Check: test (windows-latest, i686-pc-windows-msvc, false, stable)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs (1)</summary>

`109-115`: **Assertion change looks good**

The new assertion correctly permits in-place normalisation while still
catching partial overlaps. Nice!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T06:24:18Z

Review comments:
diff_hunk: @@ -118,13 +119,23 @@ pub unsafe fn normalize_blocks(
     len: usize,
     color_mode: ColorNormalizationMode,
 ) {
+    // Assert that buffers either don't overlap or are identical (in-place transformation)
     debug_assert!(
-        input_ptr.add(len) <= output_ptr || output_ptr.add(len) <= input_ptr as *mut u8,
-        "Input and output memory regions must not overlap"
+        eq(input_ptr, output_ptr as *const u8) || 
+        input_ptr.add(len) <= output_ptr || 
+        output_ptr.add(len) <= input_ptr as *mut u8,
+        "normalize_blocks: overlapping buffers are not supported (must be either completely separate or identical)"
     );
 
     // Skip normalization if mode is None
     if color_mode == ColorNormalizationMode::None {
+        // No need to copy if buffers are identical
+        if eq(input_ptr, output_ptr) {
+            return;
+        }
path: projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Compile-time error: `eq` called with mismatched pointer types**

`core::ptr::eq` expects two `*const T` arguments, but `output_ptr` is `*mut u8`.  
This will not compile.

```diff
-        if eq(input_ptr, output_ptr) {
+        if eq(input_ptr, output_ptr as *const u8) {
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs around lines 132
to 135, the call to core::ptr::eq uses mismatched pointer types: input_ptr is
*const u8 but output_ptr is *mut u8, causing a compile-time error. To fix this,
cast output_ptr to *const u8 before passing it to ptr::eq so both arguments have
the same pointer type. This will resolve the type mismatch and allow the code to
compile.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-17T06:24:18Z
author_association: NONE
diff_hunk: @@ -79,7 +79,8 @@ use likely_stable::unlikely;
 /// - input_ptr must be valid for reads of len bytes
 /// - output_ptr must be valid for writes of len bytes
 /// - len must be divisible by 16 (BC3 block size)
-/// - input_ptr and output_ptr must not overlap
+/// - The implementation supports `input_ptr` == `output_ptr` (in-place transformation)
+/// - The implementation does NOT support partially overlapping buffers (they must either be completely separate or identical)
 ///
path: projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Safety docs allow in-place writes, but `normalize_blocks_all_modes` still panics**

Lines 82-84 correctly state that `input_ptr == output_ptr` is supported.  
However, the debug-assert inside `normalize_blocks_all_modes` (≈ lines 485-490, unchanged in this diff) rejects equality and will `panic!` in debug builds when an in-place buffer is used.

Suggested fix for the assertion:

```diff
-    debug_assert!(
-        input_ptr.add(len) <= out_ptr || out_ptr.add(len) <= input_ptr as *mut _
+    debug_assert!(
+        input_ptr == out_ptr as *const _
+            || input_ptr.add(len) <= out_ptr
+            || out_ptr.add(len) <= input_ptr as *mut _
```

Without this change the new “in-place” guarantee is unsound in debug
builds.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs around lines 82
to 84, the safety documentation states that in-place transformation with
input_ptr equal to output_ptr is supported, but the debug assertion in
normalize_blocks_all_modes (around lines 485-490) incorrectly panics when these
pointers are equal. To fix this, modify the debug assertion to allow input_ptr
and output_ptr to be equal, ensuring it only panics if the buffers partially
overlap but are not identical. This change aligns the assertion with the
documented safety guarantee and prevents unsound panics in debug builds.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-17T06:24:18Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-17T06:20:53Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes add support for in-place normalization to the BC1, BC2, and BC3 block normalization modules. This is achieved by permitting `input_ptr` and `output_ptr` to be identical, updating safety documentation, replacing non-overlapping copy operations with unaligned reads/writes, and introducing new tests to verify correct in-place behavior.

## Changes

| File(s)                                                                                  | Change Summary                                                                                                                                                                                                                                                                                                                                                 |
|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `projects/dxt-lossless-transform-bc1/src/normalize_blocks.rs`                            | Enabled in-place normalization by allowing identical input and output pointers, updated safety documentation, replaced `copy_nonoverlapping` with unaligned reads/writes, adjusted debug assertions, and added the `can_normalize_in_place` test.                                                                      |
| `projects/dxt-lossless-transform-bc2/src/normalize_blocks.rs`                            | Added support for in-place normalization, allowing identical pointers, updated safety docs, replaced copy operations with unaligned reads/writes, added early return for no-op cases, and introduced the `can_perform_inplace_transformation` test.                                                                   |
| `projects/dxt-lossless-transform-bc3/src/normalize_blocks.rs`                            | Modified to support in-place normalization, updated safety comments, replaced copy operations with unaligned reads/writes, used `write_bytes` consistently, and added the `can_normalize_blocks_inplace` test.                                                                                                        |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Caller
    participant Normalizer

    Caller->>Normalizer: normalize_blocks(input_ptr, output_ptr, ...)
    alt input_ptr == output_ptr (in-place)
        Normalizer-->>Normalizer: Perform normalization in-place\n(using unaligned reads/writes)
    else input_ptr != output_ptr
        Normalizer-->>Normalizer: Copy and normalize from input to output
    end
    Normalizer-->>Caller: Normalization complete
```

## Possibly related PRs

- Sewer56/dxt-lossless-transform#29: Introduced the initial BC3 block normalization implementation, which is extended here to support in-place normalization.

## Poem

> A hop, a skip, a pointer's dance,  
> Now in-place moves get their chance!  
> With unaligned reads and careful writes,  
> Buffers twirl through day and night.  
> Tests ensure all blocks behave,  
> As rabbits cheer the code they crave!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Sewer56/dxt-lossless-transform&utm_content=39):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: codecov[bot]
created_at: 2025-05-17T06:21:40Z
body: ## [Codecov](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) Report
Attention: Patch coverage is `65.07937%` with `22 lines` in your changes missing coverage. Please review.
> Project coverage is 74.38%. Comparing base [(`7cdedc0`)](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/commit/7cdedc08dd8de8eaf81c9b2eef6f3ffb6502d9dc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) to head [(`a18d7b4`)](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/commit/a18d7b4754b5e9476663805939956b92ca0edc2a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56).
> Report is 2 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) | Patch % | Lines |
|---|---|---|
| [...dxt-lossless-transform-bc3/src/normalize\_blocks.rs](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&filepath=projects%2Fdxt-lossless-transform-bc3%2Fsrc%2Fnormalize_blocks.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#diff-cHJvamVjdHMvZHh0LWxvc3NsZXNzLXRyYW5zZm9ybS1iYzMvc3JjL25vcm1hbGl6ZV9ibG9ja3MucnM=) | 67.85% | [9 Missing :warning: ](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) |
| [...dxt-lossless-transform-bc1/src/normalize\_blocks.rs](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&filepath=projects%2Fdxt-lossless-transform-bc1%2Fsrc%2Fnormalize_blocks.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#diff-cHJvamVjdHMvZHh0LWxvc3NsZXNzLXRyYW5zZm9ybS1iYzEvc3JjL25vcm1hbGl6ZV9ibG9ja3MucnM=) | 58.82% | [7 Missing :warning: ](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) |
| [...dxt-lossless-transform-bc2/src/normalize\_blocks.rs](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&filepath=projects%2Fdxt-lossless-transform-bc2%2Fsrc%2Fnormalize_blocks.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#diff-cHJvamVjdHMvZHh0LWxvc3NsZXNzLXRyYW5zZm9ybS1iYzIvc3JjL25vcm1hbGl6ZV9ibG9ja3MucnM=) | 66.66% | [6 Missing :warning: ](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) |

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39/graphs/tree.svg?width=650&height=150&src=pr&token=mSqTg8vzAQ&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56)](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56)

```diff
@@            Coverage Diff             @@
##             main      #39      +/-   ##
==========================================
- Coverage   74.45%   74.38%   -0.08%     
==========================================
  Files          61       61              
  Lines        5077     5102      +25     
==========================================
+ Hits         3780     3795      +15     
- Misses       1297     1307      +10     
```

| [Flag](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) | `74.38% <65.07%> (-0.08%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files with missing lines](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56) | Coverage Δ | |
|---|---|---|
| [...dxt-lossless-transform-bc2/src/normalize\_blocks.rs](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&filepath=projects%2Fdxt-lossless-transform-bc2%2Fsrc%2Fnormalize_blocks.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#diff-cHJvamVjdHMvZHh0LWxvc3NsZXNzLXRyYW5zZm9ybS1iYzIvc3JjL25vcm1hbGl6ZV9ibG9ja3MucnM=) | `79.79% <66.66%> (-0.43%)` | :arrow_down: |
| [...dxt-lossless-transform-bc1/src/normalize\_blocks.rs](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&filepath=projects%2Fdxt-lossless-transform-bc1%2Fsrc%2Fnormalize_blocks.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#diff-cHJvamVjdHMvZHh0LWxvc3NsZXNzLXRyYW5zZm9ybS1iYzEvc3JjL25vcm1hbGl6ZV9ibG9ja3MucnM=) | `82.85% <58.82%> (-2.15%)` | :arrow_down: |
| [...dxt-lossless-transform-bc3/src/normalize\_blocks.rs](https://app.codecov.io/gh/Sewer56/dxt-lossless-transform/pull/39?src=pr&el=tree&filepath=projects%2Fdxt-lossless-transform-bc3%2Fsrc%2Fnormalize_blocks.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sewer56#diff-cHJvamVjdHMvZHh0LWxvc3NsZXNzLXRyYW5zZm9ybS1iYzMvc3JjL25vcm1hbGl6ZV9ibG9ja3MucnM=) | `75.33% <67.85%> (-1.48%)` | :arrow_down: |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE