wagahai_lut 0.1.0

CUBE LUT parser and image processing library with SIMD
Documentation
/*
 * SPDX-FileCopyrightText: © 2026 Jinwoo Park (pmnxis@gmail.com)
 *
 * SPDX-License-Identifier: MIT
 */

//! 1D LUT data structures

use super::Rgb;
use crate::error::{CubeError, Result};

/// Macro to generate Lut1D enum variants and implementations
macro_rules! generate_lut1d_variants {
    ($(
        $variant:ident, $size:expr, $bits:expr
    ),* $(,)?) => {
        /// Optimized 1D LUT with three independent channels (R, G, B)
        /// Uses heap-allocated fixed-size arrays for common bit depths for maximum performance
        #[derive(Clone)]
        pub enum Lut1D {
            $(
                #[doc = concat!($bits, "-bit (", stringify!($size), " points) - heap-allocated array for optimal performance")]
                $variant { data: Box<[Rgb; $size]> },
            )*
            /// Other sizes - uses Vec for flexibility
            Other { size: usize, data: Vec<Rgb> },
        }

        impl std::fmt::Debug for Lut1D {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    $(
                        Lut1D::$variant { .. } => write!(f, concat!("Lut1D::", stringify!($variant), " (", $bits, "-bit)")),
                    )*
                    Lut1D::Other { size, .. } => write!(f, "Lut1D::Other (size: {})", size),
                }
            }
        }

        impl Lut1D {
            /// Create a new 1D LUT
            pub fn new(size: usize) -> Result<Self> {
                if !(2..=65536).contains(&size) {
                    return Err(CubeError::LutSizeOutOfRange {
                        size,
                        min: 2,
                        max: 65536,
                    });
                }

                Ok(match size {
                    $(
                        $size => {
                            let data: Box<[Rgb; $size]> = Box::new([[0.0, 0.0, 0.0]; $size]);
                            Lut1D::$variant { data }
                        }
                    )*
                    _ => {
                        let data = vec![[0.0, 0.0, 0.0]; size];
                        Lut1D::Other { size, data }
                    }
                })
            }

            /// Get the size of the LUT
            pub fn size(&self) -> usize {
                match self {
                    $(
                        Lut1D::$variant { .. } => $size,
                    )*
                    Lut1D::Other { size, .. } => *size,
                }
            }

            /// Get data as slice
            #[inline]
            pub fn as_slice(&self) -> &[Rgb] {
                match self {
                    $(
                        Lut1D::$variant { data } => &**data,
                    )*
                    Lut1D::Other { data, .. } => data.as_slice(),
                }
            }

            /// Get mutable data as slice
            #[inline]
            pub fn as_slice_mut(&mut self) -> &mut [Rgb] {
                match self {
                    $(
                        Lut1D::$variant { data } => &mut **data,
                    )*
                    Lut1D::Other { data, .. } => data.as_mut_slice(),
                }
            }

            /// Get value at index for a specific channel (0=R, 1=G, 2=B)
            pub fn get(&self, index: usize, channel: usize) -> Result<f32> {
                if channel > 2 {
                    return Err(CubeError::IndexOutOfBounds {
                        index: channel,
                        len: 3,
                    });
                }
                let size = self.size();
                if index >= size {
                    return Err(CubeError::IndexOutOfBounds { index, len: size });
                }
                Ok(self.as_slice()[index][channel])
            }

            /// Get value without bounds checking (unsafe, for performance)
            #[inline]
            pub unsafe fn get_unchecked(&self, index: usize, channel: usize) -> f32 {
                let data = self.as_slice();
                (*data.get_unchecked(index))[channel]
            }

            /// Set value at index for a specific channel (0=R, 1=G, 2=B)
            pub fn set(&mut self, index: usize, channel: usize, value: f32) -> Result<()> {
                if channel > 2 {
                    return Err(CubeError::IndexOutOfBounds {
                        index: channel,
                        len: 3,
                    });
                }
                let size = self.size();
                if index >= size {
                    return Err(CubeError::IndexOutOfBounds { index, len: size });
                }
                let data = self.as_slice_mut();
                data[index][channel] = value;
                Ok(())
            }

            /// Get full RGB triplet at index
            pub fn get_rgb(&self, index: usize) -> Result<Rgb> {
                let size = self.size();
                if index >= size {
                    return Err(CubeError::IndexOutOfBounds { index, len: size });
                }
                Ok(self.as_slice()[index])
            }

            /// Get RGB without bounds checking (unsafe, for performance)
            #[inline]
            pub unsafe fn get_rgb_unchecked(&self, index: usize) -> Rgb {
                let data = self.as_slice();
                *data.get_unchecked(index)
            }

            /// Set full RGB triplet at index
            pub fn set_rgb(&mut self, index: usize, rgb: Rgb) -> Result<()> {
                let size = self.size();
                if index >= size {
                    return Err(CubeError::IndexOutOfBounds { index, len: size });
                }
                let data = self.as_slice_mut();
                data[index] = rgb;
                Ok(())
            }
        }
    };
}

// Generate Lut1D variants for common sizes
generate_lut1d_variants!(
    Bit10, 1024, "10-bit", Bit12, 4096, "12-bit", Bit14, 16384, "14-bit", Bit16, 65536, "16-bit"
);