blend-srgb 0.1.1

A small, `#![no_std]`-compatible sRGB conversion and blending library designed for performance
Documentation
//! Functions for converting between sRGB and linear space. There are
//! floating-point ([`srgb_to_rgb`] and [`rgb_to_srgb`]) and integer
//! ([`srgb8_to_rgb12`] and [`rgb12_to_srgb8`]) versions.
//!
//! The reason for using 12-bit integers rather than 8- or 16-bit has to do with
//! the fact that this library uses lookup tables to implement its constant-time
//! conversions. Due to this, the input space must be small or else the tables
//! get quite large. 12 bits is enough to store the entire range of 8-bit sRGB
//! values in linear space, while still having a reasonably-sized lookup table.

/// Converts the given non-linear sRGB component (0-1) to a linear component.
/// Due to floating-point imprecision, this function is **not** guaranteed to
/// roundtrip with [`rgb_to_srgb`].
#[cfg(all(feature = "std", not(feature = "libm")))]
#[inline]
pub fn srgb_to_rgb(v: f32) -> f32 {
	if v < 0.0404599 {
		v / 12.9232102
	} else {
		((v + 0.055) / 1.055).powf(2.4)
	}
}

/// Converts the given linear component (0-1) to a non-linear sRGB component.
/// Due to floating-point imprecision, this function is **not** guaranteed to
/// roundtrip with [`srgb_to_rgb`].
#[cfg(all(feature = "std", not(feature = "libm")))]
#[inline]
pub fn rgb_to_srgb(v: f32) -> f32 {
	if v < 0.0031308 {
		v * 12.9232102
	} else {
		const GAMMA: f32 = 1.0 / 2.4;
		1.055 * v.powf(GAMMA) - 0.055
	}
}

/// Converts the given non-linear sRGB component (0-1) to a linear component.
/// Due to floating-point imprecision, this function is **not** guaranteed to
/// roundtrip with [`rgb_to_srgb`].
#[cfg(feature = "libm")]
pub fn srgb_to_rgb(v: f32) -> f32 {
	if v < 0.0404599 {
		v / 12.9232102
	} else {
		libm::powf((v + 0.055) / 1.055, 2.4)
	}
}

/// Converts the given linear component (0-1) to a non-linear sRGB component.
/// Due to floating-point imprecision, this function is **not** guaranteed to
/// roundtrip with [`srgb_to_rgb`].
#[cfg(feature = "libm")]
pub fn rgb_to_srgb(v: f32) -> f32 {
	if v < 0.0031308 {
		v * 12.9232102
	} else {
		const GAMMA: f32 = 1.0 / 2.4;
		1.055 * libm::powf(v, GAMMA) - 0.055
	}
}

/// Converts the given sRGB 8-bit value to a linear 12-bit value.
///
/// [`srgb8_to_rgb12`] and [`rgb12_to_srgb8`] are guaranteed to roundtrip; that
/// is, `rgb12_to_srgb8(srgb8_to_rgb12(value))` will always return `value`.
/// However, exact quantization guarantees are not provided. Blending is fast,
/// but may be off-by-one in specific edge cases.
#[inline]
pub fn srgb8_to_rgb12(srgb8: u8) -> u16 {
	// 256 big-endian u16s
	const SRGB8_TO_RGB12: &[u8; 512] = include_bytes!("srgb8_to_rgb12.bin");

	// SAFETY: u8 cannot store values above 255, and 510/511 are safe indices
	unsafe {
		let offset = srgb8 as usize * 2;

		u16::from_be_bytes([
			*SRGB8_TO_RGB12.get_unchecked(offset),
			*SRGB8_TO_RGB12.get_unchecked(offset + 1)
		])
	}
}

/// Converts the given RGB 12-bit value to an sRGB 8-bit value. Truncates the
/// given value to 12 bits. Also see [`rgb12_to_srgb8_unchecked`] for the unsafe
/// version.
///
/// [`srgb8_to_rgb12`] and [`rgb12_to_srgb8`] are guaranteed to roundtrip; that
/// is, `rgb12_to_srgb8(srgb8_to_rgb12(value))` will always return `value`.
/// However, exact quantization guarantees are not provided. Blending is fast,
/// but may be off-by-one in specific edge cases.
#[inline]
pub fn rgb12_to_srgb8(rgb12: u16) -> u8 {
	unsafe { rgb12_to_srgb8_unchecked(rgb12 & 0xFFF) }
}

/// Converts the given RGB 12-bit value to an sRGB 8-bit value. Undefined
/// behavior results if the given value does not fit in 12 bits. Also see
/// [`rgb12_to_srgb8`] for the safe version.
///
/// [`srgb8_to_rgb12`] and [`rgb12_to_srgb8`] are guaranteed to roundtrip; that
/// is, `rgb12_to_srgb8(srgb8_to_rgb12(value))` will always return `value`.
/// However, exact quantization guarantees are not provided. Blending is fast,
/// but may be off-by-one in specific edge cases.
#[inline]
pub unsafe fn rgb12_to_srgb8_unchecked(rgb12: u16) -> u8 {
	const RGB12_TO_SRGB8: &[u8; 4096] = include_bytes!("rgb12_to_srgb8.bin");
	*RGB12_TO_SRGB8.get_unchecked(rgb12 as usize)
}