#![allow(unsafe_code)]
#[cfg(not(feature = "std"))]
use crate::compat::*;
use crate::error::{OxiGeoError, Result};
#[cfg(not(feature = "std"))]
use crate::math::FloatExt;
use crate::types::RasterDataType;
mod private {
pub trait Sealed {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RasterElementKind {
Integer,
Float,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FloatToIntRounding {
#[default]
Nearest,
Truncate,
}
pub trait RasterElement: Copy + Default + Send + Sync + 'static + private::Sealed {
const DATA_TYPE: RasterDataType;
const KIND: RasterElementKind;
const SIZE: usize = core::mem::size_of::<Self>();
type Bytes: AsRef<[u8]> + AsMut<[u8]> + Copy + Default + for<'a> TryFrom<&'a [u8]>;
fn from_ne_bytes(bytes: Self::Bytes) -> Self;
fn to_ne_bytes(self) -> Self::Bytes;
fn to_raster_f64(self) -> f64;
fn to_raster_i128(self) -> i128;
fn from_raster_f64(value: f64) -> Self;
fn from_raster_f64_truncating(value: f64) -> Self;
fn from_raster_i128(value: i128) -> Self;
#[inline]
fn from_ne_slice(bytes: &[u8]) -> Self {
match bytes
.get(..Self::SIZE)
.and_then(|head| <Self::Bytes as TryFrom<&[u8]>>::try_from(head).ok())
{
Some(raw) => Self::from_ne_bytes(raw),
None => Self::default(),
}
}
}
#[inline]
fn round_half_away_from_zero(value: f64) -> f64 {
let floor = value.floor();
let fract = value - floor;
if value.is_sign_negative() {
if fract > 0.5 { floor + 1.0 } else { floor }
} else if fract >= 0.5 {
floor + 1.0
} else {
floor
}
}
macro_rules! impl_raster_element_int {
($($ty:ty => $variant:ident, $len:literal;)*) => {
$(
impl private::Sealed for $ty {}
impl RasterElement for $ty {
const DATA_TYPE: RasterDataType = RasterDataType::$variant;
const KIND: RasterElementKind = RasterElementKind::Integer;
type Bytes = [u8; $len];
#[inline]
fn from_ne_bytes(bytes: Self::Bytes) -> Self {
<$ty>::from_ne_bytes(bytes)
}
#[inline]
fn to_ne_bytes(self) -> Self::Bytes {
<$ty>::to_ne_bytes(self)
}
#[inline]
fn to_raster_f64(self) -> f64 {
self as f64
}
#[inline]
fn to_raster_i128(self) -> i128 {
self as i128
}
#[inline]
fn from_raster_f64(value: f64) -> Self {
round_half_away_from_zero(value) as $ty
}
#[inline]
fn from_raster_f64_truncating(value: f64) -> Self {
value as $ty
}
#[inline]
fn from_raster_i128(value: i128) -> Self {
value.clamp(<$ty>::MIN as i128, <$ty>::MAX as i128) as $ty
}
}
)*
};
}
macro_rules! impl_raster_element_float {
($($ty:ty => $variant:ident, $len:literal;)*) => {
$(
impl private::Sealed for $ty {}
impl RasterElement for $ty {
const DATA_TYPE: RasterDataType = RasterDataType::$variant;
const KIND: RasterElementKind = RasterElementKind::Float;
type Bytes = [u8; $len];
#[inline]
fn from_ne_bytes(bytes: Self::Bytes) -> Self {
<$ty>::from_ne_bytes(bytes)
}
#[inline]
fn to_ne_bytes(self) -> Self::Bytes {
<$ty>::to_ne_bytes(self)
}
#[inline]
fn to_raster_f64(self) -> f64 {
self as f64
}
#[inline]
fn to_raster_i128(self) -> i128 {
round_half_away_from_zero(self as f64) as i128
}
#[inline]
fn from_raster_f64(value: f64) -> Self {
value as $ty
}
#[inline]
fn from_raster_f64_truncating(value: f64) -> Self {
value as $ty
}
#[inline]
fn from_raster_i128(value: i128) -> Self {
value as $ty
}
}
)*
};
}
impl_raster_element_int! {
u8 => UInt8, 1;
i8 => Int8, 1;
u16 => UInt16, 2;
i16 => Int16, 2;
u32 => UInt32, 4;
i32 => Int32, 4;
u64 => UInt64, 8;
i64 => Int64, 8;
}
impl_raster_element_float! {
f32 => Float32, 4;
f64 => Float64, 8;
}
macro_rules! dispatch_data_type {
($data_type:expr, $body:ident) => {
match $data_type {
RasterDataType::UInt8 => $body!(u8, 1),
RasterDataType::Int8 => $body!(i8, 1),
RasterDataType::UInt16 => $body!(u16, 2),
RasterDataType::Int16 => $body!(i16, 2),
RasterDataType::UInt32 => $body!(u32, 4),
RasterDataType::Int32 => $body!(i32, 4),
RasterDataType::UInt64 => $body!(u64, 8),
RasterDataType::Int64 => $body!(i64, 8),
RasterDataType::Float32 => $body!(f32, 4),
RasterDataType::Float64 => $body!(f64, 8),
RasterDataType::CFloat32 => $body!(f32, 8),
RasterDataType::CFloat64 => $body!(f64, 16),
}
};
}
#[inline]
fn samples_from_bytes<S: RasterElement>(src: &[u8]) -> Option<&[S]> {
if S::SIZE == 0 || !src.len().is_multiple_of(S::SIZE) {
return None;
}
if src.as_ptr().align_offset(core::mem::align_of::<S>()) != 0 {
return None;
}
Some(unsafe { core::slice::from_raw_parts(src.as_ptr().cast::<S>(), src.len() / S::SIZE) })
}
#[inline]
fn samples_from_bytes_mut<D: RasterElement>(dst: &mut [u8]) -> Option<&mut [D]> {
if D::SIZE == 0 || !dst.len().is_multiple_of(D::SIZE) {
return None;
}
if dst.as_ptr().align_offset(core::mem::align_of::<D>()) != 0 {
return None;
}
let count = dst.len() / D::SIZE;
Some(unsafe { core::slice::from_raw_parts_mut(dst.as_mut_ptr().cast::<D>(), count) })
}
#[inline]
const fn sample_capacity(bytes: usize, size: usize) -> usize {
match bytes.checked_div(size) {
Some(count) => count,
None => 0,
}
}
#[inline]
fn convert_samples<S: RasterElement, D: RasterElement>(
src: &[S],
dst: &mut [D],
rounding: FloatToIntRounding,
) {
macro_rules! run {
($convert:expr) => {{
for (value, out) in src.iter().zip(dst.iter_mut()) {
*out = $convert(*value);
}
}};
}
match (S::KIND, D::KIND, rounding) {
(RasterElementKind::Integer, RasterElementKind::Integer, _) => {
run!(|value: S| D::from_raster_i128(value.to_raster_i128()));
}
(_, _, FloatToIntRounding::Nearest) => {
run!(|value: S| D::from_raster_f64(value.to_raster_f64()));
}
(_, _, FloatToIntRounding::Truncate) => {
run!(|value: S| D::from_raster_f64_truncating(value.to_raster_f64()));
}
}
}
#[inline]
fn convert_unaligned_samples<S: RasterElement, D: RasterElement>(
src: &[u8],
dst: &mut [D],
rounding: FloatToIntRounding,
) {
let count = dst.len().min(sample_capacity(src.len(), S::SIZE));
let base = src.as_ptr();
let Some(dst) = dst.get_mut(..count) else {
return;
};
macro_rules! run {
($convert:expr) => {{
for (index, out) in dst.iter_mut().enumerate() {
let value: S = unsafe { base.add(index * S::SIZE).cast::<S>().read_unaligned() };
*out = $convert(value);
}
}};
}
match (S::KIND, D::KIND, rounding) {
(RasterElementKind::Integer, RasterElementKind::Integer, _) => {
run!(|value: S| D::from_raster_i128(value.to_raster_i128()));
}
(_, _, FloatToIntRounding::Nearest) => {
run!(|value: S| D::from_raster_f64(value.to_raster_f64()));
}
(_, _, FloatToIntRounding::Truncate) => {
run!(|value: S| D::from_raster_f64_truncating(value.to_raster_f64()));
}
}
}
#[inline]
fn convert_contiguous<S: RasterElement, D: RasterElement>(
src: &[u8],
dst: &mut [D],
rounding: FloatToIntRounding,
) {
match samples_from_bytes::<S>(src) {
Some(values) => convert_samples(values, dst, rounding),
None => convert_unaligned_samples::<S, D>(src, dst, rounding),
}
}
#[inline]
fn convert_unaligned_bytes<S: RasterElement, D: RasterElement>(
src: &[u8],
dst: &mut [u8],
rounding: FloatToIntRounding,
) {
let count = sample_capacity(src.len(), S::SIZE).min(sample_capacity(dst.len(), D::SIZE));
let base = src.as_ptr();
let out = dst.as_mut_ptr();
macro_rules! run {
($convert:expr) => {{
for index in 0..count {
let value: S = unsafe { base.add(index * S::SIZE).cast::<S>().read_unaligned() };
let converted: D = $convert(value);
unsafe {
out.add(index * D::SIZE)
.cast::<D>()
.write_unaligned(converted);
}
}
}};
}
match (S::KIND, D::KIND, rounding) {
(RasterElementKind::Integer, RasterElementKind::Integer, _) => {
run!(|value: S| D::from_raster_i128(value.to_raster_i128()));
}
(_, _, FloatToIntRounding::Nearest) => {
run!(|value: S| D::from_raster_f64(value.to_raster_f64()));
}
(_, _, FloatToIntRounding::Truncate) => {
run!(|value: S| D::from_raster_f64_truncating(value.to_raster_f64()));
}
}
}
#[inline]
fn convert_into_typed<S: RasterElement, D: RasterElement, const SRC_STRIDE: usize>(
src: &[u8],
dst: &mut [D],
rounding: FloatToIntRounding,
) {
if SRC_STRIDE == S::SIZE {
convert_contiguous::<S, D>(src, dst, rounding);
return;
}
macro_rules! run {
($convert:expr) => {{
for (chunk, out) in src.chunks_exact(SRC_STRIDE).zip(dst.iter_mut()) {
*out = $convert(S::from_ne_slice(chunk));
}
}};
}
match (S::KIND, D::KIND, rounding) {
(RasterElementKind::Integer, RasterElementKind::Integer, _) => {
run!(|value: S| D::from_raster_i128(value.to_raster_i128()));
}
(_, _, FloatToIntRounding::Nearest) => {
run!(|value: S| D::from_raster_f64(value.to_raster_f64()));
}
(_, _, FloatToIntRounding::Truncate) => {
run!(|value: S| D::from_raster_f64_truncating(value.to_raster_f64()));
}
}
}
#[inline]
fn convert_into_bytes<
S: RasterElement,
D: RasterElement,
const SRC_STRIDE: usize,
const DST_STRIDE: usize,
>(
src: &[u8],
dst: &mut [u8],
rounding: FloatToIntRounding,
) {
if SRC_STRIDE == S::SIZE && DST_STRIDE == D::SIZE {
match samples_from_bytes_mut::<D>(dst) {
Some(out) => convert_contiguous::<S, D>(src, out, rounding),
None => convert_unaligned_bytes::<S, D>(src, dst, rounding),
}
return;
}
macro_rules! run {
($convert:expr) => {{
for (chunk, out) in src
.chunks_exact(SRC_STRIDE)
.zip(dst.chunks_exact_mut(DST_STRIDE))
{
let value: D = $convert(S::from_ne_slice(chunk));
let encoded = value.to_ne_bytes();
if let Some(slot) = out.get_mut(..D::SIZE) {
if let Some(bytes) = encoded.as_ref().get(..D::SIZE) {
slot.copy_from_slice(bytes);
}
}
if DST_STRIDE > D::SIZE {
if let Some(padding) = out.get_mut(D::SIZE..) {
padding.fill(0);
}
}
}
}};
}
match (S::KIND, D::KIND, rounding) {
(RasterElementKind::Integer, RasterElementKind::Integer, _) => {
run!(|value: S| D::from_raster_i128(value.to_raster_i128()));
}
(_, _, FloatToIntRounding::Nearest) => {
run!(|value: S| D::from_raster_f64(value.to_raster_f64()));
}
(_, _, FloatToIntRounding::Truncate) => {
run!(|value: S| D::from_raster_f64_truncating(value.to_raster_f64()));
}
}
}
#[cold]
#[inline(never)]
fn zero_stride_error() -> OxiGeoError {
OxiGeoError::Internal {
message: "Raster data type reported a zero-byte sample size".to_string(),
}
}
#[cold]
#[inline(never)]
fn partial_sample_error(src_len: usize, stride: usize) -> OxiGeoError {
OxiGeoError::InvalidParameter {
parameter: "src",
message: format!(
"Source length {} is not a whole number of {}-byte samples",
src_len, stride
),
}
}
#[cold]
#[inline(never)]
fn length_mismatch_error(
count: usize,
src_len: usize,
stride: usize,
dst_len: usize,
) -> OxiGeoError {
OxiGeoError::InvalidParameter {
parameter: "dst",
message: format!(
"Destination length mismatch: source holds {} samples ({} bytes / {} bytes each), destination has {}",
count, src_len, stride, dst_len
),
}
}
fn sample_count(src_len: usize, stride: usize, dst_len: usize) -> Result<usize> {
if stride == 0 {
return Err(zero_stride_error());
}
if !src_len.is_multiple_of(stride) {
return Err(partial_sample_error(src_len, stride));
}
let count = src_len / stride;
if count != dst_len {
return Err(length_mismatch_error(count, src_len, stride, dst_len));
}
Ok(count)
}
#[inline]
fn check_sample_count<const STRIDE: usize>(src_len: usize, dst_len: usize) -> Result<()> {
if STRIDE == 0 {
return Err(zero_stride_error());
}
if !src_len.is_multiple_of(STRIDE) {
return Err(partial_sample_error(src_len, STRIDE));
}
let count = src_len / STRIDE;
if count != dst_len {
return Err(length_mismatch_error(count, src_len, STRIDE, dst_len));
}
Ok(())
}
#[inline]
fn copy_same_type<T: RasterElement>(src: &[u8], dst: &mut [T]) {
let dst_bytes = unsafe {
core::slice::from_raw_parts_mut(dst.as_mut_ptr().cast::<u8>(), core::mem::size_of_val(dst))
};
if dst_bytes.len() == src.len() {
dst_bytes.copy_from_slice(src);
}
}
#[must_use]
pub fn elements_as_bytes<T: RasterElement>(values: &[T]) -> &[u8] {
unsafe {
core::slice::from_raw_parts(values.as_ptr().cast::<u8>(), core::mem::size_of_val(values))
}
}
pub fn convert_raw_into<T: RasterElement>(
src: &[u8],
src_type: RasterDataType,
dst: &mut [T],
) -> Result<()> {
convert_raw_into_with(src, src_type, dst, FloatToIntRounding::Nearest)
}
pub fn convert_raw_into_with<T: RasterElement>(
src: &[u8],
src_type: RasterDataType,
dst: &mut [T],
rounding: FloatToIntRounding,
) -> Result<()> {
macro_rules! body {
($source:ty, $stride:literal) => {{
check_sample_count::<$stride>(src.len(), dst.len())?;
if src_type == T::DATA_TYPE {
copy_same_type(src, dst);
} else {
convert_into_typed::<$source, T, $stride>(src, dst, rounding);
}
}};
}
dispatch_data_type!(src_type, body);
Ok(())
}
pub fn convert_raw_bytes(
src: &[u8],
src_type: RasterDataType,
dst: &mut [u8],
dst_type: RasterDataType,
rounding: FloatToIntRounding,
) -> Result<()> {
let src_stride = src_type.size_bytes();
let dst_stride = dst_type.size_bytes();
if dst_stride == 0 {
return Err(OxiGeoError::Internal {
message: "Raster data type reported a zero-byte sample size".to_string(),
});
}
if !dst.len().is_multiple_of(dst_stride) {
return Err(OxiGeoError::InvalidParameter {
parameter: "dst",
message: format!(
"Destination length {} is not a whole number of {}-byte samples",
dst.len(),
dst_stride
),
});
}
sample_count(src.len(), src_stride, dst.len() / dst_stride)?;
if src_type == dst_type {
dst.copy_from_slice(src);
return Ok(());
}
macro_rules! destination {
($dest:ty, $dst_stride:literal) => {{
macro_rules! source {
($source:ty, $src_stride:literal) => {
convert_into_bytes::<$source, $dest, $src_stride, $dst_stride>(
src, dst, rounding,
)
};
}
dispatch_data_type!(src_type, source)
}};
}
dispatch_data_type!(dst_type, destination);
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
#[test]
fn test_data_type_mapping() {
assert_eq!(u8::DATA_TYPE, RasterDataType::UInt8);
assert_eq!(i8::DATA_TYPE, RasterDataType::Int8);
assert_eq!(u16::DATA_TYPE, RasterDataType::UInt16);
assert_eq!(i16::DATA_TYPE, RasterDataType::Int16);
assert_eq!(u32::DATA_TYPE, RasterDataType::UInt32);
assert_eq!(i32::DATA_TYPE, RasterDataType::Int32);
assert_eq!(u64::DATA_TYPE, RasterDataType::UInt64);
assert_eq!(i64::DATA_TYPE, RasterDataType::Int64);
assert_eq!(f32::DATA_TYPE, RasterDataType::Float32);
assert_eq!(f64::DATA_TYPE, RasterDataType::Float64);
}
#[test]
fn test_elements_are_send_and_sync() {
const fn assert_send_sync<T: Send + Sync>() {}
const fn assert_element<T: RasterElement>() {
assert_send_sync::<T>();
}
assert_element::<u8>();
assert_element::<i8>();
assert_element::<u16>();
assert_element::<i16>();
assert_element::<u32>();
assert_element::<i32>();
assert_element::<u64>();
assert_element::<i64>();
assert_element::<f32>();
assert_element::<f64>();
}
#[test]
fn test_size_matches_data_type() {
assert_eq!(u8::SIZE, RasterDataType::UInt8.size_bytes());
assert_eq!(i16::SIZE, RasterDataType::Int16.size_bytes());
assert_eq!(f32::SIZE, RasterDataType::Float32.size_bytes());
assert_eq!(f64::SIZE, RasterDataType::Float64.size_bytes());
assert_eq!(u64::SIZE, RasterDataType::UInt64.size_bytes());
}
#[test]
fn test_round_half_away_from_zero() {
assert_eq!(round_half_away_from_zero(2.5), 3.0);
assert_eq!(round_half_away_from_zero(-2.5), -3.0);
assert_eq!(round_half_away_from_zero(2.4999), 2.0);
assert_eq!(round_half_away_from_zero(-2.4999), -2.0);
assert_eq!(round_half_away_from_zero(0.0), 0.0);
assert_eq!(round_half_away_from_zero(-0.4), -0.0);
assert_eq!(round_half_away_from_zero(0.499_999_999_999_999_94), 0.0);
let big = 9_007_199_254_740_993f64; assert_eq!(round_half_away_from_zero(big), big);
assert_eq!(
round_half_away_from_zero(4_503_599_627_370_497.0),
4_503_599_627_370_497.0
);
assert!(round_half_away_from_zero(f64::NAN).is_nan());
assert_eq!(round_half_away_from_zero(f64::INFINITY), f64::INFINITY);
assert_eq!(
round_half_away_from_zero(f64::NEG_INFINITY),
f64::NEG_INFINITY
);
}
#[test]
fn test_float_to_int_saturates() {
assert_eq!(u8::from_raster_f64(300.0), 255);
assert_eq!(u8::from_raster_f64(-5.0), 0);
assert_eq!(i8::from_raster_f64(1e9), 127);
assert_eq!(i8::from_raster_f64(-1e9), -128);
assert_eq!(u8::from_raster_f64(f64::NAN), 0);
assert_eq!(i32::from_raster_f64(f64::INFINITY), i32::MAX);
assert_eq!(i32::from_raster_f64(f64::NEG_INFINITY), i32::MIN);
}
#[test]
fn test_int_bridge_is_exact_beyond_f64_mantissa() {
let value = (1i128 << 53) + 1;
assert_eq!(i64::from_raster_i128(value), 9_007_199_254_740_993);
assert_eq!(u64::from_raster_i128(u64::MAX as i128), u64::MAX);
assert_eq!(u64::from_raster_i128(-1), 0);
assert_eq!(i32::from_raster_i128(i128::MAX), i32::MAX);
assert_eq!(i32::from_raster_i128(i128::MIN), i32::MIN);
}
#[test]
fn test_from_ne_slice_truncates_and_pads() {
let bytes = 0x0102_0304u32.to_ne_bytes();
assert_eq!(u32::from_ne_slice(&bytes), 0x0102_0304);
let mut padded = bytes.to_vec();
padded.extend_from_slice(&[0xFF; 4]);
assert_eq!(u32::from_ne_slice(&padded), 0x0102_0304);
assert_eq!(u32::from_ne_slice(&bytes[..2]), 0);
}
#[test]
fn test_elements_as_bytes() {
let values = [0x0102u16, 0x0304u16];
let bytes = elements_as_bytes(&values);
assert_eq!(bytes.len(), 4);
assert_eq!(
bytes,
&[0x0102u16.to_ne_bytes(), 0x0304u16.to_ne_bytes()].concat()[..]
);
}
#[test]
fn test_complex_source_reads_real_component() {
let mut raw = Vec::new();
raw.extend_from_slice(&1.5f32.to_ne_bytes());
raw.extend_from_slice(&9.0f32.to_ne_bytes());
raw.extend_from_slice(&(-2.5f32).to_ne_bytes());
raw.extend_from_slice(&7.0f32.to_ne_bytes());
let mut dst = [0.0f64; 2];
convert_raw_into(&raw, RasterDataType::CFloat32, &mut dst).expect("convert");
assert_eq!(dst, [1.5, -2.5]);
}
#[test]
fn test_complex_destination_zeroes_imaginary() {
let src: Vec<u8> = [1.5f32, -2.5]
.iter()
.flat_map(|v| v.to_ne_bytes())
.collect();
let mut dst = vec![0xAAu8; 16];
convert_raw_bytes(
&src,
RasterDataType::Float32,
&mut dst,
RasterDataType::CFloat32,
FloatToIntRounding::Nearest,
)
.expect("convert");
assert_eq!(&dst[0..4], &1.5f32.to_ne_bytes());
assert_eq!(&dst[4..8], &0f32.to_ne_bytes());
assert_eq!(&dst[8..12], &(-2.5f32).to_ne_bytes());
assert_eq!(&dst[12..16], &0f32.to_ne_bytes());
}
#[test]
fn test_length_validation() {
let src = [0u8; 6];
let mut dst = [0u16; 3];
assert!(convert_raw_into(&src, RasterDataType::Float32, &mut dst).is_err());
assert!(convert_raw_into(&src, RasterDataType::UInt16, &mut dst).is_ok());
let mut short = [0u16; 2];
assert!(convert_raw_into(&src, RasterDataType::UInt16, &mut short).is_err());
}
#[test]
fn test_unaligned_source_slice() {
let mut raw = vec![0u8; 1];
for value in [1.0f64, -2.0, 3.5] {
raw.extend_from_slice(&value.to_ne_bytes());
}
let mut dst = [0.0f32; 3];
convert_raw_into(&raw[1..], RasterDataType::Float64, &mut dst).expect("convert");
assert_eq!(dst, [1.0f32, -2.0, 3.5]);
}
#[test]
fn test_memcpy_fast_path_is_bit_exact() {
let values = [f32::NAN, f32::INFINITY, -0.0, 1.25];
let raw: Vec<u8> = values.iter().flat_map(|v| v.to_ne_bytes()).collect();
let mut dst = [0.0f32; 4];
convert_raw_into(&raw, RasterDataType::Float32, &mut dst).expect("convert");
assert!(dst[0].is_nan());
assert_eq!(dst[1], f32::INFINITY);
assert_eq!(dst[2].to_bits(), (-0.0f32).to_bits());
assert_eq!(dst[3], 1.25);
}
#[test]
fn test_empty_input() {
let mut dst: [f64; 0] = [];
convert_raw_into(&[], RasterDataType::UInt16, &mut dst).expect("convert");
convert_raw_into(&[], RasterDataType::Float64, &mut dst).expect("memcpy path");
}
fn reference_convert<S: RasterElement, D: RasterElement>(
src: &[u8],
dst: &mut [D],
rounding: FloatToIntRounding,
) {
for (chunk, out) in src.chunks_exact(S::SIZE).zip(dst.iter_mut()) {
let value = S::from_ne_slice(chunk);
*out = match (S::KIND, D::KIND, rounding) {
(RasterElementKind::Integer, RasterElementKind::Integer, _) => {
D::from_raster_i128(value.to_raster_i128())
}
(_, _, FloatToIntRounding::Nearest) => D::from_raster_f64(value.to_raster_f64()),
(_, _, FloatToIntRounding::Truncate) => {
D::from_raster_f64_truncating(value.to_raster_f64())
}
};
}
}
fn assert_same_bits<D: RasterElement>(actual: &[D], expected: &[D], label: &str) {
if cfg!(miri)
&& D::KIND == RasterElementKind::Float
&& actual.len() == expected.len()
&& actual.iter().zip(expected.iter()).all(|(a, b)| {
let (a, b) = (a.to_raster_f64(), b.to_raster_f64());
a.to_bits() == b.to_bits() || (a.is_nan() && b.is_nan())
})
{
return;
}
assert_eq!(
elements_as_bytes(actual),
elements_as_bytes(expected),
"{label}"
);
}
fn assert_paths_agree<S: RasterElement, D: RasterElement>(
values: &[S],
rounding: FloatToIntRounding,
) {
let count = values.len();
let src = elements_as_bytes(values);
let mut expected = vec![D::default(); count];
reference_convert::<S, D>(src, &mut expected, rounding);
let label = |path: &str| {
format!(
"{:?} -> {:?} ({:?}) mismatch on the {path} path",
S::DATA_TYPE,
D::DATA_TYPE,
rounding
)
};
let mut typed = vec![D::default(); count];
convert_raw_into_with(src, S::DATA_TYPE, &mut typed, rounding).expect("typed convert");
assert_same_bits(&typed, &expected, &label("typed"));
let mut shifted = Vec::with_capacity(src.len() + 1);
shifted.push(0xA5u8);
shifted.extend_from_slice(src);
let mut unaligned = vec![D::default(); count];
convert_raw_into_with(&shifted[1..], S::DATA_TYPE, &mut unaligned, rounding)
.expect("unaligned convert");
assert_same_bits(&unaligned, &expected, &label("unaligned-source"));
let mut bytes_out = vec![D::default(); count];
convert_raw_bytes(
src,
S::DATA_TYPE,
byte_view(&mut bytes_out),
D::DATA_TYPE,
rounding,
)
.expect("byte convert");
assert_same_bits(&bytes_out, &expected, &label("bytes"));
let mut shifted_out = vec![0u8; count * D::SIZE + 1];
convert_raw_bytes(
&shifted[1..],
S::DATA_TYPE,
&mut shifted_out[1..],
D::DATA_TYPE,
rounding,
)
.expect("byte convert (misaligned)");
let mut recovered = vec![D::default(); count];
for (slot, chunk) in recovered
.iter_mut()
.zip(shifted_out[1..].chunks_exact(D::SIZE))
{
*slot = D::from_ne_slice(chunk);
}
assert_same_bits(&recovered, &expected, &label("unaligned-destination"));
}
fn byte_view<D: RasterElement>(values: &mut [D]) -> &mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(
values.as_mut_ptr().cast::<u8>(),
core::mem::size_of_val(values),
)
}
}
fn padded<S: RasterElement>(values: &[S]) -> Vec<S> {
const LENGTH: usize = if cfg!(miri) { 37 } else { 293 };
let mut out = Vec::with_capacity(LENGTH);
while out.len() < LENGTH {
out.extend_from_slice(values);
}
out.truncate(LENGTH);
out
}
fn check_all_destinations<S: RasterElement>(values: &[S]) {
let values = padded(values);
for rounding in [FloatToIntRounding::Nearest, FloatToIntRounding::Truncate] {
assert_paths_agree::<S, u8>(&values, rounding);
assert_paths_agree::<S, i8>(&values, rounding);
assert_paths_agree::<S, u16>(&values, rounding);
assert_paths_agree::<S, i16>(&values, rounding);
assert_paths_agree::<S, u32>(&values, rounding);
assert_paths_agree::<S, i32>(&values, rounding);
assert_paths_agree::<S, u64>(&values, rounding);
assert_paths_agree::<S, i64>(&values, rounding);
assert_paths_agree::<S, f32>(&values, rounding);
assert_paths_agree::<S, f64>(&values, rounding);
}
}
#[test]
fn test_bulk_paths_match_reference_for_integer_sources() {
check_all_destinations::<u8>(&[0, 1, 127, 128, 254, 255]);
check_all_destinations::<i8>(&[i8::MIN, -128 + 1, -1, 0, 1, 126, i8::MAX]);
check_all_destinations::<u16>(&[0, 1, 255, 256, 32_767, 32_768, 65_534, u16::MAX]);
check_all_destinations::<i16>(&[i16::MIN, -32_767, -1, 0, 1, 255, 256, i16::MAX]);
check_all_destinations::<u32>(&[0, 1, 65_535, 65_536, 2_147_483_647, u32::MAX]);
check_all_destinations::<i32>(&[i32::MIN, -1, 0, 1, 16_777_217, i32::MAX]);
check_all_destinations::<u64>(&[0, 1, (1 << 53) + 1, u64::MAX - 1, u64::MAX]);
check_all_destinations::<i64>(&[i64::MIN, -(1 << 53) - 1, -1, 0, (1 << 53) + 1, i64::MAX]);
}
#[test]
fn test_bulk_paths_match_reference_for_float_sources() {
let f32_values = [
f32::from_bits(0x7FC0_1234),
f32::from_bits(0xFFC0_0001),
f32::NAN,
f32::INFINITY,
f32::NEG_INFINITY,
0.0,
-0.0,
f32::MIN_POSITIVE,
f32::from_bits(1), -f32::from_bits(1), 2.5,
-2.5,
0.499_999_97,
255.5,
-0.5,
65_535.5,
f32::MAX,
f32::MIN,
16_777_217.0,
-16_777_217.0,
];
check_all_destinations::<f32>(&f32_values);
let f64_values = [
f64::from_bits(0x7FF8_0000_1234_5678),
f64::from_bits(0xFFF8_0000_0000_0001),
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
0.0,
-0.0,
f64::MIN_POSITIVE,
f64::from_bits(1),
-f64::from_bits(1),
2.5,
-2.5,
0.499_999_999_999_999_94,
255.5,
-0.5,
4_294_967_295.5,
9_007_199_254_740_993.0,
f64::MAX,
f64::MIN,
1e300,
];
check_all_destinations::<f64>(&f64_values);
}
#[test]
fn test_misaligned_source_takes_the_unaligned_path() {
let raw: Vec<u8> = (0u8..64).collect();
assert!(
samples_from_bytes::<u8>(&raw[1..]).is_some(),
"u8 is align 1"
);
assert!(samples_from_bytes::<u16>(&raw[1..]).is_none());
assert!(samples_from_bytes::<f32>(&raw[1..]).is_none());
assert!(samples_from_bytes::<f64>(&raw[1..]).is_none());
assert!(samples_from_bytes::<f64>(&raw[..12]).is_none());
}
#[test]
fn test_unaligned_load_decodes_exactly_like_from_ne_bytes() {
let values = [
f32::from_bits(0x7FC0_1234),
-0.0,
f32::from_bits(1),
1.25,
f32::NEG_INFINITY,
];
let mut shifted = vec![0xA5u8];
shifted.extend_from_slice(elements_as_bytes(&values));
let expected_bits: Vec<u32> = values.iter().map(|v| v.to_bits()).collect();
let mut as_bits = [0u32; 5];
convert_unaligned_samples::<u32, u32>(
&shifted[1..],
&mut as_bits,
FloatToIntRounding::Nearest,
);
assert_eq!(as_bits.as_slice(), expected_bits.as_slice());
let mut bytes_out = vec![0u8; values.len() * 4 + 1];
convert_unaligned_bytes::<u32, u32>(
&shifted[1..],
&mut bytes_out[1..],
FloatToIntRounding::Nearest,
);
assert_eq!(&bytes_out[1..], elements_as_bytes(&values));
let mut decoded = [0.0f32; 5];
convert_unaligned_samples::<f32, f32>(
&shifted[1..],
&mut decoded,
FloatToIntRounding::Nearest,
);
assert_same_bits(&decoded, &values, "unaligned f32 decode");
}
#[test]
fn test_sample_capacity_never_divides_by_zero() {
assert_eq!(sample_capacity(9, 4), 2);
assert_eq!(sample_capacity(0, 4), 0);
assert_eq!(sample_capacity(9, 0), 0);
}
}