use crate::clang::*;
use crate::x86::*;
pub const SIMD_SSE_BYTES: usize = 16;
pub const SIMD_AVX_BYTES: usize = 32;
pub const SIMD_AVX512_BYTES: usize = 64;
pub const SSE_LANES_F32: usize = 4;
pub const AVX_LANES_F32: usize = 8;
pub const AVX512_LANES_F32: usize = 16;
pub const SSE_LANES_F64: usize = 2;
pub const AVX_LANES_F64: usize = 4;
pub const AVX512_LANES_F64: usize = 8;
pub const MAX_IMAGE_DIMENSION: usize = 65536;
pub const DEFAULT_TILE_SIZE: usize = 64;
pub const MAT4_SIZE: usize = 4;
const PI: f64 = std::f64::consts::PI;
const TAU: f64 = 2.0 * std::f64::consts::PI;
const DEG_TO_RAD: f64 = PI / 180.0;
const RAD_TO_DEG: f64 = 180.0 / PI;
const EPSILON: f32 = 1e-6;
#[inline]
fn frexp(x: f64) -> (f64, i32) {
if x == 0.0 {
return (0.0, 0);
}
let bits = x.to_bits();
let exponent = ((bits >> 52) & 0x7FF) as i32;
if exponent == 0 {
let mantissa = f64::from_bits((bits & 0x800F_FFFF_FFFF_FFFF) | 0x3FE0_0000_0000_0000);
(mantissa - 1.0, -1022)
} else {
let mantissa = f64::from_bits((bits & 0x800F_FFFF_FFFF_FFFF) | 0x3FE0_0000_0000_0000);
(mantissa, exponent - 1023)
}
}
#[inline]
fn ldexp(x: f64, n: i32) -> f64 {
x * (2.0f64).powi(n)
}
#[derive(Debug, Clone)]
pub struct AABB {
pub min: [f32; 3],
pub max: [f32; 3],
}
impl AABB {
pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
Self { min, max }
}
pub fn empty() -> Self {
Self {
min: [f32::MAX, f32::MAX, f32::MAX],
max: [f32::MIN, f32::MIN, f32::MIN],
}
}
pub fn center(&self) -> [f32; 3] {
[
(self.min[0] + self.max[0]) * 0.5,
(self.min[1] + self.max[1]) * 0.5,
(self.min[2] + self.max[2]) * 0.5,
]
}
pub fn half_extents(&self) -> [f32; 3] {
[
(self.max[0] - self.min[0]) * 0.5,
(self.max[1] - self.min[1]) * 0.5,
(self.max[2] - self.min[2]) * 0.5,
]
}
pub fn intersects(&self, other: &AABB) -> bool {
self.min[0] <= other.max[0]
&& self.max[0] >= other.min[0]
&& self.min[1] <= other.max[1]
&& self.max[1] >= other.min[1]
&& self.min[2] <= other.max[2]
&& self.max[2] >= other.min[2]
}
pub fn contains_point(&self, p: &[f32; 3]) -> bool {
p[0] >= self.min[0]
&& p[0] <= self.max[0]
&& p[1] >= self.min[1]
&& p[1] <= self.max[1]
&& p[2] >= self.min[2]
&& p[2] <= self.max[2]
}
pub fn extend(&mut self, p: &[f32; 3]) {
self.min[0] = self.min[0].min(p[0]);
self.min[1] = self.min[1].min(p[1]);
self.min[2] = self.min[2].min(p[2]);
self.max[0] = self.max[0].max(p[0]);
self.max[1] = self.max[1].max(p[1]);
self.max[2] = self.max[2].max(p[2]);
}
pub fn merge(&mut self, other: &AABB) {
self.min[0] = self.min[0].min(other.min[0]);
self.min[1] = self.min[1].min(other.min[1]);
self.min[2] = self.min[2].min(other.min[2]);
self.max[0] = self.max[0].max(other.max[0]);
self.max[1] = self.max[1].max(other.max[1]);
self.max[2] = self.max[2].max(other.max[2]);
}
}
pub struct Ray {
pub origin: [f32; 3],
pub direction: [f32; 3],
}
impl Ray {
pub fn new(origin: [f32; 3], direction: [f32; 3]) -> Self {
Self { origin, direction }
}
pub fn at(&self, t: f32) -> [f32; 3] {
[
self.origin[0] + t * self.direction[0],
self.origin[1] + t * self.direction[1],
self.origin[2] + t * self.direction[2],
]
}
}
pub struct Frustum {
pub planes: [[f32; 4]; 6],
}
impl Frustum {
pub fn from_view_projection(vp: &[f32; 16]) -> Self {
let mut planes = [[0.0f32; 4]; 6];
let t = |j: usize| vp[j];
planes[0] = [t(3) + t(0), t(7) + t(4), t(11) + t(8), t(15) + t(12)];
planes[1] = [t(3) - t(0), t(7) - t(4), t(11) - t(8), t(15) - t(12)];
planes[2] = [t(3) + t(1), t(7) + t(5), t(11) + t(9), t(15) + t(13)];
planes[3] = [t(3) - t(1), t(7) - t(5), t(11) - t(9), t(15) - t(13)];
planes[4] = [t(2), t(6), t(10), t(14)];
planes[5] = [t(3) - t(2), t(7) - t(6), t(11) - t(10), t(15) - t(14)];
for p in planes.iter_mut() {
let len = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
if len > EPSILON {
let inv_len = 1.0 / len;
p[0] *= inv_len;
p[1] *= inv_len;
p[2] *= inv_len;
p[3] *= inv_len;
}
}
Frustum { planes }
}
fn plane_distance(&self, plane: &[f32; 4], point: &[f32; 3]) -> f32 {
plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] + plane[3]
}
}
pub struct Quaternion {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
impl Quaternion {
pub fn identity() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
}
}
pub fn from_axis_angle(axis: [f32; 3], angle: f32) -> Self {
let half = angle * 0.5;
let s = half.sin();
Self {
x: axis[0] * s,
y: axis[1] * s,
z: axis[2] * s,
w: half.cos(),
}
}
pub fn from_euler(yaw: f32, pitch: f32, roll: f32) -> Self {
let cy = (yaw * 0.5).cos();
let sy = (yaw * 0.5).sin();
let cp = (pitch * 0.5).cos();
let sp = (pitch * 0.5).sin();
let cr = (roll * 0.5).cos();
let sr = (roll * 0.5).sin();
Self {
x: sr * cp * cy - cr * sp * sy,
y: cr * sp * cy + sr * cp * sy,
z: cr * cp * sy - sr * sp * cy,
w: cr * cp * cy + sr * sp * sy,
}
}
pub fn mul(&self, other: &Quaternion) -> Quaternion {
Quaternion {
x: self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y,
y: self.w * other.y - self.x * other.z + self.y * other.w + self.z * other.x,
z: self.w * other.z + self.x * other.y - self.y * other.x + self.z * other.w,
w: self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z,
}
}
pub fn conjugate(&self) -> Quaternion {
Quaternion {
x: -self.x,
y: -self.y,
z: -self.z,
w: self.w,
}
}
pub fn norm(&self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
}
pub fn magnitude(&self) -> f32 {
self.norm().sqrt()
}
pub fn normalize(&self) -> Quaternion {
let mag = self.magnitude();
if mag > EPSILON {
let inv = 1.0 / mag;
Quaternion {
x: self.x * inv,
y: self.y * inv,
z: self.z * inv,
w: self.w * inv,
}
} else {
*self
}
}
pub fn inverse(&self) -> Quaternion {
let norm = self.norm();
if norm > EPSILON {
let conj = self.conjugate();
let inv_norm = 1.0 / norm;
Quaternion {
x: conj.x * inv_norm,
y: conj.y * inv_norm,
z: conj.z * inv_norm,
w: conj.w * inv_norm,
}
} else {
*self
}
}
pub fn to_mat4(&self) -> [f32; 16] {
let x2 = self.x + self.x;
let y2 = self.y + self.y;
let z2 = self.z + self.z;
let xx = self.x * x2;
let xy = self.x * y2;
let xz = self.x * z2;
let yy = self.y * y2;
let yz = self.y * z2;
let zz = self.z * z2;
let wx = self.w * x2;
let wy = self.w * y2;
let wz = self.w * z2;
[
1.0 - (yy + zz),
xy + wz,
xz - wy,
0.0,
xy - wz,
1.0 - (xx + zz),
yz + wx,
0.0,
xz + wy,
yz - wx,
1.0 - (xx + yy),
0.0,
0.0,
0.0,
0.0,
1.0,
]
}
pub fn slerp(a: &Quaternion, b: &Quaternion, t: f32) -> Quaternion {
let mut cos_half_theta = a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
let mut b = *b;
if cos_half_theta < 0.0 {
b = Quaternion {
x: -b.x,
y: -b.y,
z: -b.z,
w: -b.w,
};
cos_half_theta = -cos_half_theta;
}
if cos_half_theta >= 1.0 - EPSILON {
return a.nlerp(&b, t);
}
let half_theta = cos_half_theta.acos();
let sin_half_theta = (1.0 - cos_half_theta * cos_half_theta).sqrt();
let ratio_a = ((1.0 - t) * half_theta).sin() / sin_half_theta;
let ratio_b = (t * half_theta).sin() / sin_half_theta;
Quaternion {
x: a.x * ratio_a + b.x * ratio_b,
y: a.y * ratio_a + b.y * ratio_b,
z: a.z * ratio_a + b.z * ratio_b,
w: a.w * ratio_a + b.w * ratio_b,
}
}
pub fn nlerp(&self, other: &Quaternion, t: f32) -> Quaternion {
let q = Quaternion {
x: self.x + (other.x - self.x) * t,
y: self.y + (other.y - self.y) * t,
z: self.z + (other.z - self.z) * t,
w: self.w + (other.w - self.w) * t,
};
q.normalize()
}
pub fn rotate_vector(&self, v: &[f32; 3]) -> [f32; 3] {
let qv = Quaternion {
x: v[0],
y: v[1],
z: v[2],
w: 0.0,
};
let result = self.mul(&qv).mul(&self.conjugate());
[result.x, result.y, result.z]
}
}
pub struct TTFTableRecord {
pub tag: [u8; 4],
pub checksum: u32,
pub offset: u32,
pub length: u32,
}
pub struct HeadTable {
pub major_version: u16,
pub minor_version: u16,
pub font_revision: u32,
pub units_per_em: u16,
pub created: i64,
pub modified: i64,
pub x_min: i16,
pub y_min: i16,
pub x_max: i16,
pub y_max: i16,
pub mac_style: u16,
pub lowest_rec_ppem: u16,
pub font_direction: i16,
pub index_to_loc_format: i16,
pub glyph_data_format: i16,
}
pub struct HheaTable {
pub ascent: i16,
pub descent: i16,
pub line_gap: i16,
pub advance_width_max: u16,
pub min_left_side_bearing: i16,
pub min_right_side_bearing: i16,
pub x_max_extent: i16,
pub caret_slope_rise: i16,
pub caret_slope_run: i16,
pub caret_offset: i16,
pub num_hmetrics: u16,
}
pub struct HorizontalMetric {
pub advance_width: u16,
pub left_side_bearing: i16,
}
pub struct CmapSubtable {
pub platform_id: u16,
pub encoding_id: u16,
pub format: u16,
pub format4_segments: Vec<(u16, u16, i16, u16)>,
pub format12_groups: Vec<(u32, u32, u32)>,
}
pub struct GlyphPoint {
pub x: i16,
pub y: i16,
pub on_curve: bool,
}
pub struct GlyphOutline {
pub contours: Vec<Vec<GlyphPoint>>,
pub x_min: i16,
pub y_min: i16,
pub x_max: i16,
pub y_max: i16,
pub advance_width: u16,
pub left_side_bearing: i16,
}
pub struct ShapedGlyph {
pub glyph_id: u16,
pub cluster: u32,
pub x_offset: f32,
pub y_offset: f32,
pub x_advance: f32,
pub y_advance: f32,
}
pub mod spirv_addressing_model {
pub const LOGICAL: u32 = 0;
pub const PHYSICAL32: u32 = 1;
pub const PHYSICAL64: u32 = 2;
pub const PHYSICAL_STORAGE_BUFFER64: u32 = 5348;
}
pub mod spirv_memory_model {
pub const SIMPLE: u32 = 0;
pub const GLSL450: u32 = 1;
pub const OPENCL: u32 = 2;
pub const VULKAN: u32 = 3;
}
pub mod spirv_execution_model {
pub const VERTEX: u32 = 0;
pub const TESSELLATION_CONTROL: u32 = 1;
pub const TESSELLATION_EVALUATION: u32 = 2;
pub const GEOMETRY: u32 = 3;
pub const FRAGMENT: u32 = 4;
pub const GL_COMPUTE: u32 = 5;
pub const KERNEL: u32 = 6;
pub const TASK_NV: u32 = 5267;
pub const MESH_NV: u32 = 5268;
pub const RAY_GENERATION: u32 = 5313;
pub const INTERSECTION: u32 = 5314;
pub const ANY_HIT: u32 = 5315;
pub const CLOSEST_HIT: u32 = 5316;
pub const MISS: u32 = 5317;
pub const CALLABLE: u32 = 5318;
}
pub mod spirv_storage_class {
pub const UNIFORM_CONSTANT: u32 = 0;
pub const INPUT: u32 = 1;
pub const UNIFORM: u32 = 2;
pub const OUTPUT: u32 = 3;
pub const WORKGROUP: u32 = 4;
pub const CROSS_WORKGROUP: u32 = 5;
pub const PRIVATE: u32 = 6;
pub const FUNCTION: u32 = 7;
pub const GENERIC: u32 = 8;
pub const PUSH_CONSTANT: u32 = 9;
pub const ATOMIC_COUNTER: u32 = 10;
pub const IMAGE: u32 = 11;
pub const STORAGE_BUFFER: u32 = 12;
}
pub mod spirv_capability {
pub const MATRIX: u32 = 0;
pub const SHADER: u32 = 1;
pub const GEOMETRY: u32 = 2;
pub const TESSELLATION: u32 = 3;
pub const ADDRESSES: u32 = 4;
pub const LINKAGE: u32 = 5;
pub const KERNEL: u32 = 6;
pub const VECTOR16: u32 = 7;
pub const FLOAT16_BUFFER: u32 = 8;
pub const FLOAT16: u32 = 9;
pub const FLOAT64: u32 = 10;
pub const INT64: u32 = 11;
pub const INT64_ATOMICS: u32 = 12;
pub const IMAGE_BASIC: u32 = 13;
pub const INT8: u32 = 39;
pub const INT16: u32 = 40;
pub const STORAGE_BUFFER_16BIT_ACCESS: u32 = 4433;
pub const VULKAN_MEMORY_MODEL: u32 = 5345;
}
pub mod spirv_builtin {
pub const POSITION: u32 = 0;
pub const POINT_SIZE: u32 = 1;
pub const CLIP_DISTANCE: u32 = 3;
pub const CULL_DISTANCE: u32 = 4;
pub const VERTEX_ID: u32 = 5;
pub const INSTANCE_ID: u32 = 6;
pub const PRIMITIVE_ID: u32 = 7;
pub const INVOCATION_ID: u32 = 8;
pub const LAYER: u32 = 9;
pub const VIEWPORT_INDEX: u32 = 10;
pub const TESS_LEVEL_OUTER: u32 = 11;
pub const TESS_LEVEL_INNER: u32 = 12;
pub const TESS_COORD: u32 = 13;
pub const PATCH_VERTICES: u32 = 14;
pub const FRAG_COORD: u32 = 15;
pub const POINT_COORD: u32 = 16;
pub const FRONT_FACING: u32 = 17;
pub const SAMPLE_ID: u32 = 18;
pub const SAMPLE_POSITION: u32 = 19;
pub const SAMPLE_MASK: u32 = 20;
pub const FRAG_DEPTH: u32 = 22;
pub const HELPER_INVOCATION: u32 = 23;
pub const NUM_WORKGROUPS: u32 = 24;
pub const WORKGROUP_SIZE: u32 = 25;
pub const WORKGROUP_ID: u32 = 26;
pub const LOCAL_INVOCATION_ID: u32 = 27;
pub const GLOBAL_INVOCATION_ID: u32 = 28;
pub const LOCAL_INVOCATION_INDEX: u32 = 29;
pub const WORK_DIM: u32 = 30;
pub const GLOBAL_SIZE: u32 = 31;
pub const SUBGROUP_SIZE: u32 = 36;
pub const SUBGROUP_LOCAL_INVOCATION_ID: u32 = 38;
pub const VERTEX_INDEX: u32 = 42;
pub const INSTANCE_INDEX: u32 = 43;
}
pub enum GPUMemoryScope {
Subgroup = 0,
Workgroup = 1,
Device = 2,
System = 3,
Invocation = 4,
CrossWorkgroup = 5,
}
impl GPUMemoryScope {
pub fn spirv_scope(&self) -> u32 {
match self {
GPUMemoryScope::Subgroup => 3,
GPUMemoryScope::Workgroup => 2,
GPUMemoryScope::Device => 1,
GPUMemoryScope::System => 4,
GPUMemoryScope::Invocation => 0,
GPUMemoryScope::CrossWorkgroup => 1,
}
}
pub fn opencl_scope(&self) -> u32 {
match self {
GPUMemoryScope::Workgroup => 0x1,
GPUMemoryScope::Device => 0x2,
GPUMemoryScope::System => 0x4,
_ => 0x0,
}
}
pub fn cuda_scope(&self) -> u32 {
match self {
GPUMemoryScope::Subgroup => 0, GPUMemoryScope::Workgroup => 1, GPUMemoryScope::Device => 2, GPUMemoryScope::System => 3, _ => 0,
}
}
}
pub struct GPUMemorySemantics {
pub acquire: bool,
pub release: bool,
pub sequentially_consistent: bool,
pub make_available: bool,
pub make_visible: bool,
}
impl GPUMemorySemantics {
pub fn relaxed() -> Self {
Self {
acquire: false,
release: false,
sequentially_consistent: false,
make_available: false,
make_visible: false,
}
}
pub fn acq_rel() -> Self {
Self {
acquire: true,
release: true,
sequentially_consistent: false,
make_available: false,
make_visible: false,
}
}
pub fn seq_cst() -> Self {
Self {
acquire: false,
release: false,
sequentially_consistent: true,
make_available: false,
make_visible: false,
}
}
pub fn spirv_semantics(&self) -> u32 {
let mut mask = 0u32;
if self.acquire {
mask |= 0x2; }
if self.release {
mask |= 0x4; }
if self.sequentially_consistent {
mask |= 0x10; }
if self.make_available {
mask |= 0x2000; }
if self.make_visible {
mask |= 0x4000; }
mask
}
}
pub struct BMPFileHeader {
pub signature: [u8; 2], pub file_size: u32,
pub reserved1: u16,
pub reserved2: u16,
pub pixel_offset: u32,
}
pub struct BMPDIBHeader {
pub header_size: u32,
pub width: i32,
pub height: i32, pub planes: u16,
pub bit_count: u16,
pub compression: u32,
pub image_size: u32,
pub x_pixels_per_meter: i32,
pub y_pixels_per_meter: i32,
pub colors_used: u32,
pub colors_important: u32,
}
pub mod png_chunk_type {
pub const IHDR: [u8; 4] = [b'I', b'H', b'D', b'R'];
pub const PLTE: [u8; 4] = [b'P', b'L', b'T', b'E'];
pub const IDAT: [u8; 4] = [b'I', b'D', b'A', b'T'];
pub const IEND: [u8; 4] = [b'I', b'E', b'N', b'D'];
pub const TRNS: [u8; 4] = [b't', b'R', b'N', b'S'];
pub const GAMA: [u8; 4] = [b'g', b'A', b'M', b'A'];
pub const CHRM: [u8; 4] = [b'c', b'H', b'R', b'M'];
pub const SRGB: [u8; 4] = [b's', b'R', b'G', b'B'];
pub const ICCP: [u8; 4] = [b'i', b'C', b'C', b'P'];
pub const TEXT: [u8; 4] = [b't', b'E', b'X', b't'];
pub const ZTXT: [u8; 4] = [b'z', b'T', b'X', b't'];
pub const ITXT: [u8; 4] = [b'i', b'T', b'X', b't'];
pub const TIME: [u8; 4] = [b't', b'I', b'M', b'E'];
pub const BKGD: [u8; 4] = [b'b', b'K', b'G', b'D'];
pub const PHYS: [u8; 4] = [b'p', b'H', b'Y', b's'];
}
pub struct PNGHeader {
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub color_type: u8,
pub compression: u8,
pub filter: u8,
pub interlace: u8,
}
pub struct PNGChunk {
pub chunk_type: [u8; 4],
pub data_offset: usize,
pub data_length: u32,
}
pub struct PNGChunkIter<'a> {
data: &'a [u8],
pos: usize,
}
pub mod jpeg_marker {
pub const SOI: u8 = 0xD8; pub const SOF0: u8 = 0xC0; pub const SOF1: u8 = 0xC1; pub const SOF2: u8 = 0xC2; pub const DHT: u8 = 0xC4; pub const DQT: u8 = 0xDB; pub const DRI: u8 = 0xDD; pub const SOS: u8 = 0xDA; pub const RST0: u8 = 0xD0; pub const RST7: u8 = 0xD7; pub const APP0: u8 = 0xE0; pub const APP1: u8 = 0xE1; pub const COM: u8 = 0xFE; pub const EOI: u8 = 0xD9; }
pub struct JPEGMarker {
pub marker: u8,
pub offset: usize,
pub length: usize,
}
pub struct JPEGHuffmanTable {
pub table_class: u8, pub table_id: u8,
pub counts: [u8; 16],
pub symbols: Vec<u8>,
}
pub struct JPEGQuantTable {
pub table_id: u8,
pub precision: u8, pub values: [u8; 64], }
pub struct TIFFIFDEntry {
pub tag: u16,
pub field_type: u16,
pub count: u32,
pub value_offset: u32,
}
pub mod tiff_tag {
pub const IMAGE_WIDTH: u16 = 256;
pub const IMAGE_LENGTH: u16 = 257;
pub const BITS_PER_SAMPLE: u16 = 258;
pub const COMPRESSION: u16 = 259;
pub const PHOTOMETRIC: u16 = 262;
pub const STRIP_OFFSETS: u16 = 273;
pub const SAMPLES_PER_PIXEL: u16 = 277;
pub const ROWS_PER_STRIP: u16 = 278;
pub const STRIP_BYTE_COUNTS: u16 = 279;
pub const X_RESOLUTION: u16 = 282;
pub const Y_RESOLUTION: u16 = 283;
pub const PLANAR_CONFIG: u16 = 284;
pub const RESOLUTION_UNIT: u16 = 296;
pub const SOFTWARE: u16 = 305;
pub const DATE_TIME: u16 = 306;
pub const ARTIST: u16 = 315;
pub const COLOR_MAP: u16 = 320;
pub const EXTRA_SAMPLES: u16 = 338;
pub const SAMPLE_FORMAT: u16 = 339;
pub const JPEG_TABLES: u16 = 347;
pub const PREDICTOR: u16 = 317;
pub const TILE_WIDTH: u16 = 322;
pub const TILE_LENGTH: u16 = 323;
pub const TILE_OFFSETS: u16 = 324;
pub const TILE_BYTE_COUNTS: u16 = 325;
}
pub struct GIFImageDescriptor {
pub left: u16,
pub top: u16,
pub width: u16,
pub height: u16,
pub local_color_table: bool,
pub interlace: bool,
pub sort: bool,
pub local_color_table_size: u8,
}
pub struct GIFScreenDescriptor {
pub width: u16,
pub height: u16,
pub global_color_table: bool,
pub color_resolution: u8,
pub sort: bool,
pub global_color_table_size: u8,
pub background_color_index: u8,
pub pixel_aspect_ratio: u8,
}
pub struct QOIHeader {
pub width: u32,
pub height: u32,
pub channels: u8, pub colorspace: u8, }
struct QOIPixel {
r: u8,
g: u8,
b: u8,
a: u8,
}
pub enum ImageFormat {
BMP,
PNG,
JPEG,
TIFF,
GIF,
WebP,
QOI,
Unknown,
}
pub struct X86Graphics {
pub simd_level: X86SIMDGraphicsLevel,
pub has_fma: bool,
pub has_avx512: bool,
pub has_avx2: bool,
pub has_sse42: bool,
pub simd_graphics: X86SIMDGraphics,
pub shader_intrinsics: X86ShaderIntrinsics,
pub geometry_ops: X86GeometryOps,
pub color_space_ops: X86ColorSpaceOps,
pub font_rendering: X86FontRendering,
pub gpu_support: X86GPUSupport,
pub image_codecs: X86ImageCodecs,
}
impl X86Graphics {
pub fn new() -> Self {
let level = X86SIMDGraphicsLevel::detect();
let has_fma = level.has_fma();
let has_avx512 = matches!(level, X86SIMDGraphicsLevel::AVX512);
let has_avx2 = matches!(level, X86SIMDGraphicsLevel::AVX2) || has_avx512;
let has_sse42 = matches!(level, X86SIMDGraphicsLevel::SSE42) || has_avx2 || has_avx512;
let simd_graphics = X86SIMDGraphics::new(level);
let shader_intrinsics = X86ShaderIntrinsics::new(level);
let geometry_ops = X86GeometryOps::new();
let color_space_ops = X86ColorSpaceOps::new();
let font_rendering = X86FontRendering::new();
let gpu_support = X86GPUSupport::new();
let image_codecs = X86ImageCodecs::new();
Self {
simd_level: level,
has_fma,
has_avx512,
has_avx2,
has_sse42,
simd_graphics,
shader_intrinsics,
geometry_ops,
color_space_ops,
font_rendering,
gpu_support,
image_codecs,
}
}
pub fn with_simd_level(level: X86SIMDGraphicsLevel) -> Self {
let has_fma = level.has_fma();
let has_avx512 = matches!(level, X86SIMDGraphicsLevel::AVX512);
let has_avx2 = matches!(level, X86SIMDGraphicsLevel::AVX2) || has_avx512;
let has_sse42 = matches!(level, X86SIMDGraphicsLevel::SSE42) || has_avx2 || has_avx512;
Self {
simd_level: level,
has_fma,
has_avx512,
has_avx2,
has_sse42,
simd_graphics: X86SIMDGraphics::new(level),
shader_intrinsics: X86ShaderIntrinsics::new(level),
geometry_ops: X86GeometryOps::new(),
color_space_ops: X86ColorSpaceOps::new(),
font_rendering: X86FontRendering::new(),
gpu_support: X86GPUSupport::new(),
image_codecs: X86ImageCodecs::new(),
}
}
pub fn capabilities(&self) -> X86GraphicsCapabilities {
X86GraphicsCapabilities {
simd_level: self.simd_level,
has_fma: self.has_fma,
has_avx512: self.has_avx512,
has_avx2: self.has_avx2,
has_sse42: self.has_sse42,
vector_width_f32: self.simd_level.vector_width_f32(),
vector_width_f64: self.simd_level.vector_width_f64(),
max_image_dimension: MAX_IMAGE_DIMENSION,
supports_hdr: true,
supports_gpu_fallback: true,
}
}
}
impl Default for X86Graphics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86GraphicsCapabilities {
pub simd_level: X86SIMDGraphicsLevel,
pub has_fma: bool,
pub has_avx512: bool,
pub has_avx2: bool,
pub has_sse42: bool,
pub vector_width_f32: usize,
pub vector_width_f64: usize,
pub max_image_dimension: usize,
pub supports_hdr: bool,
pub supports_gpu_fallback: bool,
}
impl std::fmt::Display for X86GraphicsCapabilities {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"X86Graphics {{ simd: {:?}, fma: {}, avx512: {}, avx2: {}, sse42: {}, \
vec_f32: {}, vec_f64: {}, max_dim: {}, hdr: {}, gpu_fb: {} }}",
self.simd_level,
self.has_fma,
self.has_avx512,
self.has_avx2,
self.has_sse42,
self.vector_width_f32,
self.vector_width_f64,
self.max_image_dimension,
self.supports_hdr,
self.supports_gpu_fallback,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86SIMDGraphicsLevel {
Scalar = 0,
SSE2 = 1,
SSE42 = 2,
AVX2 = 3,
AVX512 = 4,
}
impl X86SIMDGraphicsLevel {
pub fn detect() -> Self {
#[cfg(target_feature = "avx512f")]
{
return X86SIMDGraphicsLevel::AVX512;
}
#[cfg(target_feature = "avx2")]
{
return X86SIMDGraphicsLevel::AVX2;
}
#[cfg(target_feature = "sse4.2")]
{
return X86SIMDGraphicsLevel::SSE42;
}
#[cfg(target_feature = "sse2")]
{
return X86SIMDGraphicsLevel::SSE2;
}
X86SIMDGraphicsLevel::Scalar
}
pub fn has_fma(&self) -> bool {
match self {
X86SIMDGraphicsLevel::AVX512 | X86SIMDGraphicsLevel::AVX2 => true,
X86SIMDGraphicsLevel::SSE42 | X86SIMDGraphicsLevel::SSE2 => {
#[cfg(target_feature = "fma")]
{
true
}
#[cfg(not(target_feature = "fma"))]
{
false
}
}
X86SIMDGraphicsLevel::Scalar => false,
}
}
pub fn vector_width_f32(&self) -> usize {
match self {
X86SIMDGraphicsLevel::Scalar => 1,
X86SIMDGraphicsLevel::SSE2 | X86SIMDGraphicsLevel::SSE42 => SSE_LANES_F32,
X86SIMDGraphicsLevel::AVX2 => AVX_LANES_F32,
X86SIMDGraphicsLevel::AVX512 => AVX512_LANES_F32,
}
}
pub fn vector_width_f64(&self) -> usize {
match self {
X86SIMDGraphicsLevel::Scalar => 1,
X86SIMDGraphicsLevel::SSE2 | X86SIMDGraphicsLevel::SSE42 => SSE_LANES_F64,
X86SIMDGraphicsLevel::AVX2 => AVX_LANES_F64,
X86SIMDGraphicsLevel::AVX512 => AVX512_LANES_F64,
}
}
pub fn vector_bytes(&self) -> usize {
match self {
X86SIMDGraphicsLevel::Scalar => 4,
X86SIMDGraphicsLevel::SSE2 | X86SIMDGraphicsLevel::SSE42 => SIMD_SSE_BYTES,
X86SIMDGraphicsLevel::AVX2 => SIMD_AVX_BYTES,
X86SIMDGraphicsLevel::AVX512 => SIMD_AVX512_BYTES,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PixelFormat {
R8,
R8G8,
R8G8B8,
R8G8B8A8,
B8G8R8A8,
A8R8G8B8,
R16F,
R16G16B16A16F,
R32F,
R32G32B32A32F,
RGBE8,
R10G10B10A2,
R11G11B10F,
}
impl PixelFormat {
pub fn bytes_per_pixel(&self) -> usize {
match self {
PixelFormat::R8 => 1,
PixelFormat::R8G8 => 2,
PixelFormat::R8G8B8 => 3,
PixelFormat::R8G8B8A8 | PixelFormat::B8G8R8A8 | PixelFormat::A8R8G8B8 => 4,
PixelFormat::R16F => 2,
PixelFormat::R16G16B16A16F => 8,
PixelFormat::R32F => 4,
PixelFormat::R32G32B32A32F => 16,
PixelFormat::RGBE8 => 4,
PixelFormat::R10G10B10A2 => 4,
PixelFormat::R11G11B10F => 4,
}
}
pub fn channels(&self) -> usize {
match self {
PixelFormat::R8 | PixelFormat::R16F | PixelFormat::R32F => 1,
PixelFormat::R8G8 => 2,
PixelFormat::R8G8B8 | PixelFormat::R11G11B10F => 3,
PixelFormat::R8G8B8A8
| PixelFormat::B8G8R8A8
| PixelFormat::A8R8G8B8
| PixelFormat::R16G16B16A16F
| PixelFormat::R32G32B32A32F
| PixelFormat::RGBE8
| PixelFormat::R10G10B10A2 => 4,
}
}
pub fn is_float(&self) -> bool {
matches!(
self,
PixelFormat::R16F
| PixelFormat::R16G16B16A16F
| PixelFormat::R32F
| PixelFormat::R32G32B32A32F
| PixelFormat::R11G11B10F
)
}
pub fn has_alpha(&self) -> bool {
matches!(
self,
PixelFormat::R8G8A8(_)
| PixelFormat::B8G8R8A8
| PixelFormat::A8R8G8B8
| PixelFormat::R16G16B16A16F
| PixelFormat::R32G32B32A32F
| PixelFormat::R10G10B10A2
| _ if matches!(self, PixelFormat::R8G8B8A8)
) || self.channels() == 4 && !matches!(self, PixelFormat::RGBE8)
}
pub fn name(&self) -> &'static str {
match self {
PixelFormat::R8 => "R8_UNORM",
PixelFormat::R8G8 => "R8G8_UNORM",
PixelFormat::R8G8B8 => "R8G8B8_UNORM",
PixelFormat::R8G8B8A8 => "R8G8B8A8_UNORM",
PixelFormat::B8G8R8A8 => "B8G8R8A8_UNORM",
PixelFormat::A8R8G8B8 => "A8R8G8B8_UNORM",
PixelFormat::R16F => "R16_FLOAT",
PixelFormat::R16G16B16A16F => "R16G16B16A16_FLOAT",
PixelFormat::R32F => "R32_FLOAT",
PixelFormat::R32G32B32A32F => "R32G32B32A32_FLOAT",
PixelFormat::RGBE8 => "RGBE8",
PixelFormat::R10G10B10A2 => "R10G10B10A2_UNORM",
PixelFormat::R11G11B10F => "R11G11B10_FLOAT",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlendMode {
Over,
Add,
Multiply,
Screen,
Overlay,
Difference,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Exclusion,
Subtract,
Divide,
LinearBurn,
LinearDodge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DitherKind {
FloydSteinberg,
Ordered,
BlueNoise,
Atkinson,
JarvisJudiceNinke,
Stucki,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeFilter {
Nearest,
Bilinear,
Bicubic,
Lanczos3,
Mitchell,
Box,
}
#[derive(Debug, Clone, Copy)]
pub struct X86SIMDGraphics {
pub level: X86SIMDGraphicsLevel,
pub lanes_f32: usize,
}
impl X86SIMDGraphics {
pub fn new(level: X86SIMDGraphicsLevel) -> Self {
Self {
level,
lanes_f32: level.vector_width_f32(),
}
}
pub fn pack_rgba_f32_to_u32(&self, r: f32, g: f32, b: f32, a: f32) -> u32 {
let ri = (r.clamp(0.0, 1.0) * 255.0 + 0.5) as u32;
let gi = (g.clamp(0.0, 1.0) * 255.0 + 0.5) as u32;
let bi = (b.clamp(0.0, 1.0) * 255.0 + 0.5) as u32;
let ai = (a.clamp(0.0, 1.0) * 255.0 + 0.5) as u32;
ri | (gi << 8) | (bi << 16) | (ai << 24)
}
pub fn unpack_rgba_u32_to_f32(&self, packed: u32) -> (f32, f32, f32, f32) {
let r = ((packed & 0xFF) as f32) / 255.0;
let g = (((packed >> 8) & 0xFF) as f32) / 255.0;
let b = (((packed >> 16) & 0xFF) as f32) / 255.0;
let a = (((packed >> 24) & 0xFF) as f32) / 255.0;
(r, g, b, a)
}
pub fn premultiply_alpha(&self, pixels: &mut [u8], format: PixelFormat) {
assert_eq!(format, PixelFormat::R8G8B8A8);
for chunk in pixels.chunks_exact_mut(4) {
let a = chunk[3] as f32 / 255.0;
chunk[0] = ((chunk[0] as f32) * a + 0.5) as u8;
chunk[1] = ((chunk[1] as f32) * a + 0.5) as u8;
chunk[2] = ((chunk[2] as f32) * a + 0.5) as u8;
}
}
pub fn unpremultiply_alpha(&self, pixels: &mut [u8], format: PixelFormat) {
assert_eq!(format, PixelFormat::R8G8B8A8);
for chunk in pixels.chunks_exact_mut(4) {
let a = chunk[3] as f32 / 255.0;
if a > 0.0 {
chunk[0] = ((chunk[0] as f32) / a).clamp(0.0, 255.0) as u8;
chunk[1] = ((chunk[1] as f32) / a).clamp(0.0, 255.0) as u8;
chunk[2] = ((chunk[2] as f32) / a).clamp(0.0, 255.0) as u8;
} else {
chunk[0] = 0;
chunk[1] = 0;
chunk[2] = 0;
}
}
}
pub fn scale_rgba(&self, pixels: &mut [u8], factor: f32) {
for chunk in pixels.chunks_exact_mut(4) {
chunk[0] = ((chunk[0] as f32 * factor).clamp(0.0, 255.0)) as u8;
chunk[1] = ((chunk[1] as f32 * factor).clamp(0.0, 255.0)) as u8;
chunk[2] = ((chunk[2] as f32 * factor).clamp(0.0, 255.0)) as u8;
}
}
pub fn extract_alpha(&self, rgba: &[u8], alpha: &mut [u8]) {
for (i, chunk) in rgba.chunks(4).enumerate() {
if i < alpha.len() {
alpha[i] = chunk[3];
}
}
}
pub fn set_alpha(&self, rgba: &mut [u8], value: u8) {
for chunk in rgba.chunks_exact_mut(4) {
chunk[3] = value;
}
}
pub fn rgba8_to_bgra8(&self, pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(4) {
chunk.swap(0, 2);
}
}
pub fn bgra8_to_rgba8(&self, pixels: &mut [u8]) {
self.rgba8_to_bgra8(pixels); }
pub fn rgb8_to_bgr8(&self, pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(3) {
chunk.swap(0, 2);
}
}
pub fn rgb8_to_rgba8(&self, src: &[u8], dst: &mut [u8]) {
assert_eq!(src.len() / 3, dst.len() / 4);
for (i, chunk) in src.chunks(3).enumerate() {
let o = i * 4;
dst[o] = chunk[0];
dst[o + 1] = chunk[1];
dst[o + 2] = chunk[2];
dst[o + 3] = 255;
}
}
pub fn rgba8_to_rgb8(&self, src: &[u8], dst: &mut [u8]) {
assert_eq!(src.len() / 4, dst.len() / 3);
for (i, chunk) in src.chunks(4).enumerate() {
let o = i * 3;
dst[o] = chunk[0];
dst[o + 1] = chunk[1];
dst[o + 2] = chunk[2];
}
}
pub fn float_rgba_to_u8_rgba(&self, src: &[f32], dst: &mut [u8]) {
assert_eq!(src.len() / 4, dst.len() / 4);
for i in 0..src.len() / 4 {
let o = i * 4;
dst[o] = (src[o].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
dst[o + 1] = (src[o + 1].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
dst[o + 2] = (src[o + 2].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
dst[o + 3] = (src[o + 3].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
}
}
pub fn u8_rgba_to_float_rgba(&self, src: &[u8], dst: &mut [f32]) {
assert_eq!(src.len() / 4, dst.len() / 4);
for i in 0..src.len() / 4 {
let o = i * 4;
dst[o] = src[o] as f32 / 255.0;
dst[o + 1] = src[o + 1] as f32 / 255.0;
dst[o + 2] = src[o + 2] as f32 / 255.0;
dst[o + 3] = src[o + 3] as f32 / 255.0;
}
}
pub fn resize_rgba8(
&self,
src: &[u8],
src_w: usize,
src_h: usize,
dst: &mut [u8],
dst_w: usize,
dst_h: usize,
filter: ResizeFilter,
) {
assert_eq!(src.len(), src_w * src_h * 4);
assert_eq!(dst.len(), dst_w * dst_h * 4);
match filter {
ResizeFilter::Nearest => self.resize_nearest(src, src_w, src_h, dst, dst_w, dst_h),
ResizeFilter::Bilinear => self.resize_bilinear(src, src_w, src_h, dst, dst_w, dst_h),
ResizeFilter::Bicubic => self.resize_bicubic(src, src_w, src_h, dst, dst_w, dst_h),
_ => self.resize_bilinear(src, src_w, src_h, dst, dst_w, dst_h),
}
}
fn resize_nearest(
&self,
src: &[u8],
src_w: usize,
src_h: usize,
dst: &mut [u8],
dst_w: usize,
dst_h: usize,
) {
let x_ratio = src_w as f64 / dst_w as f64;
let y_ratio = src_h as f64 / dst_h as f64;
for dy in 0..dst_h {
let sy = ((dy as f64) * y_ratio) as usize;
for dx in 0..dst_w {
let sx = ((dx as f64) * x_ratio) as usize;
let si = (sy * src_w + sx) * 4;
let di = (dy * dst_w + dx) * 4;
dst[di] = src[si];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si + 2];
dst[di + 3] = src[si + 3];
}
}
}
fn resize_bilinear(
&self,
src: &[u8],
src_w: usize,
src_h: usize,
dst: &mut [u8],
dst_w: usize,
dst_h: usize,
) {
let x_ratio = if dst_w > 1 {
(src_w - 1) as f64 / (dst_w - 1) as f64
} else {
0.0
};
let y_ratio = if dst_h > 1 {
(src_h - 1) as f64 / (dst_h - 1) as f64
} else {
0.0
};
for dy in 0..dst_h {
let sy = dy as f64 * y_ratio;
let sy_int = sy as usize;
let sy_frac = sy - sy_int as f64;
let sy_next = (sy_int + 1).min(src_h - 1);
for dx in 0..dst_w {
let sx = dx as f64 * x_ratio;
let sx_int = sx as usize;
let sx_frac = sx - sx_int as f64;
let sx_next = (sx_int + 1).min(src_w - 1);
let i00 = (sy_int * src_w + sx_int) * 4;
let i01 = (sy_int * src_w + sx_next) * 4;
let i10 = (sy_next * src_w + sx_int) * 4;
let i11 = (sy_next * src_w + sx_next) * 4;
let di = (dy * dst_w + dx) * 4;
for c in 0..4 {
let v00 = src[i00 + c] as f64;
let v01 = src[i01 + c] as f64;
let v10 = src[i10 + c] as f64;
let v11 = src[i11 + c] as f64;
let top = v00 * (1.0 - sx_frac) + v01 * sx_frac;
let bot = v10 * (1.0 - sx_frac) + v11 * sx_frac;
let val = top * (1.0 - sy_frac) + bot * sy_frac;
dst[di + c] = val.clamp(0.0, 255.0) as u8;
}
}
}
}
fn resize_bicubic(
&self,
src: &[u8],
src_w: usize,
src_h: usize,
dst: &mut [u8],
dst_w: usize,
dst_h: usize,
) {
let x_ratio = if dst_w > 1 {
(src_w - 1) as f64 / (dst_w - 1) as f64
} else {
0.0
};
let y_ratio = if dst_h > 1 {
(src_h - 1) as f64 / (dst_h - 1) as f64
} else {
0.0
};
for dy in 0..dst_h {
let sy = dy as f64 * y_ratio;
let sy_int = sy as isize;
for dx in 0..dst_w {
let sx = dx as f64 * x_ratio;
let sx_int = sx as isize;
let di = (dy * dst_w + dx) * 4;
for c in 0..4 {
let mut sum = 0.0;
let mut weight_sum = 0.0;
for m in -1..=2 {
let sy_idx = (sy_int + m).clamp(0, src_h as isize - 1) as usize;
let wy = Self::cubic_kernel(sy - (sy_int + m) as f64);
for n in -1..=2 {
let sx_idx = (sx_int + n).clamp(0, src_w as isize - 1) as usize;
let wx = Self::cubic_kernel(sx - (sx_int + n) as f64);
let si = (sy_idx * src_w + sx_idx) * 4 + c;
sum += src[si] as f64 * wx * wy;
weight_sum += wx * wy;
}
}
if weight_sum > 0.0 {
sum /= weight_sum;
}
dst[di + c] = sum.clamp(0.0, 255.0) as u8;
}
}
}
}
fn cubic_kernel(x: f64) -> f64 {
let ax = x.abs();
if ax <= 1.0 {
(1.5 * ax - 2.5) * ax * ax + 1.0
} else if ax < 2.0 {
((-0.5 * ax + 2.5) * ax - 4.0) * ax + 2.0
} else {
0.0
}
}
pub fn rotate_rgba8(
&self,
src: &[u8],
src_w: usize,
src_h: usize,
dst: &mut [u8],
degrees: u32,
) {
match degrees % 360 {
0 => {
dst.copy_from_slice(src);
}
90 => {
let dst_w = src_h;
let dst_h = src_w;
assert_eq!(dst.len(), dst_w * dst_h * 4);
for y in 0..src_h {
for x in 0..src_w {
let si = (y * src_w + x) * 4;
let dx = src_h - 1 - y;
let dy = x;
let di = (dy * dst_w + dx) * 4;
dst[di] = src[si];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si + 2];
dst[di + 3] = src[si + 3];
}
}
}
180 => {
for y in 0..src_h {
for x in 0..src_w {
let si = (y * src_w + x) * 4;
let dx = src_w - 1 - x;
let dy = src_h - 1 - y;
let di = (dy * src_w + dx) * 4;
dst[di] = src[si];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si + 2];
dst[di + 3] = src[si + 3];
}
}
}
270 => {
let dst_w = src_h;
let dst_h = src_w;
assert_eq!(dst.len(), dst_w * dst_h * 4);
for y in 0..src_h {
for x in 0..src_w {
let si = (y * src_w + x) * 4;
let dx = y;
let dy = src_w - 1 - x;
let di = (dy * dst_w + dx) * 4;
dst[di] = src[si];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si + 2];
dst[di + 3] = src[si + 3];
}
}
}
_ => panic!("Rotation angle must be 0, 90, 180, or 270 degrees"),
}
}
pub fn flip_horizontal(&self, pixels: &mut [u8], width: usize, height: usize) {
for y in 0..height {
let row_start = y * width * 4;
for x in 0..width / 2 {
let a = row_start + x * 4;
let b = row_start + (width - 1 - x) * 4;
for c in 0..4 {
pixels.swap(a + c, b + c);
}
}
}
}
pub fn flip_vertical(&self, pixels: &mut [u8], width: usize, height: usize) {
let row_bytes = width * 4;
for y in 0..height / 2 {
let top = y * row_bytes;
let bot = (height - 1 - y) * row_bytes;
for x in 0..row_bytes {
pixels.swap(top + x, bot + x);
}
}
}
pub fn crop_rgba8(
&self,
src: &[u8],
src_w: usize,
x: usize,
y: usize,
crop_w: usize,
crop_h: usize,
dst: &mut [u8],
) {
assert!(x + crop_w <= src_w);
assert_eq!(dst.len(), crop_w * crop_h * 4);
for row in 0..crop_h {
let src_start = ((y + row) * src_w + x) * 4;
let dst_start = row * crop_w * 4;
dst[dst_start..dst_start + crop_w * 4]
.copy_from_slice(&src[src_start..src_start + crop_w * 4]);
}
}
pub fn pad_rgba8(
&self,
src: &[u8],
src_w: usize,
src_h: usize,
pad_left: usize,
pad_top: usize,
pad_right: usize,
pad_bottom: usize,
fill: [u8; 4],
dst: &mut [u8],
) {
let dst_w = src_w + pad_left + pad_right;
let dst_h = src_h + pad_top + pad_bottom;
assert_eq!(dst.len(), dst_w * dst_h * 4);
for chunk in dst.chunks_exact_mut(4) {
chunk.copy_from_slice(&fill);
}
for row in 0..src_h {
let src_start = row * src_w * 4;
let dst_start = ((pad_top + row) * dst_w + pad_left) * 4;
dst[dst_start..dst_start + src_w * 4]
.copy_from_slice(&src[src_start..src_start + src_w * 4]);
}
}
pub fn box_blur_rgba8(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
radius: usize,
) {
let r = radius as isize;
for y in 0..height {
for x in 0..width {
let mut sum = [0u32; 4];
let mut count = 0u32;
for dy in -r..=r {
let ny = y as isize + dy;
if ny < 0 || ny >= height as isize {
continue;
}
for dx in -r..=r {
let nx = x as isize + dx;
if nx < 0 || nx >= width as isize {
continue;
}
let si = (ny as usize * width + nx as usize) * 4;
for c in 0..4 {
sum[c] += src[si + c] as u32;
}
count += 1;
}
}
let di = (y * width + x) * 4;
for c in 0..4 {
dst[di + c] = (sum[c] / count) as u8;
}
}
}
}
pub fn gaussian_blur_rgba8(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
sigma: f32,
) {
let radius = (sigma * 3.0).ceil() as usize;
let kernel = Self::gaussian_kernel(sigma, radius);
let mut temp = vec![0u8; src.len()];
for y in 0..height {
for x in 0..width {
let mut sum = [0.0f32; 4];
for (k, &w) in kernel.iter().enumerate() {
let kx = k as isize - radius as isize;
let sx = (x as isize + kx).clamp(0, width as isize - 1) as usize;
let si = (y * width + sx) * 4;
for c in 0..4 {
sum[c] += src[si + c] as f32 * w;
}
}
let di = (y * width + x) * 4;
for c in 0..4 {
temp[di + c] = sum[c].clamp(0.0, 255.0) as u8;
}
}
}
for y in 0..height {
for x in 0..width {
let mut sum = [0.0f32; 4];
for (k, &w) in kernel.iter().enumerate() {
let ky = k as isize - radius as isize;
let sy = (y as isize + ky).clamp(0, height as isize - 1) as usize;
let si = (sy * width + x) * 4;
for c in 0..4 {
sum[c] += temp[si + c] as f32 * w;
}
}
let di = (y * width + x) * 4;
for c in 0..4 {
dst[di + c] = sum[c].clamp(0.0, 255.0) as u8;
}
}
}
}
fn gaussian_kernel(sigma: f32, radius: usize) -> Vec<f32> {
let mut kernel = Vec::with_capacity(radius * 2 + 1);
let two_sigma2 = 2.0 * sigma * sigma;
for i in 0..=radius * 2 {
let x = i as f32 - radius as f32;
kernel.push((-x * x / two_sigma2).exp());
}
let sum: f32 = kernel.iter().sum();
for v in kernel.iter_mut() {
*v /= sum;
}
kernel
}
pub fn median_filter_rgba8(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
radius: usize,
) {
let r = radius as isize;
let window_size = (radius * 2 + 1) * (radius * 2 + 1);
let mut r_vals = vec![0u8; window_size];
let mut g_vals = vec![0u8; window_size];
let mut b_vals = vec![0u8; window_size];
let mut a_vals = vec![0u8; window_size];
for y in 0..height {
for x in 0..width {
let mut count = 0;
for dy in -r..=r {
let ny = y as isize + dy;
if ny < 0 || ny >= height as isize {
continue;
}
for dx in -r..=r {
let nx = x as isize + dx;
if nx < 0 || nx >= width as isize {
continue;
}
let si = (ny as usize * width + nx as usize) * 4;
r_vals[count] = src[si];
g_vals[count] = src[si + 1];
b_vals[count] = src[si + 2];
a_vals[count] = src[si + 3];
count += 1;
}
}
r_vals[..count].sort_unstable();
g_vals[..count].sort_unstable();
b_vals[..count].sort_unstable();
a_vals[..count].sort_unstable();
let mid = count / 2;
let di = (y * width + x) * 4;
dst[di] = r_vals[mid];
dst[di + 1] = g_vals[mid];
dst[di + 2] = b_vals[mid];
dst[di + 3] = a_vals[mid];
}
}
}
pub fn bilateral_filter_rgba8(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
spatial_sigma: f32,
range_sigma: f32,
) {
let radius = (spatial_sigma * 2.0).ceil() as isize;
let two_spatial2 = 2.0 * spatial_sigma * spatial_sigma;
let two_range2 = 2.0 * range_sigma * range_sigma * 255.0 * 255.0;
for y in 0..height {
for x in 0..width {
let si_center = (y * width + x) * 4;
let mut sum = [0.0f32; 4];
let mut norm = 0.0f32;
for dy in -radius..=radius {
let ny = y as isize + dy;
if ny < 0 || ny >= height as isize {
continue;
}
for dx in -radius..=radius {
let nx = x as isize + dx;
if nx < 0 || nx >= width as isize {
continue;
}
let dist2 = (dx * dx + dy * dy) as f32;
let spatial_w = (-dist2 / two_spatial2).exp();
let si = (ny as usize * width + nx as usize) * 4;
let range_dist2: f32 = (0..3)
.map(|c| {
let d = src[si + c] as f32 - src[si_center + c] as f32;
d * d
})
.sum();
let range_w = (-range_dist2 / two_range2).exp();
let w = spatial_w * range_w;
for c in 0..4 {
sum[c] += src[si + c] as f32 * w;
}
norm += w;
}
}
let di = (y * width + x) * 4;
if norm > 0.0 {
for c in 0..4 {
dst[di + c] = (sum[c] / norm).clamp(0.0, 255.0) as u8;
}
} else {
for c in 0..4 {
dst[di + c] = src[si_center + c];
}
}
}
}
}
pub fn sharpen_unsharp_mask(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
amount: f32,
radius: f32,
threshold: u8,
) {
let mut blurred = vec![0u8; src.len()];
self.gaussian_blur_rgba8(src, &mut blurred, width, height, radius);
for y in 0..height {
for x in 0..width {
let si = (y * width + x) * 4;
let di = (y * width + x) * 4;
for c in 0..3 {
let orig = src[si + c] as f32;
let blur = blurred[si + c] as f32;
let diff = orig - blur;
if diff.abs() >= threshold as f32 {
let val = orig + amount * diff;
dst[di + c] = val.clamp(0.0, 255.0) as u8;
} else {
dst[di + c] = src[si + c];
}
}
dst[di + 3] = src[si + 3]; }
}
}
pub fn sharpen_laplacian(&self, src: &[u8], dst: &mut [u8], width: usize, height: usize) {
let kernel: [[i32; 3]; 3] = [[0, -1, 0], [-1, 5, -1], [0, -1, 0]];
self.apply_3x3_kernel_i32(src, dst, width, height, &kernel);
}
fn apply_3x3_kernel_i32(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
kernel: &[[i32; 3]; 3],
) {
for y in 0..height {
for x in 0..width {
let mut sum = [0i32; 4];
for ky in 0..3 {
for kx in 0..3 {
let sy =
(y as isize + ky as isize - 1).clamp(0, height as isize - 1) as usize;
let sx =
(x as isize + kx as isize - 1).clamp(0, width as isize - 1) as usize;
let si = (sy * width + sx) * 4;
let kw = kernel[ky][kx];
for c in 0..4 {
sum[c] += src[si + c] as i32 * kw;
}
}
}
let di = (y * width + x) * 4;
for c in 0..4 {
dst[di + c] = sum[c].clamp(0, 255) as u8;
}
}
}
}
pub fn blend_pixel(&self, src: [u8; 4], dst: [u8; 4], mode: BlendMode) -> [u8; 4] {
let s = [
src[0] as f32 / 255.0,
src[1] as f32 / 255.0,
src[2] as f32 / 255.0,
src[3] as f32 / 255.0,
];
let d = [
dst[0] as f32 / 255.0,
dst[1] as f32 / 255.0,
dst[2] as f32 / 255.0,
dst[3] as f32 / 255.0,
];
let result = match mode {
BlendMode::Over => Self::blend_over(s, d),
BlendMode::Add => Self::blend_add(s, d),
BlendMode::Multiply => Self::blend_multiply(s, d),
BlendMode::Screen => Self::blend_screen(s, d),
BlendMode::Overlay => Self::blend_overlay(s, d),
BlendMode::Difference => Self::blend_difference(s, d),
BlendMode::Darken => Self::blend_darken(s, d),
BlendMode::Lighten => Self::blend_lighten(s, d),
BlendMode::ColorDodge => Self::blend_color_dodge(s, d),
BlendMode::ColorBurn => Self::blend_color_burn(s, d),
BlendMode::HardLight => Self::blend_hard_light(s, d),
BlendMode::SoftLight => Self::blend_soft_light(s, d),
BlendMode::Exclusion => Self::blend_exclusion(s, d),
BlendMode::Subtract => Self::blend_subtract(s, d),
BlendMode::Divide => Self::blend_divide(s, d),
BlendMode::LinearBurn => Self::blend_linear_burn(s, d),
BlendMode::LinearDodge => Self::blend_linear_dodge(s, d),
};
[
(result[0] * 255.0 + 0.5).clamp(0.0, 255.0) as u8,
(result[1] * 255.0 + 0.5).clamp(0.0, 255.0) as u8,
(result[2] * 255.0 + 0.5).clamp(0.0, 255.0) as u8,
(result[3] * 255.0 + 0.5).clamp(0.0, 255.0) as u8,
]
}
pub fn blend_images(
&self,
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
mode: BlendMode,
) {
for i in 0..width * height {
let si = i * 4;
let sp = [src[si], src[si + 1], src[si + 2], src[si + 3]];
let dp = [dst[si], dst[si + 1], dst[si + 2], dst[si + 3]];
let r = self.blend_pixel(sp, dp, mode);
dst[si] = r[0];
dst[si + 1] = r[1];
dst[si + 2] = r[2];
dst[si + 3] = r[3];
}
}
fn blend_over(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let a = s[3] + d[3] * (1.0 - s[3]);
if a == 0.0 {
return [0.0; 4];
}
[
(s[0] * s[3] + d[0] * d[3] * (1.0 - s[3])) / a,
(s[1] * s[3] + d[1] * d[3] * (1.0 - s[3])) / a,
(s[2] * s[3] + d[2] * d[3] * (1.0 - s[3])) / a,
a,
]
}
fn blend_add(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
(s[0] + d[0]).min(1.0),
(s[1] + d[1]).min(1.0),
(s[2] + d[2]).min(1.0),
(s[3] + d[3]).min(1.0),
]
}
fn blend_multiply(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
s[0] * d[0],
s[1] * d[1],
s[2] * d[2],
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_screen(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
1.0 - (1.0 - s[0]) * (1.0 - d[0]),
1.0 - (1.0 - s[1]) * (1.0 - d[1]),
1.0 - (1.0 - s[2]) * (1.0 - d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_overlay(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let overlay_ch = |s: f32, d: f32| {
if d < 0.5 {
2.0 * s * d
} else {
1.0 - 2.0 * (1.0 - s) * (1.0 - d)
}
};
[
overlay_ch(s[0], d[0]),
overlay_ch(s[1], d[1]),
overlay_ch(s[2], d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_difference(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
(d[0] - s[0]).abs(),
(d[1] - s[1]).abs(),
(d[2] - s[2]).abs(),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_darken(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
s[0].min(d[0]),
s[1].min(d[1]),
s[2].min(d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_lighten(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
s[0].max(d[0]),
s[1].max(d[1]),
s[2].max(d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_color_dodge(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let dodge = |s: f32, d: f32| {
if s >= 1.0 {
1.0
} else {
(d / (1.0 - s)).min(1.0)
}
};
[
dodge(s[0], d[0]),
dodge(s[1], d[1]),
dodge(s[2], d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_color_burn(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let burn = |s: f32, d: f32| {
if s <= 0.0 {
0.0
} else {
1.0 - ((1.0 - d) / s).min(1.0)
}
};
[
burn(s[0], d[0]),
burn(s[1], d[1]),
burn(s[2], d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_hard_light(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let hl = |s: f32, d: f32| {
if s <= 0.5 {
2.0 * s * d
} else {
1.0 - 2.0 * (1.0 - s) * (1.0 - d)
}
};
[
hl(s[0], d[0]),
hl(s[1], d[1]),
hl(s[2], d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_soft_light(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let sl = |s: f32, d: f32| {
if s <= 0.5 {
d - (1.0 - 2.0 * s) * d * (1.0 - d)
} else if d <= 0.25 {
d + (2.0 * s - 1.0) * ((((16.0 * d - 12.0) * d + 4.0) * d) - d)
} else {
d + (2.0 * s - 1.0) * (d.sqrt() - d)
}
};
[
sl(s[0], d[0]),
sl(s[1], d[1]),
sl(s[2], d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_exclusion(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
d[0] + s[0] - 2.0 * d[0] * s[0],
d[1] + s[1] - 2.0 * d[1] * s[1],
d[2] + s[2] - 2.0 * d[2] * s[2],
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_subtract(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
(d[0] - s[0]).max(0.0),
(d[1] - s[1]).max(0.0),
(d[2] - s[2]).max(0.0),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_divide(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
let div = |s: f32, d: f32| if s > 0.0 { (d / s).min(1.0) } else { 1.0 };
[
div(s[0], d[0]),
div(s[1], d[1]),
div(s[2], d[2]),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_linear_burn(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
(d[0] + s[0] - 1.0).max(0.0),
(d[1] + s[1] - 1.0).max(0.0),
(d[2] + s[2] - 1.0).max(0.0),
Self::composite_alpha(s[3], d[3]),
]
}
fn blend_linear_dodge(s: [f32; 4], d: [f32; 4]) -> [f32; 4] {
[
(d[0] + s[0]).min(1.0),
(d[1] + s[1]).min(1.0),
(d[2] + s[2]).min(1.0),
Self::composite_alpha(s[3], d[3]),
]
}
fn composite_alpha(sa: f32, da: f32) -> f32 {
sa + da * (1.0 - sa)
}
pub fn dither_rgba8(
&self,
pixels: &mut [u8],
width: usize,
height: usize,
levels: u8,
kind: DitherKind,
) {
match kind {
DitherKind::FloydSteinberg => {
self.dither_floyd_steinberg(pixels, width, height, levels)
}
DitherKind::Ordered => self.dither_ordered(pixels, width, height, levels),
DitherKind::BlueNoise => self.dither_blue_noise(pixels, width, height, levels),
DitherKind::Atkinson => self.dither_atkinson(pixels, width, height, levels),
DitherKind::JarvisJudiceNinke => self.dither_jarvis(pixels, width, height, levels),
DitherKind::Stucki => self.dither_stucki(pixels, width, height, levels),
}
}
fn dither_floyd_steinberg(&self, pixels: &mut [u8], width: usize, height: usize, levels: u8) {
let level_f = (levels - 1) as f32;
let step = 255.0 / level_f;
let mut errors = vec![0.0f32; width * height * 4];
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) * 4;
for c in 0..3 {
let old_val = pixels[idx + c] as f32 + errors[idx + c];
let new_val = ((old_val / step).round() * step).clamp(0.0, 255.0);
let quant_error = old_val - new_val;
pixels[idx + c] = new_val as u8;
let err = quant_error / 16.0;
if x + 1 < width {
errors[idx + 4 + c] += err * 7.0;
}
if y + 1 < height {
if x > 0 {
errors[(y + 1) * width * 4 + (x - 1) * 4 + c] += err * 3.0;
}
errors[(y + 1) * width * 4 + x * 4 + c] += err * 5.0;
if x + 1 < width {
errors[(y + 1) * width * 4 + (x + 1) * 4 + c] += err * 1.0;
}
}
}
}
}
}
fn dither_ordered(&self, pixels: &mut [u8], width: usize, height: usize, levels: u8) {
let bayer = Self::bayer_matrix(8);
let level_f = (levels - 1) as f32;
let step = 255.0 / level_f;
for y in 0..height {
for x in 0..width {
let threshold = (bayer[(y & 7) * 8 + (x & 7)] as f32 / 64.0 - 0.5) * step;
let idx = (y * width + x) * 4;
for c in 0..3 {
let val = pixels[idx + c] as f32 + threshold;
pixels[idx + c] = ((val / step).round() * step).clamp(0.0, 255.0) as u8;
}
}
}
}
fn bayer_matrix(size: usize) -> Vec<u32> {
if size == 1 {
return vec![0];
}
let half = size / 2;
let smaller = Self::bayer_matrix(half);
let mut result = vec![0u32; size * size];
for y in 0..half {
for x in 0..half {
let v = smaller[y * half + x] * 4;
result[y * size + x] = v;
result[y * size + x + half] = v + 2;
result[(y + half) * size + x] = v + 3;
result[(y + half) * size + x + half] = v + 1;
}
}
result
}
fn dither_blue_noise(&self, pixels: &mut [u8], width: usize, height: usize, levels: u8) {
let blue_noise = Self::blue_noise_64x64();
let level_f = (levels - 1) as f32;
let step = 255.0 / level_f;
for y in 0..height {
for x in 0..width {
let noise = blue_noise[(y & 63) * 64 + (x & 63)] as f32 / 255.0;
let threshold = (noise - 0.5) * step;
let idx = (y * width + x) * 4;
for c in 0..3 {
let val = pixels[idx + c] as f32 + threshold;
pixels[idx + c] = ((val / step).round() * step).clamp(0.0, 255.0) as u8;
}
}
}
}
fn blue_noise_64x64() -> &'static [u8] {
static NOISE: [u8; 4096] = [128u8; 4096];
&NOISE
}
fn dither_atkinson(&self, pixels: &mut [u8], width: usize, height: usize, levels: u8) {
let level_f = (levels - 1) as f32;
let step = 255.0 / level_f;
let mut errors = vec![0.0f32; width * height * 4];
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) * 4;
for c in 0..3 {
let old_val = pixels[idx + c] as f32 + errors[idx + c];
let new_val = ((old_val / step).round() * step).clamp(0.0, 255.0);
let quant_error = old_val - new_val;
pixels[idx + c] = new_val as u8;
let err = quant_error / 8.0;
let neighbors = [
(x + 1, y, err),
(x + 2, y, err),
(x.wrapping_sub(1), y + 1, err),
(x, y + 1, err),
(x + 1, y + 1, err),
(x, y + 2, err),
];
for &(nx, ny, e) in &neighbors {
if nx < width && ny < height {
errors[(ny * width + nx) * 4 + c] += e;
}
}
}
}
}
}
fn dither_jarvis(&self, pixels: &mut [u8], width: usize, height: usize, levels: u8) {
let level_f = (levels - 1) as f32;
let step = 255.0 / level_f;
let mut errors = vec![0.0f32; width * height * 4];
let weights: [(isize, isize, f32); 12] = [
(1, 0, 7.0),
(2, 0, 5.0),
(-2, 1, 3.0),
(-1, 1, 5.0),
(0, 1, 7.0),
(1, 1, 5.0),
(2, 1, 3.0),
(-2, 2, 1.0),
(-1, 2, 3.0),
(0, 2, 5.0),
(1, 2, 3.0),
(2, 2, 1.0),
];
let divisor: f32 = weights.iter().map(|w| w.2).sum();
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) * 4;
for c in 0..3 {
let old_val = pixels[idx + c] as f32 + errors[idx + c];
let new_val = ((old_val / step).round() * step).clamp(0.0, 255.0);
let quant_error = old_val - new_val;
pixels[idx + c] = new_val as u8;
for &(dx, dy, w) in &weights {
let nx = x as isize + dx;
let ny = y as isize + dy;
if nx >= 0 && nx < width as isize && ny >= 0 && ny < height as isize {
errors[(ny as usize * width + nx as usize) * 4 + c] +=
quant_error * w / divisor;
}
}
}
}
}
}
fn dither_stucki(&self, pixels: &mut [u8], width: usize, height: usize, levels: u8) {
let level_f = (levels - 1) as f32;
let step = 255.0 / level_f;
let mut errors = vec![0.0f32; width * height * 4];
let weights: [(isize, isize, f32); 12] = [
(1, 0, 8.0),
(2, 0, 4.0),
(-2, 1, 2.0),
(-1, 1, 4.0),
(0, 1, 8.0),
(1, 1, 4.0),
(2, 1, 2.0),
(-2, 2, 1.0),
(-1, 2, 2.0),
(0, 2, 4.0),
(1, 2, 2.0),
(2, 2, 1.0),
];
let divisor: f32 = weights.iter().map(|w| w.2).sum();
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) * 4;
for c in 0..3 {
let old_val = pixels[idx + c] as f32 + errors[idx + c];
let new_val = ((old_val / step).round() * step).clamp(0.0, 255.0);
let quant_error = old_val - new_val;
pixels[idx + c] = new_val as u8;
for &(dx, dy, w) in &weights {
let nx = x as isize + dx;
let ny = y as isize + dy;
if nx >= 0 && nx < width as isize && ny >= 0 && ny < height as isize {
errors[(ny as usize * width + nx as usize) * 4 + c] +=
quant_error * w / divisor;
}
}
}
}
}
}
pub fn gamma_correct(&self, pixels: &mut [u8], gamma: f32) {
let inv_gamma = 1.0 / gamma;
for chunk in pixels.chunks_exact_mut(4) {
for c in 0..3 {
let v = chunk[c] as f32 / 255.0;
let corrected = v.powf(inv_gamma);
chunk[c] = (corrected * 255.0 + 0.5) as u8;
}
}
}
pub fn fill_rgba8(&self, pixels: &mut [u8], color: [u8; 4]) {
for chunk in pixels.chunks_exact_mut(4) {
chunk.copy_from_slice(&color);
}
}
pub fn brightness_contrast(&self, pixels: &mut [u8], brightness: f32, contrast: f32) {
let factor = (259.0 * (contrast + 255.0)) / (255.0 * (259.0 - contrast));
for chunk in pixels.chunks_exact_mut(4) {
for c in 0..3 {
let v = factor * (chunk[c] as f32 - 128.0) + 128.0 + brightness;
chunk[c] = v.clamp(0.0, 255.0) as u8;
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86ShaderIntrinsics {
pub level: X86SIMDGraphicsLevel,
}
impl X86ShaderIntrinsics {
pub fn new(level: X86SIMDGraphicsLevel) -> Self {
Self { level }
}
pub fn _mm_dp_ps(&self, a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
let mut dot = 0.0f32;
for i in 0..4 {
dot += a[i] * b[i];
}
[dot, dot, dot, dot]
}
pub fn dp4_masked(&self, a: [f32; 4], b: [f32; 4], mask: u8) -> [f32; 4] {
let mut dot = 0.0f32;
for i in 0..4 {
if (mask >> i) & 1 != 0 {
dot += a[i] * b[i];
}
}
let mut result = [0.0f32; 4];
for i in 0..4 {
if (mask >> (i + 4)) & 1 != 0 {
result[i] = dot;
}
}
result
}
pub fn _mm256_dp_ps(&self, a: [f32; 8], b: [f32; 8]) -> [f32; 8] {
let mut dot = 0.0f32;
for i in 0..8 {
dot += a[i] * b[i];
}
[dot; 8]
}
pub fn dot(&self, a: &[f32], b: &[f32]) -> f32 {
assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(a, b)| a * b).sum()
}
pub fn dot_f64(&self, a: &[f64], b: &[f64]) -> f64 {
assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(a, b)| a * b).sum()
}
pub fn cross3(&self, a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
pub fn cross3_f64(&self, a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
pub fn normalize3(&self, v: [f32; 3]) -> [f32; 3] {
let len_sq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
if len_sq > EPSILON {
let inv_len = 1.0 / len_sq.sqrt();
[v[0] * inv_len, v[1] * inv_len, v[2] * inv_len]
} else {
[0.0; 3]
}
}
pub fn normalize4(&self, v: [f32; 4]) -> [f32; 4] {
let len_sq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3];
if len_sq > EPSILON {
let inv_len = 1.0 / len_sq.sqrt();
[
v[0] * inv_len,
v[1] * inv_len,
v[2] * inv_len,
v[3] * inv_len,
]
} else {
[0.0; 4]
}
}
pub fn length3(&self, v: [f32; 3]) -> f32 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
pub fn length4(&self, v: [f32; 4]) -> f32 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]).sqrt()
}
pub fn lerp(&self, a: f32, b: f32, t: f32) -> f32 {
a + t * (b - a)
}
pub fn lerp3(&self, a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
[
self.lerp(a[0], b[0], t),
self.lerp(a[1], b[1], t),
self.lerp(a[2], b[2], t),
]
}
pub fn lerp4(&self, a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
[
self.lerp(a[0], b[0], t),
self.lerp(a[1], b[1], t),
self.lerp(a[2], b[2], t),
self.lerp(a[3], b[3], t),
]
}
pub fn smoothstep(&self, edge0: f32, edge1: f32, x: f32) -> f32 {
let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
pub fn step(&self, edge: f32, x: f32) -> f32 {
if x < edge {
0.0
} else {
1.0
}
}
pub fn clamp(&self, x: f32, min_val: f32, max_val: f32) -> f32 {
x.max(min_val).min(max_val)
}
pub fn saturate(&self, x: f32) -> f32 {
self.clamp(x, 0.0, 1.0)
}
pub fn clamp3(&self, v: [f32; 3], min_val: f32, max_val: f32) -> [f32; 3] {
[
self.clamp(v[0], min_val, max_val),
self.clamp(v[1], min_val, max_val),
self.clamp(v[2], min_val, max_val),
]
}
pub fn reflect(&self, i: [f32; 3], n: [f32; 3]) -> [f32; 3] {
let dot_ni = self.dot(&n, &i);
let two_dot = 2.0 * dot_ni;
[
i[0] - two_dot * n[0],
i[1] - two_dot * n[1],
i[2] - two_dot * n[2],
]
}
pub fn refract(&self, i: [f32; 3], n: [f32; 3], eta: f32) -> [f32; 3] {
let dot_ni = self.dot(&n, &i);
let k = 1.0 - eta * eta * (1.0 - dot_ni * dot_ni);
if k < 0.0 {
[0.0; 3] } else {
let sqrt_k = k.sqrt();
[
eta * i[0] - (eta * dot_ni + sqrt_k) * n[0],
eta * i[1] - (eta * dot_ni + sqrt_k) * n[1],
eta * i[2] - (eta * dot_ni + sqrt_k) * n[2],
]
}
}
pub fn faceforward(&self, n: [f32; 3], i: [f32; 3], nref: [f32; 3]) -> [f32; 3] {
if self.dot(&nref, &i) < 0.0 {
n
} else {
[-n[0], -n[1], -n[2]]
}
}
pub fn mat4_mul_mat4(&self, a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
let mut r = [0.0f32; 16];
for col in 0..4 {
for row in 0..4 {
let mut sum = 0.0f32;
for k in 0..4 {
sum += a[k * 4 + row] * b[col * 4 + k];
}
r[col * 4 + row] = sum;
}
}
r
}
pub fn mat4_mul_vec4(&self, m: &[f32; 16], v: &[f32; 4]) -> [f32; 4] {
[
m[0] * v[0] + m[4] * v[1] + m[8] * v[2] + m[12] * v[3],
m[1] * v[0] + m[5] * v[1] + m[9] * v[2] + m[13] * v[3],
m[2] * v[0] + m[6] * v[1] + m[10] * v[2] + m[14] * v[3],
m[3] * v[0] + m[7] * v[1] + m[11] * v[2] + m[15] * v[3],
]
}
pub fn mat4_mul_point3(&self, m: &[f32; 16], p: &[f32; 3]) -> [f32; 3] {
let v = self.mat4_mul_vec4(m, &[p[0], p[1], p[2], 1.0]);
let inv_w = if v[3].abs() > EPSILON {
1.0 / v[3]
} else {
0.0
};
[v[0] * inv_w, v[1] * inv_w, v[2] * inv_w]
}
pub fn mat4_mul_dir3(&self, m: &[f32; 16], d: &[f32; 3]) -> [f32; 3] {
let v = self.mat4_mul_vec4(m, &[d[0], d[1], d[2], 0.0]);
[v[0], v[1], v[2]]
}
pub fn mat4_transpose(&self, m: &[f32; 16]) -> [f32; 16] {
[
m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7],
m[11], m[15],
]
}
pub fn mat4_determinant(&self, m: &[f32; 16]) -> f32 {
let s0 = m[0] * m[5] - m[4] * m[1];
let s1 = m[0] * m[6] - m[4] * m[2];
let s2 = m[0] * m[7] - m[4] * m[3];
let s3 = m[1] * m[6] - m[5] * m[2];
let s4 = m[1] * m[7] - m[5] * m[3];
let s5 = m[2] * m[7] - m[6] * m[3];
let c0 = m[10] * m[15] - m[14] * m[11];
let c1 = m[9] * m[15] - m[13] * m[11];
let c2 = m[9] * m[14] - m[13] * m[10];
let c3 = m[8] * m[15] - m[12] * m[11];
let c4 = m[8] * m[14] - m[12] * m[10];
let c5 = m[8] * m[13] - m[12] * m[9];
s0 * c0 - s1 * c1 + s2 * c2 + s3 * c3 - s4 * c4 + s5 * c5
}
pub fn mat4_inverse(&self, m: &[f32; 16]) -> [f32; 16] {
let det = self.mat4_determinant(m);
if det.abs() < EPSILON {
return Self::mat4_identity();
}
let inv_det = 1.0 / det;
let s0 = m[0] * m[5] - m[4] * m[1];
let s1 = m[0] * m[6] - m[4] * m[2];
let s2 = m[0] * m[7] - m[4] * m[3];
let s3 = m[1] * m[6] - m[5] * m[2];
let s4 = m[1] * m[7] - m[5] * m[3];
let s5 = m[2] * m[7] - m[6] * m[3];
let c0 = m[10] * m[15] - m[14] * m[11];
let c1 = m[9] * m[15] - m[13] * m[11];
let c2 = m[9] * m[14] - m[13] * m[10];
let c3 = m[8] * m[15] - m[12] * m[11];
let c4 = m[8] * m[14] - m[12] * m[10];
let c5 = m[8] * m[13] - m[12] * m[9];
[
(m[5] * c0 - m[6] * c1 + m[7] * c2) * inv_det,
(-m[1] * c0 + m[2] * c1 - m[3] * c2) * inv_det,
(m[13] * s5 - m[14] * s4 + m[15] * s3) * inv_det,
(-m[9] * s5 + m[10] * s4 - m[11] * s3) * inv_det,
(-m[4] * c0 + m[6] * c3 - m[7] * c4) * inv_det,
(m[0] * c0 - m[2] * c3 + m[3] * c4) * inv_det,
(-m[12] * s5 + m[14] * s2 - m[15] * s1) * inv_det,
(m[8] * s5 - m[10] * s2 + m[11] * s1) * inv_det,
(m[4] * c1 - m[5] * c3 + m[7] * c5) * inv_det,
(-m[0] * c1 + m[1] * c3 - m[3] * c5) * inv_det,
(m[12] * s4 - m[13] * s2 + m[15] * s0) * inv_det,
(-m[8] * s4 + m[9] * s2 - m[11] * s0) * inv_det,
(-m[4] * c2 + m[5] * c4 - m[6] * c5) * inv_det,
(m[0] * c2 - m[1] * c4 + m[2] * c5) * inv_det,
(-m[12] * s3 + m[13] * s1 - m[14] * s0) * inv_det,
(m[8] * s3 - m[9] * s1 + m[10] * s0) * inv_det,
]
}
pub fn mat4_identity() -> [f32; 16] {
[
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
pub fn distance3(&self, a: [f32; 3], b: [f32; 3]) -> f32 {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let dz = a[2] - b[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn fresnel_schlick(&self, cos_theta: f32, f0: [f32; 3]) -> [f32; 3] {
let t = (1.0 - cos_theta).max(0.0);
let t2 = t * t;
let t4 = t2 * t2;
let t5 = t4 * t;
[
f0[0] + (1.0 - f0[0]) * t5,
f0[1] + (1.0 - f0[1]) * t5,
f0[2] + (1.0 - f0[2]) * t5,
]
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86GeometryOps {
pub shader: X86ShaderIntrinsics,
}
impl X86GeometryOps {
pub fn new() -> Self {
Self {
shader: X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::detect()),
}
}
pub fn aabb_intersects(&self, a: &AABB, b: &AABB) -> bool {
a.intersects(b)
}
pub fn aabb_intersection(&self, a: &AABB, b: &AABB) -> Option<AABB> {
let min = [
a.min[0].max(b.min[0]),
a.min[1].max(b.min[1]),
a.min[2].max(b.min[2]),
];
let max = [
a.max[0].min(b.max[0]),
a.max[1].min(b.max[1]),
a.max[2].min(b.max[2]),
];
if min[0] <= max[0] && min[1] <= max[1] && min[2] <= max[2] {
Some(AABB::new(min, max))
} else {
None
}
}
pub fn ray_triangle_intersect(
&self,
ray: &Ray,
v0: [f32; 3],
v1: [f32; 3],
v2: [f32; 3],
) -> Option<f32> {
let e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
let e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
let h = self.shader.cross3(ray.direction, e2);
let a = self.shader.dot(&e1, &h);
if a.abs() < EPSILON {
return None; }
let f = 1.0 / a;
let s = [
ray.origin[0] - v0[0],
ray.origin[1] - v0[1],
ray.origin[2] - v0[2],
];
let u = f * self.shader.dot(&s, &h);
if u < 0.0 || u > 1.0 {
return None;
}
let q = self.shader.cross3(s, e1);
let v = f * self.shader.dot(&ray.direction, &q);
if v < 0.0 || u + v > 1.0 {
return None;
}
let t = f * self.shader.dot(&e2, &q);
if t > EPSILON {
Some(t)
} else {
None
}
}
pub fn ray_sphere_intersect(&self, ray: &Ray, center: [f32; 3], radius: f32) -> Option<f32> {
let oc = [
ray.origin[0] - center[0],
ray.origin[1] - center[1],
ray.origin[2] - center[2],
];
let a = self.shader.dot(&ray.direction, &ray.direction);
let b = 2.0 * self.shader.dot(&oc, &ray.direction);
let c = self.shader.dot(&oc, &oc) - radius * radius;
let discriminant = b * b - 4.0 * a * c;
if discriminant < 0.0 {
return None;
}
let sqrt_d = discriminant.sqrt();
let t0 = (-b - sqrt_d) / (2.0 * a);
let t1 = (-b + sqrt_d) / (2.0 * a);
if t0 > EPSILON {
Some(t0)
} else if t1 > EPSILON {
Some(t1)
} else {
None
}
}
pub fn ray_aabb_intersect(&self, ray: &Ray, aabb: &AABB) -> Option<(f32, f32)> {
let mut tmin = f32::NEG_INFINITY;
let mut tmax = f32::INFINITY;
for i in 0..3 {
let inv_d = if ray.direction[i].abs() > EPSILON {
1.0 / ray.direction[i]
} else {
0.0
};
let t0 = (aabb.min[i] - ray.origin[i]) * inv_d;
let t1 = (aabb.max[i] - ray.origin[i]) * inv_d;
let (tnear, tfar) = if inv_d < 0.0 { (t1, t0) } else { (t0, t1) };
tmin = tmin.max(tnear);
tmax = tmax.min(tfar);
}
if tmin <= tmax && tmax >= 0.0 {
Some((tmin.max(0.0), tmax))
} else {
None
}
}
pub fn sphere_frustum_test(&self, frustum: &Frustum, center: [f32; 3], radius: f32) -> bool {
for plane in &frustum.planes {
if frustum.plane_distance(plane, ¢er) < -radius {
return false;
}
}
true
}
pub fn aabb_frustum_test(&self, frustum: &Frustum, aabb: &AABB) -> bool {
for plane in &frustum.planes {
let pv = [
if plane[0] >= 0.0 {
aabb.max[0]
} else {
aabb.min[0]
},
if plane[1] >= 0.0 {
aabb.max[1]
} else {
aabb.min[1]
},
if plane[2] >= 0.0 {
aabb.max[2]
} else {
aabb.min[2]
},
];
if frustum.plane_distance(plane, &pv) < 0.0 {
return false;
}
}
true
}
pub fn translate(&self, tx: f32, ty: f32, tz: f32) -> [f32; 16] {
[
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, tx, ty, tz, 1.0,
]
}
pub fn rotate_x(&self, angle: f32) -> [f32; 16] {
let c = angle.cos();
let s = angle.sin();
[
1.0, 0.0, 0.0, 0.0, 0.0, c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
pub fn rotate_y(&self, angle: f32) -> [f32; 16] {
let c = angle.cos();
let s = angle.sin();
[
c, 0.0, -s, 0.0, 0.0, 1.0, 0.0, 0.0, s, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
pub fn rotate_z(&self, angle: f32) -> [f32; 16] {
let c = angle.cos();
let s = angle.sin();
[
c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
pub fn rotate_euler(&self, yaw: f32, pitch: f32, roll: f32) -> [f32; 16] {
let rz = self.rotate_z(roll);
let ry = self.rotate_y(yaw);
let rx = self.rotate_x(pitch);
self.shader
.mat4_mul_mat4(&rz, &self.shader.mat4_mul_mat4(&ry, &rx))
}
pub fn scale(&self, sx: f32, sy: f32, sz: f32) -> [f32; 16] {
[
sx, 0.0, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 0.0, sz, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
pub fn look_at(&self, eye: [f32; 3], center: [f32; 3], up: [f32; 3]) -> [f32; 16] {
let f =
self.shader
.normalize3([center[0] - eye[0], center[1] - eye[1], center[2] - eye[2]]);
let s = self.shader.normalize3(self.shader.cross3(f, up));
let u = self.shader.cross3(s, f);
[
s[0],
u[0],
-f[0],
0.0,
s[1],
u[1],
-f[1],
0.0,
s[2],
u[2],
-f[2],
0.0,
-self.shader.dot(&s, &eye),
-self.shader.dot(&u, &eye),
self.shader.dot(&f, &eye),
1.0,
]
}
pub fn perspective(&self, fov: f32, aspect: f32, near: f32, far: f32) -> [f32; 16] {
let f = 1.0 / (fov * 0.5).tan();
let nf = 1.0 / (near - far);
[
f / aspect,
0.0,
0.0,
0.0,
0.0,
f,
0.0,
0.0,
0.0,
0.0,
(far + near) * nf,
-1.0,
0.0,
0.0,
2.0 * far * near * nf,
0.0,
]
}
pub fn ortho(
&self,
left: f32,
right: f32,
bottom: f32,
top: f32,
near: f32,
far: f32,
) -> [f32; 16] {
let rl = 1.0 / (right - left);
let tb = 1.0 / (top - bottom);
let r#fn = 1.0 / (near - far);
[
2.0 * rl,
0.0,
0.0,
0.0,
0.0,
2.0 * tb,
0.0,
0.0,
0.0,
0.0,
2.0 * r#fn,
0.0,
-(right + left) * rl,
-(top + bottom) * tb,
(far + near) * r#fn,
1.0,
]
}
pub fn compose_mvp(&self, model: &[f32; 16], view: &[f32; 16], proj: &[f32; 16]) -> [f32; 16] {
let mv = self.shader.mat4_mul_mat4(view, model);
self.shader.mat4_mul_mat4(proj, &mv)
}
pub fn bounding_sphere(&self, points: &[[f32; 3]]) -> ([f32; 3], f32) {
if points.is_empty() {
return ([0.0; 3], 0.0);
}
let sum: [f32; 3] = points.iter().fold([0.0; 3], |acc, p| {
[acc[0] + p[0], acc[1] + p[1], acc[2] + p[2]]
});
let n = points.len() as f32;
let center = [sum[0] / n, sum[1] / n, sum[2] / n];
let radius = points
.iter()
.map(|p| {
let dx = p[0] - center[0];
let dy = p[1] - center[1];
let dz = p[2] - center[2];
(dx * dx + dy * dy + dz * dz).sqrt()
})
.fold(0.0f32, f32::max);
(center, radius)
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86ColorSpaceOps;
impl X86ColorSpaceOps {
pub fn new() -> Self {
Self
}
pub fn srgb_to_linear_component(&self, c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
pub fn linear_to_srgb_component(&self, c: f32) -> f32 {
if c <= 0.0031308 {
c * 12.92
} else {
1.055 * c.powf(1.0 / 2.4) - 0.055
}
}
pub fn srgb_to_linear(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
(
self.srgb_to_linear_component(r),
self.srgb_to_linear_component(g),
self.srgb_to_linear_component(b),
)
}
pub fn linear_to_srgb(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
(
self.linear_to_srgb_component(r),
self.linear_to_srgb_component(g),
self.linear_to_srgb_component(b),
)
}
pub fn srgb8_to_linear_f32(&self, pixels: &[u8], out: &mut [f32]) {
assert_eq!(pixels.len() / 4, out.len() / 4);
for i in 0..pixels.len() / 4 {
let o = i * 4;
out[o] = self.srgb_to_linear_component(pixels[o] as f32 / 255.0);
out[o + 1] = self.srgb_to_linear_component(pixels[o + 1] as f32 / 255.0);
out[o + 2] = self.srgb_to_linear_component(pixels[o + 2] as f32 / 255.0);
out[o + 3] = pixels[o + 3] as f32 / 255.0;
}
}
pub fn linear_f32_to_srgb8(&self, src: &[f32], pixels: &mut [u8]) {
assert_eq!(src.len() / 4, pixels.len() / 4);
for i in 0..pixels.len() / 4 {
let o = i * 4;
pixels[o] = (self.linear_to_srgb_component(src[o]) * 255.0 + 0.5) as u8;
pixels[o + 1] = (self.linear_to_srgb_component(src[o + 1]) * 255.0 + 0.5) as u8;
pixels[o + 2] = (self.linear_to_srgb_component(src[o + 2]) * 255.0 + 0.5) as u8;
pixels[o + 3] = (src[o + 3] * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
pub fn rgb_to_hsv(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let h = if delta == 0.0 {
0.0
} else if max == r {
60.0 * (((g - b) / delta) % 6.0)
} else if max == g {
60.0 * (((b - r) / delta) + 2.0)
} else {
60.0 * (((r - g) / delta) + 4.0)
};
let h = if h < 0.0 { h + 360.0 } else { h };
let s = if max == 0.0 { 0.0 } else { delta / max };
let v = max;
(h, s, v)
}
pub fn hsv_to_rgb(&self, h: f32, s: f32, v: f32) -> (f32, f32, f32) {
let c = v * s;
let hp = h / 60.0;
let x = c * (1.0 - ((hp % 2.0) - 1.0).abs());
let m = v - c;
let (r1, g1, b1) = match hp as u32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
(r1 + m, g1 + m, b1 + m)
}
pub fn rgb_to_hsl(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let l = (max + min) / 2.0;
if delta == 0.0 {
return (0.0, 0.0, l);
}
let s = if l <= 0.5 {
delta / (max + min)
} else {
delta / (2.0 - max - min)
};
let h = if max == r {
60.0 * (((g - b) / delta) % 6.0)
} else if max == g {
60.0 * (((b - r) / delta) + 2.0)
} else {
60.0 * (((r - g) / delta) + 4.0)
};
let h = if h < 0.0 { h + 360.0 } else { h };
(h, s, l)
}
pub fn hsl_to_rgb(&self, h: f32, s: f32, l: f32) -> (f32, f32, f32) {
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let hp = h / 60.0;
let x = c * (1.0 - ((hp % 2.0) - 1.0).abs());
let m = l - c / 2.0;
let (r1, g1, b1) = match hp as u32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
(r1 + m, g1 + m, b1 + m)
}
pub fn rgb_to_yuv(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let y = 0.299 * r + 0.587 * g + 0.114 * b;
let u = -0.14713 * r - 0.28886 * g + 0.436 * b;
let v = 0.615 * r - 0.51499 * g - 0.10001 * b;
(y, u, v)
}
pub fn yuv_to_rgb(&self, y: f32, u: f32, v: f32) -> (f32, f32, f32) {
let r = y + 1.13983 * v;
let g = y - 0.39465 * u - 0.58060 * v;
let b = y + 2.03211 * u;
(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0))
}
pub fn rgb_to_ycbcr(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let y = 0.299 * r + 0.587 * g + 0.114 * b;
let cb = -0.168736 * r - 0.331264 * g + 0.5 * b + 0.5;
let cr = 0.5 * r - 0.418688 * g - 0.081312 * b + 0.5;
(y, cb, cr)
}
pub fn ycbcr_to_rgb(&self, y: f32, cb: f32, cr: f32) -> (f32, f32, f32) {
let r = y + 1.402 * (cr - 0.5);
let g = y - 0.344136 * (cb - 0.5) - 0.714136 * (cr - 0.5);
let b = y + 1.772 * (cb - 0.5);
(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0))
}
pub fn rgb_to_xyz(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b;
let y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
let z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b;
(x, y, z)
}
pub fn xyz_to_rgb(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z;
let g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z;
let b = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z;
(r.max(0.0), g.max(0.0), b.max(0.0))
}
pub fn xyz_to_lab(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let xn = 0.95047;
let yn = 1.0;
let zn = 1.08883;
let fx = Self::lab_f(x / xn);
let fy = Self::lab_f(y / yn);
let fz = Self::lab_f(z / zn);
let l = 116.0 * fy - 16.0;
let a = 500.0 * (fx - fy);
let b = 200.0 * (fy - fz);
(l, a, b)
}
pub fn lab_to_xyz(&self, l: f32, a: f32, b: f32) -> (f32, f32, f32) {
let fy = (l + 16.0) / 116.0;
let fx = a / 500.0 + fy;
let fz = fy - b / 200.0;
let xn = 0.95047;
let yn = 1.0;
let zn = 1.08883;
let x = xn * Self::lab_f_inv(fx);
let y = yn * Self::lab_f_inv(fy);
let z = zn * Self::lab_f_inv(fz);
(x, y, z)
}
fn lab_f(t: f32) -> f32 {
let delta: f32 = 6.0 / 29.0;
let delta3 = delta * delta * delta;
let delta2_3 = delta * delta / 3.0;
let four_over_29 = 4.0 / 29.0;
if t > delta3 {
t.cbrt()
} else {
t / (3.0 * delta2_3) + four_over_29
}
}
fn lab_f_inv(t: f32) -> f32 {
let delta: f32 = 6.0 / 29.0;
if t > delta {
t * t * t
} else {
3.0 * delta * delta * (t - 4.0 / 29.0)
}
}
pub fn lab_to_lch(&self, l: f32, a: f32, b: f32) -> (f32, f32, f32) {
let c = (a * a + b * b).sqrt();
let h = b.atan2(a).to_degrees();
let h = if h < 0.0 { h + 360.0 } else { h };
(l, c, h)
}
pub fn lch_to_lab(&self, l: f32, c: f32, h: f32) -> (f32, f32, f32) {
let h_rad = h.to_radians();
let a = c * h_rad.cos();
let b = c * h_rad.sin();
(l, a, b)
}
pub fn kelvin_to_rgb(&self, kelvin: f32) -> (f32, f32, f32) {
let temp = kelvin / 100.0;
let r = if temp <= 66.0 {
1.0
} else {
let r = temp - 60.0;
(1.29293618606 * r.powf(-0.1332047592)).clamp(0.0, 1.0)
};
let g = if temp <= 66.0 {
let g = temp;
(0.39008157876902 * g.ln() - 0.631841443788627).clamp(0.0, 1.0)
} else {
let g = temp - 60.0;
(1.129890860895 * g.powf(-0.0755148492)).clamp(0.0, 1.0)
};
let b = if temp >= 66.0 {
1.0
} else if temp <= 19.0 {
0.0
} else {
let b = temp - 10.0;
(0.543206789110196 * b.ln() - 1.19625408914).clamp(0.0, 1.0)
};
(r, g, b)
}
pub fn tonemap_reinhard(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
(r / (1.0 + r), g / (1.0 + g), b / (1.0 + b))
}
pub fn tonemap_reinhard_ext(&self, r: f32, g: f32, b: f32, white: f32) -> (f32, f32, f32) {
let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
let mapped = luminance * (1.0 + luminance / (white * white)) / (1.0 + luminance);
let scale = if luminance > 0.0 {
mapped / luminance
} else {
0.0
};
(r * scale, g * scale, b * scale)
}
pub fn tonemap_aces(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let a = 2.51;
let b = 0.03;
let c = 2.43;
let d = 0.59;
let e = 0.14;
let f = |x: f32| {
let v = (x * (a * x + b)) / (x * (c * x + d) + e);
v.clamp(0.0, 1.0)
};
(f(r), f(g), f(b))
}
pub fn tonemap_hable(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let a = 0.15;
let b = 0.50;
let c = 0.10;
let d = 0.20;
let e = 0.02;
let f = 0.30;
let w = 11.2;
let h =
|x: f32| -> f32 { ((x * (a * x + c * b) + d * e) / (x * (a * x + b) + d * f)) - e / f };
let white_scale = 1.0 / h(w);
(
(h(r) * white_scale).clamp(0.0, 1.0),
(h(g) * white_scale).clamp(0.0, 1.0),
(h(b) * white_scale).clamp(0.0, 1.0),
)
}
pub fn tonemap_agx(&self, r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let agx = |x: f32| -> f32 {
let x = x.max(0.0);
let x2 = x * x;
let x3 = x2 * x;
(x3 + 0.01 * x2 + 0.001 * x) / (x3 + 0.1 * x2 + 0.02 * x + 0.001)
};
(agx(r), agx(g), agx(b))
}
pub fn f32_to_f16(&self, value: f32) -> u16 {
let bits = value.to_bits();
let sign = (bits >> 16) as u16 & 0x8000;
let exponent = ((bits >> 23) & 0xFF) as i32;
let mantissa = bits & 0x007FFFFF;
if exponent == 0 {
return sign;
}
if exponent == 0xFF {
if mantissa == 0 {
return sign | 0x7C00; } else {
return sign | 0x7FFF; }
}
let mut exp16 = exponent - 127 + 15;
if exp16 <= 0 {
return sign; }
if exp16 >= 0x1F {
return sign | 0x7C00; }
let frac16 = (mantissa >> 13) as u16 & 0x03FF;
sign | ((exp16 as u16) << 10) | frac16
}
pub fn f16_to_f32(&self, value: u16) -> f32 {
let sign = ((value & 0x8000) as u32) << 16;
let exponent = ((value >> 10) & 0x1F) as u32;
let mantissa = (value & 0x03FF) as u32;
if exponent == 0 {
if mantissa == 0 {
return f32::from_bits(sign);
}
let mut m = mantissa;
let mut e = -14i32;
while (m & 0x0400) == 0 {
m <<= 1;
e -= 1;
}
m &= 0x03FF;
let exp_bits = ((e + 127) as u32) << 23;
return f32::from_bits(sign | exp_bits | (m << 13));
}
if exponent == 0x1F {
if mantissa == 0 {
return f32::from_bits(sign | 0x7F800000); } else {
return f32::from_bits(sign | 0x7FC00000); }
}
let exp_bits = (exponent - 15 + 127) << 23;
let mantissa_bits = mantissa << 13;
f32::from_bits(sign | exp_bits | mantissa_bits)
}
pub fn rgba_f16_to_f32(&self, src: &[u16], dst: &mut [f32]) {
for (i, &v) in src.iter().enumerate() {
dst[i] = self.f16_to_f32(v);
}
}
pub fn rgba_f32_to_f16(&self, src: &[f32], dst: &mut [u16]) {
for (i, &v) in src.iter().enumerate() {
dst[i] = self.f32_to_f16(v);
}
}
pub fn rgbe_encode(&self, r: f32, g: f32, b: f32) -> [u8; 4] {
let max_val = r.max(g).max(b);
if max_val < 1e-32 {
return [0, 0, 0, 0];
}
let (mantissa, exponent) = frexp(max_val as f64);
let mut exponent = exponent as i32 + 128;
let scale = (mantissa as f32 * 256.0) / max_val;
let ri = (r * scale).clamp(0.0, 255.0) as u8;
let gi = (g * scale).clamp(0.0, 255.0) as u8;
let bi = (b * scale).clamp(0.0, 255.0) as u8;
if exponent < 0 {
return [ri, gi, bi, 0];
}
if exponent > 255 {
exponent = 255;
}
[ri, gi, bi, exponent as u8]
}
pub fn rgbe_decode(&self, rgbe: [u8; 4]) -> (f32, f32, f32) {
if rgbe[3] == 0 {
return (0.0, 0.0, 0.0);
}
let scale = ldexp(1.0, rgbe[3] as i32 - 128 - 8) as f32;
(
(rgbe[0] as f32 + 0.5) * scale,
(rgbe[1] as f32 + 0.5) * scale,
(rgbe[2] as f32 + 0.5) * scale,
)
}
pub fn luminance(&self, r: f32, g: f32, b: f32) -> f32 {
0.2126 * r + 0.7152 * g + 0.0722 * b
}
pub fn perceived_brightness(&self, r: u8, g: u8, b: u8) -> f32 {
let rf = r as f32 / 255.0;
let gf = g as f32 / 255.0;
let bf = b as f32 / 255.0;
(0.299 * rf + 0.587 * gf + 0.114 * bf).sqrt()
}
pub fn contrast_ratio(&self, l1: f32, l2: f32) -> f32 {
let l1 = l1 + 0.05;
let l2 = l2 + 0.05;
if l1 > l2 {
l1 / l2
} else {
l2 / l1
}
}
}
#[derive(Debug, Clone)]
pub struct X86FontRendering;
impl X86FontRendering {
pub fn new() -> Self {
Self
}
pub fn parse_ttf_table_directory(&self, data: &[u8]) -> Option<Vec<TTFTableRecord>> {
if data.len() < 12 {
return None;
}
let num_tables = u16::from_be_bytes([data[4], data[5]]) as usize;
if data.len() < 12 + num_tables * 16 {
return None;
}
let mut tables = Vec::with_capacity(num_tables);
for i in 0..num_tables {
let off = 12 + i * 16;
let tag = [data[off], data[off + 1], data[off + 2], data[off + 3]];
let checksum =
u32::from_be_bytes([data[off + 4], data[off + 5], data[off + 6], data[off + 7]]);
let offset =
u32::from_be_bytes([data[off + 8], data[off + 9], data[off + 10], data[off + 11]]);
let length = u32::from_be_bytes([
data[off + 12],
data[off + 13],
data[off + 14],
data[off + 15],
]);
tables.push(TTFTableRecord {
tag,
checksum,
offset,
length,
});
}
Some(tables)
}
pub fn find_table<'a>(
&self,
tables: &'a [TTFTableRecord],
tag: &[u8; 4],
) -> Option<&'a TTFTableRecord> {
tables.iter().find(|t| &t.tag == tag)
}
pub fn parse_head_table(&self, data: &[u8], offset: usize) -> Option<HeadTable> {
if data.len() < offset + 54 {
return None;
}
let d = &data[offset..];
Some(HeadTable {
major_version: u16::from_be_bytes([d[0], d[1]]),
minor_version: u16::from_be_bytes([d[2], d[3]]),
font_revision: u32::from_be_bytes([d[4], d[5], d[6], d[7]]),
units_per_em: u16::from_be_bytes([d[18], d[19]]),
created: Self::read_long_date_time(&d[20..28]),
modified: Self::read_long_date_time(&d[28..36]),
x_min: i16::from_be_bytes([d[36], d[37]]),
y_min: i16::from_be_bytes([d[38], d[39]]),
x_max: i16::from_be_bytes([d[40], d[41]]),
y_max: i16::from_be_bytes([d[42], d[43]]),
mac_style: u16::from_be_bytes([d[44], d[45]]),
lowest_rec_ppem: u16::from_be_bytes([d[46], d[47]]),
font_direction: i16::from_be_bytes([d[48], d[49]]),
index_to_loc_format: i16::from_be_bytes([d[50], d[51]]),
glyph_data_format: i16::from_be_bytes([d[52], d[53]]),
})
}
fn read_long_date_time(bytes: &[u8]) -> i64 {
i64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
pub fn parse_hhea_table(&self, data: &[u8], offset: usize) -> Option<HheaTable> {
if data.len() < offset + 36 {
return None;
}
let d = &data[offset..];
Some(HheaTable {
ascent: i16::from_be_bytes([d[4], d[5]]),
descent: i16::from_be_bytes([d[6], d[7]]),
line_gap: i16::from_be_bytes([d[8], d[9]]),
advance_width_max: u16::from_be_bytes([d[10], d[11]]),
min_left_side_bearing: i16::from_be_bytes([d[12], d[13]]),
min_right_side_bearing: i16::from_be_bytes([d[14], d[15]]),
x_max_extent: i16::from_be_bytes([d[16], d[17]]),
caret_slope_rise: i16::from_be_bytes([d[18], d[19]]),
caret_slope_run: i16::from_be_bytes([d[20], d[21]]),
caret_offset: i16::from_be_bytes([d[22], d[23]]),
num_hmetrics: u16::from_be_bytes([d[34], d[35]]),
})
}
pub fn parse_hmtx_table(
&self,
data: &[u8],
offset: usize,
num_hmetrics: u16,
num_glyphs: u16,
) -> Vec<HorizontalMetric> {
let mut metrics = Vec::with_capacity(num_glyphs as usize);
for i in 0..num_hmetrics as usize {
let off = offset + i * 4;
if data.len() < off + 4 {
break;
}
metrics.push(HorizontalMetric {
advance_width: u16::from_be_bytes([data[off], data[off + 1]]),
left_side_bearing: i16::from_be_bytes([data[off + 2], data[off + 3]]),
});
}
let last_aw = metrics.last().map(|m| m.advance_width).unwrap_or(0);
for i in num_hmetrics as usize..num_glyphs as usize {
let off = offset + num_hmetrics as usize * 4 + (i - num_hmetrics as usize) * 2;
if data.len() < off + 2 {
break;
}
metrics.push(HorizontalMetric {
advance_width: last_aw,
left_side_bearing: i16::from_be_bytes([data[off], data[off + 1]]),
});
}
metrics
}
pub fn parse_loca_table(
&self,
data: &[u8],
offset: usize,
num_glyphs: u16,
index_to_loc_format: i16,
) -> Vec<u32> {
let count = num_glyphs as usize + 1;
let mut offsets = Vec::with_capacity(count);
if index_to_loc_format == 0 {
for i in 0..count {
let off = offset + i * 2;
if data.len() < off + 2 {
offsets.push(0);
} else {
let v = u16::from_be_bytes([data[off], data[off + 1]]) as u32;
offsets.push(v * 2);
}
}
} else {
for i in 0..count {
let off = offset + i * 4;
if data.len() < off + 4 {
offsets.push(0);
} else {
offsets.push(u32::from_be_bytes([
data[off],
data[off + 1],
data[off + 2],
data[off + 3],
]));
}
}
}
offsets
}
pub fn parse_cmap_table(&self, data: &[u8], offset: usize) -> Vec<CmapSubtable> {
let mut subtables = Vec::new();
if data.len() < offset + 4 {
return subtables;
}
let num_tables = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize;
for i in 0..num_tables {
let rec_off = offset + 4 + i * 8;
if data.len() < rec_off + 8 {
break;
}
let platform_id = u16::from_be_bytes([data[rec_off], data[rec_off + 1]]);
let encoding_id = u16::from_be_bytes([data[rec_off + 2], data[rec_off + 3]]);
let subtable_offset = u32::from_be_bytes([
data[rec_off + 4],
data[rec_off + 5],
data[rec_off + 6],
data[rec_off + 7],
]) as usize;
let abs_offset = offset + subtable_offset;
if data.len() < abs_offset + 2 {
continue;
}
let format = u16::from_be_bytes([data[abs_offset], data[abs_offset + 1]]);
match format {
4 => {
if let Some(sub) = self.parse_cmap_format4(data, abs_offset) {
subtables.push(CmapSubtable {
platform_id,
encoding_id,
format: 4,
format4_segments: sub,
format12_groups: Vec::new(),
});
}
}
12 => {
if let Some(sub) = self.parse_cmap_format12(data, abs_offset) {
subtables.push(CmapSubtable {
platform_id,
encoding_id,
format: 12,
format4_segments: Vec::new(),
format12_groups: sub,
});
}
}
_ => {}
}
}
subtables
}
fn parse_cmap_format4(&self, data: &[u8], offset: usize) -> Option<Vec<(u16, u16, i16, u16)>> {
if data.len() < offset + 14 {
return None;
}
let seg_count_x2 = u16::from_be_bytes([data[offset + 6], data[offset + 7]]);
let seg_count = (seg_count_x2 / 2) as usize;
let end_codes_off = offset + 14;
let start_codes_off = end_codes_off + 2 + seg_count * 2;
let id_delta_off = start_codes_off + seg_count * 2;
let id_range_off = id_delta_off + seg_count * 2;
let mut segments = Vec::with_capacity(seg_count);
for i in 0..seg_count {
let ec_off = end_codes_off + i * 2;
let sc_off = start_codes_off + i * 2;
let id_off = id_delta_off + i * 2;
let ir_off = id_range_off + i * 2;
if data.len() < ir_off + 2 {
break;
}
segments.push((
u16::from_be_bytes([data[sc_off], data[sc_off + 1]]),
u16::from_be_bytes([data[ec_off], data[ec_off + 1]]),
i16::from_be_bytes([data[id_off], data[id_off + 1]]),
u16::from_be_bytes([data[ir_off], data[ir_off + 1]]),
));
}
Some(segments)
}
fn parse_cmap_format12(&self, data: &[u8], offset: usize) -> Option<Vec<(u32, u32, u32)>> {
if data.len() < offset + 16 {
return None;
}
let num_groups = u32::from_be_bytes([
data[offset + 12],
data[offset + 13],
data[offset + 14],
data[offset + 15],
]) as usize;
let mut groups = Vec::with_capacity(num_groups);
for i in 0..num_groups {
let goff = offset + 16 + i * 12;
if data.len() < goff + 12 {
break;
}
groups.push((
u32::from_be_bytes([data[goff], data[goff + 1], data[goff + 2], data[goff + 3]]),
u32::from_be_bytes([
data[goff + 4],
data[goff + 5],
data[goff + 6],
data[goff + 7],
]),
u32::from_be_bytes([
data[goff + 8],
data[goff + 9],
data[goff + 10],
data[goff + 11],
]),
));
}
Some(groups)
}
pub fn cmap_lookup(&self, subtables: &[CmapSubtable], codepoint: u32) -> Option<u16> {
for sub in subtables {
match sub.format {
4 => {
if codepoint > 0xFFFF {
continue;
}
let cp = codepoint as u16;
for &(start, end, delta, range_offset) in &sub.format4_segments {
if cp >= start && cp <= end {
if range_offset == 0 {
let gid = cp.wrapping_add(delta as u16);
if gid != 0 {
return Some(gid);
}
}
}
}
}
12 => {
for &(start, end, start_glyph) in &sub.format12_groups {
if codepoint >= start && codepoint <= end {
let gid = (start_glyph + (codepoint - start)) as u16;
if gid != 0 {
return Some(gid);
}
}
}
}
_ => {}
}
}
None
}
pub fn parse_simple_glyph(
&self,
glyf_data: &[u8],
glyph_offset: usize,
glyph_length: usize,
) -> Option<GlyphOutline> {
if glyph_length < 10 {
return None;
}
let data = &glyf_data[glyph_offset..];
let num_contours = i16::from_be_bytes([data[0], data[1]]);
let x_min = i16::from_be_bytes([data[2], data[3]]);
let y_min = i16::from_be_bytes([data[4], data[5]]);
let x_max = i16::from_be_bytes([data[6], data[7]]);
let y_max = i16::from_be_bytes([data[8], data[9]]);
if num_contours <= 0 {
return Some(GlyphOutline {
contours: Vec::new(),
x_min,
y_min,
x_max,
y_max,
advance_width: 0,
left_side_bearing: 0,
});
}
let num_contours = num_contours as usize;
let mut end_pts = Vec::with_capacity(num_contours);
for i in 0..num_contours {
let off = 10 + i * 2;
end_pts.push(u16::from_be_bytes([data[off], data[off + 1]]) as usize);
}
let total_points = end_pts.last().copied().unwrap_or(0) + 1;
let inst_len_off = 10 + num_contours * 2;
let inst_len = u16::from_be_bytes([data[inst_len_off], data[inst_len_off + 1]]) as usize;
let flags_off = inst_len_off + 2 + inst_len;
let mut flags = Vec::with_capacity(total_points);
let mut pos = flags_off;
for _ in 0..total_points {
if pos >= data.len() {
break;
}
let flag = data[pos];
flags.push(flag);
pos += 1;
if flag & 0x08 != 0 {
if pos >= data.len() {
break;
}
let repeat = data[pos] as usize;
pos += 1;
for _ in 0..repeat {
flags.push(flag);
}
}
}
let mut coords_x = vec![0i16; total_points];
let mut x = 0i16;
let x_off = pos;
for i in 0..total_points.min(flags.len()) {
let flag = flags[i];
if flag & 0x02 != 0 {
if x_off + i < data.len() {
x += if flag & 0x10 != 0 {
data[x_off + i] as i16
} else {
-(data[x_off + i] as i16)
};
}
} else if flag & 0x10 == 0 {
}
coords_x[i] = x;
}
let mut y = 0i16;
let mut coords_y = vec![0i16; total_points];
let y_base = x_off + total_points;
for i in 0..total_points.min(flags.len()) {
let flag = flags[i];
if flag & 0x04 != 0 {
if y_base + i < data.len() {
y += if flag & 0x20 != 0 {
data[y_base + i] as i16
} else {
-(data[y_base + i] as i16)
};
}
}
coords_y[i] = y;
}
let mut contours = Vec::with_capacity(num_contours);
let mut start_idx = 0;
for &end_idx in &end_pts {
let end_idx = end_idx.min(total_points - 1);
let mut points = Vec::with_capacity(end_idx - start_idx + 1);
for i in start_idx..=end_idx {
if i < flags.len() {
points.push(GlyphPoint {
x: coords_x[i],
y: coords_y[i],
on_curve: flags[i] & 0x01 != 0,
});
}
}
contours.push(points);
start_idx = end_idx + 1;
}
Some(GlyphOutline {
contours,
x_min,
y_min,
x_max,
y_max,
advance_width: 0,
left_side_bearing: 0,
})
}
pub fn flatten_quadratic(
&self,
p0: GlyphPoint,
p1: GlyphPoint,
p2: GlyphPoint,
tolerance: f32,
points: &mut Vec<(f32, f32)>,
) {
let p0f = (p0.x as f32, p0.y as f32);
let p1f = (p1.x as f32, p1.y as f32);
let p2f = (p2.x as f32, p2.y as f32);
self.adaptive_quadratic(p0f, p1f, p2f, tolerance, points);
}
fn adaptive_quadratic(
&self,
p0: (f32, f32),
p1: (f32, f32),
p2: (f32, f32),
tolerance: f32,
points: &mut Vec<(f32, f32)>,
) {
let mid = ((p0.0 + p2.0) * 0.5, (p0.1 + p2.1) * 0.5);
let dx = p1.0 - mid.0;
let dy = p1.1 - mid.1;
let dist = (dx * dx + dy * dy).sqrt();
if dist <= tolerance {
points.push(p2);
} else {
let q0 = ((p0.0 + p1.0) * 0.5, (p0.1 + p1.1) * 0.5);
let q1 = ((p1.0 + p2.0) * 0.5, (p1.1 + p2.1) * 0.5);
let r = ((q0.0 + q1.0) * 0.5, (q0.1 + q1.1) * 0.5);
self.adaptive_quadratic(p0, q0, r, tolerance, points);
self.adaptive_quadratic(r, q1, p2, tolerance, points);
}
}
pub fn flatten_cubic(
&self,
p0: (f32, f32),
p1: (f32, f32),
p2: (f32, f32),
p3: (f32, f32),
tolerance: f32,
points: &mut Vec<(f32, f32)>,
) {
self.adaptive_cubic(p0, p1, p2, p3, tolerance, points);
}
fn adaptive_cubic(
&self,
p0: (f32, f32),
p1: (f32, f32),
p2: (f32, f32),
p3: (f32, f32),
tolerance: f32,
points: &mut Vec<(f32, f32)>,
) {
let chord_dx = p3.0 - p0.0;
let chord_dy = p3.1 - p0.1;
let chord_len = (chord_dx * chord_dx + chord_dy * chord_dy).sqrt();
let d1 = if chord_len > EPSILON {
((p1.0 - p0.0) * chord_dy - (p1.1 - p0.1) * chord_dx).abs() / chord_len
} else {
((p1.0 - p0.0).powi(2) + (p1.1 - p0.1).powi(2)).sqrt()
};
let d2 = if chord_len > EPSILON {
((p2.0 - p0.0) * chord_dy - (p2.1 - p0.1) * chord_dx).abs() / chord_len
} else {
((p2.0 - p0.0).powi(2) + (p2.1 - p0.1).powi(2)).sqrt()
};
if d1.max(d2) <= tolerance {
points.push(p3);
} else {
let q0 = ((p0.0 + p1.0) * 0.5, (p0.1 + p1.1) * 0.5);
let q1 = ((p1.0 + p2.0) * 0.5, (p1.1 + p2.1) * 0.5);
let q2 = ((p2.0 + p3.0) * 0.5, (p2.1 + p3.1) * 0.5);
let r0 = ((q0.0 + q1.0) * 0.5, (q0.1 + q1.1) * 0.5);
let r1 = ((q1.0 + q2.0) * 0.5, (q1.1 + q2.1) * 0.5);
let s = ((r0.0 + r1.0) * 0.5, (r0.1 + r1.1) * 0.5);
self.adaptive_cubic(p0, q0, r0, s, tolerance, points);
self.adaptive_cubic(s, r1, q2, p3, tolerance, points);
}
}
pub fn rasterize_glyph_sdf(
&self,
outline: &GlyphOutline,
pixel_size: usize,
spread: usize,
) -> Vec<f32> {
let size = pixel_size + spread * 2;
let mut sdf = vec![f32::MAX; size * size];
if outline.contours.is_empty() {
return sdf;
}
let tolerance = 0.5;
for contour in &outline.contours {
if contour.len() < 2 {
continue;
}
let mut flat_points: Vec<(f32, f32)> = Vec::new();
let mut i = 0;
while i < contour.len() {
let p0 = contour[i];
let p1 = contour[(i + 1) % contour.len()];
if p0.on_curve {
flat_points.push((p0.x as f32, p0.y as f32));
if p1.on_curve {
i += 1;
} else {
let p2 = contour[(i + 2) % contour.len()];
self.flatten_quadratic(p0, p1, p2, tolerance, &mut flat_points);
i += 2;
}
} else if p1.on_curve {
let p_prev = if i == 0 {
contour[contour.len() - 1]
} else {
contour[i - 1]
};
let mid = GlyphPoint {
x: (p0.x + p1.x) / 2,
y: (p0.y + p1.y) / 2,
on_curve: true,
};
flat_points.push((mid.x as f32, mid.y as f32));
i += 1;
} else {
i += 1;
}
}
let scale_x = size as f32 / (outline.x_max - outline.x_min).max(1) as f32;
let scale_y = size as f32 / (outline.y_max - outline.y_min).max(1) as f32;
let scale = scale_x.min(scale_y);
for py in 0..size {
for px in 0..size {
let gx = (px as f32 - spread as f32) / scale + outline.x_min as f32;
let gy = (py as f32 - spread as f32) / scale + (outline.y_max as f32);
let mut min_dist = f32::MAX;
for j in 0..flat_points.len() {
let a = flat_points[j];
let b = if j + 1 < flat_points.len() {
flat_points[j + 1]
} else {
flat_points[0]
};
let dist = Self::point_to_segment_distance(gx, gy, a.0, a.1, b.0, b.1);
if dist < min_dist {
min_dist = dist;
}
}
let sign = if Self::point_in_polygon(gx, gy, &flat_points) {
-1.0
} else {
1.0
};
sdf[py * size + px] = sign * min_dist * scale;
}
}
}
let max_dist = spread as f32;
for v in sdf.iter_mut() {
*v = (*v / max_dist).clamp(-1.0, 1.0);
}
sdf
}
fn point_to_segment_distance(px: f32, py: f32, ax: f32, ay: f32, bx: f32, by: f32) -> f32 {
let dx = bx - ax;
let dy = by - ay;
let len_sq = dx * dx + dy * dy;
if len_sq == 0.0 {
return ((px - ax).powi(2) + (py - ay).powi(2)).sqrt();
}
let t = ((px - ax) * dx + (py - ay) * dy) / len_sq;
let t = t.clamp(0.0, 1.0);
let proj_x = ax + t * dx;
let proj_y = ay + t * dy;
((px - proj_x).powi(2) + (py - proj_y).powi(2)).sqrt()
}
fn point_in_polygon(px: f32, py: f32, polygon: &[(f32, f32)]) -> bool {
let mut inside = false;
let n = polygon.len();
for i in 0..n {
let j = (i + 1) % n;
let (xi, yi) = polygon[i];
let (xj, yj) = polygon[j];
if ((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
inside = !inside;
}
}
inside
}
pub fn shape_text(
&self,
text: &str,
cmap_subtables: &[CmapSubtable],
hmtx: &[HorizontalMetric],
units_per_em: u16,
font_size: f32,
) -> Vec<ShapedGlyph> {
let scale = font_size / units_per_em as f32;
let mut result = Vec::new();
let mut x_cursor = 0.0f32;
for (cluster, ch) in text.chars().enumerate() {
let codepoint = ch as u32;
if let Some(gid) = self.cmap_lookup(cmap_subtables, codepoint) {
let metric = hmtx.get(gid as usize);
let advance = metric
.map(|m| m.advance_width as f32 * scale)
.unwrap_or(0.0);
let lsb = metric
.map(|m| m.left_side_bearing as f32 * scale)
.unwrap_or(0.0);
result.push(ShapedGlyph {
glyph_id: gid,
cluster: cluster as u32,
x_offset: x_cursor + lsb,
y_offset: 0.0,
x_advance: advance,
y_advance: 0.0,
});
x_cursor += advance;
}
}
result
}
pub fn grapheme_clusters(&self, text: &str) -> Vec<&str> {
let mut clusters = Vec::new();
let mut start = 0;
let chars: Vec<char> = text.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '\r' && i + 1 < chars.len() && chars[i + 1] == '\n' {
i += 2;
continue;
}
if i > 0 && Self::is_combining_mark(chars[i]) {
i += 1;
continue;
}
if Self::is_regional_indicator(chars[i])
&& i + 1 < chars.len()
&& Self::is_regional_indicator(chars[i + 1])
{
i += 2;
continue;
}
if i > start {
let cluster: String = chars[start..i].iter().collect();
clusters.push(Box::leak(cluster.into_boxed_str()));
start = i;
}
i += 1;
}
if start < chars.len() {
let cluster: String = chars[start..].iter().collect();
clusters.push(Box::leak(cluster.into_boxed_str()));
}
clusters
}
fn is_combining_mark(ch: char) -> bool {
matches!(
ch,
'\u{0300}'..='\u{036F}' | '\u{1AB0}'..='\u{1AFF}' | '\u{1DC0}'..='\u{1DFF}' | '\u{20D0}'..='\u{20FF}' | '\u{FE00}'..='\u{FE0F}' )
}
fn is_regional_indicator(ch: char) -> bool {
('\u{1F1E6}'..='\u{1F1FF}').contains(&ch)
}
pub fn word_segments(&self, text: &str) -> Vec<&str> {
text.split_whitespace().collect()
}
pub fn line_segments(&self, text: &str) -> Vec<&str> {
text.lines().collect()
}
}
#[derive(Debug, Clone)]
pub struct X86GPUSupport;
impl X86GPUSupport {
pub fn new() -> Self {
Self
}
pub const SPIRV_MAGIC: u32 = 0x07230203;
pub const SPIRV_VERSION_MAJOR: u32 = 1;
pub const SPIRV_VERSION_MINOR: u32 = 6;
pub const SPIRV_VERSION_1_0: u32 = 0x00010000;
pub const SPIRV_VERSION_1_1: u32 = 0x00010100;
pub const SPIRV_VERSION_1_2: u32 = 0x00010200;
pub const SPIRV_VERSION_1_3: u32 = 0x00010300;
pub const SPIRV_VERSION_1_4: u32 = 0x00010400;
pub const SPIRV_VERSION_1_5: u32 = 0x00010500;
pub const SPIRV_VERSION_1_6: u32 = 0x00010600;
pub const SPIRV_GENERATOR_ID: u32 = 0;
pub fn spirv_word_count(opcode: u16) -> usize {
match opcode {
0..=9 => 1, 10..=49 => 4, 50..=79 => 4, 80..=99 => 4, 100..=109 => 4, _ => 1,
}
}
pub fn opencl_builtin_flag(name: &str) -> Option<u32> {
match name {
"get_global_id" => Some(0x0001),
"get_local_id" => Some(0x0002),
"get_group_id" => Some(0x0004),
"get_global_size" => Some(0x0008),
"get_local_size" => Some(0x0010),
"get_num_groups" => Some(0x0020),
"get_work_dim" => Some(0x0040),
"barrier" => Some(0x0080),
"mem_fence" => Some(0x0100),
"read_mem_fence" => Some(0x0200),
"write_mem_fence" => Some(0x0400),
"async_work_group_copy" => Some(0x0800),
"async_work_group_strided_copy" => Some(0x1000),
"wait_group_events" => Some(0x2000),
"prefetch" => Some(0x4000),
_ => None,
}
}
pub fn is_opencl_math_builtin(name: &str) -> bool {
matches!(
name,
"acos"
| "acosh"
| "acospi"
| "asin"
| "asinh"
| "asinpi"
| "atan"
| "atan2"
| "atanh"
| "atanpi"
| "atan2pi"
| "cbrt"
| "ceil"
| "copysign"
| "cos"
| "cosh"
| "cospi"
| "erfc"
| "erf"
| "exp"
| "exp2"
| "exp10"
| "expm1"
| "fabs"
| "fdim"
| "floor"
| "fma"
| "fmax"
| "fmin"
| "fmod"
| "fract"
| "frexp"
| "hypot"
| "ilogb"
| "ldexp"
| "lgamma"
| "log"
| "log2"
| "log10"
| "log1p"
| "logb"
| "mad"
| "maxmag"
| "minmag"
| "modf"
| "nan"
| "nextafter"
| "pow"
| "pown"
| "powr"
| "remainder"
| "remquo"
| "rint"
| "rootn"
| "round"
| "rsqrt"
| "sin"
| "sincos"
| "sinh"
| "sinpi"
| "sqrt"
| "tan"
| "tanh"
| "tanpi"
| "tgamma"
| "trunc"
)
}
pub fn is_opencl_common_builtin(name: &str) -> bool {
matches!(
name,
"clamp"
| "degrees"
| "max"
| "min"
| "mix"
| "radians"
| "step"
| "smoothstep"
| "sign"
)
}
pub fn is_opencl_integer_builtin(name: &str) -> bool {
matches!(
name,
"abs"
| "abs_diff"
| "add_sat"
| "clz"
| "ctz"
| "hadd"
| "mad_hi"
| "mad_sat"
| "mul_hi"
| "popcount"
| "rhadd"
| "rotate"
| "sub_sat"
| "upsample"
)
}
pub fn is_opencl_relational_builtin(name: &str) -> bool {
matches!(
name,
"isequal"
| "isgreater"
| "isgreaterequal"
| "isinf"
| "isfinite"
| "isnan"
| "isless"
| "islessequal"
| "islessgreater"
| "isnormal"
| "isnotequal"
| "isordered"
| "isunordered"
| "signbit"
| "all"
| "any"
| "select"
)
}
pub fn cuda_builtin_flag(name: &str) -> Option<u32> {
match name {
"threadIdx" => Some(0x0001),
"blockIdx" => Some(0x0002),
"blockDim" => Some(0x0004),
"gridDim" => Some(0x0008),
"warpSize" => Some(0x0010),
"__syncthreads" => Some(0x0020),
"__syncwarp" => Some(0x0040),
"__threadfence" => Some(0x0080),
"__threadfence_block" => Some(0x0100),
"__threadfence_system" => Some(0x0200),
"__shfl_sync" => Some(0x0400),
"__shfl_up_sync" => Some(0x0800),
"__shfl_down_sync" => Some(0x1000),
"__shfl_xor_sync" => Some(0x2000),
"__ballot_sync" => Some(0x4000),
"__activemask" => Some(0x8000),
_ => None,
}
}
pub fn cuda_math_builtin(name: &str) -> Option<&'static str> {
match name {
"__cosf" => Some("cosf"),
"__sinf" => Some("sinf"),
"__expf" => Some("expf"),
"__logf" => Some("logf"),
"__sqrtf" => Some("sqrtf"),
"__fabsf" => Some("fabsf"),
"__fmaf_rn" => Some("fmaf"),
"__powf" => Some("powf"),
"__saturatef" => Some("clamp_01"),
"__fmul_rn" => Some("mul"),
"__fadd_rn" => Some("add"),
"__fdiv_rn" => Some("div"),
"__frcp_rn" => Some("recip"),
"__fsqrt_rn" => Some("sqrt"),
_ => None,
}
}
pub const VK_MAX_DESCRIPTOR_SET_BINDINGS: u32 = 4096;
pub const VK_MAX_BOUND_DESCRIPTOR_SETS: u32 = 8;
pub const VK_MAX_PUSH_CONSTANT_SIZE: u32 = 256;
pub const VK_MAX_COLOR_ATTACHMENTS: u32 = 8;
pub const VK_MAX_VIEWPORTS: u32 = 16;
pub const VK_MAX_SAMPLER_ALLOCATION_COUNT: u32 = 4000;
pub const VK_MAX_COMPUTE_WORK_GROUP_INVOCATIONS: u32 = 1024;
pub const VK_MAX_COMPUTE_WORK_GROUP_SIZE_X: u32 = 1024;
pub const VK_MAX_COMPUTE_WORK_GROUP_SIZE_Y: u32 = 1024;
pub const VK_MAX_COMPUTE_WORK_GROUP_SIZE_Z: u32 = 64;
pub fn compute_workgroup_barrier(
&self,
scope: GPUMemoryScope,
semantics: GPUMemorySemantics,
) -> bool {
match scope {
GPUMemoryScope::Workgroup | GPUMemoryScope::Subgroup => true,
GPUMemoryScope::Device => {
if semantics.sequentially_consistent {
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
} else if semantics.acquire || semantics.release {
std::sync::atomic::fence(std::sync::atomic::Ordering::AcqRel);
}
true
}
GPUMemoryScope::System => true,
_ => true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ImageCodecs;
impl<'a> Iterator for PNGChunkIter<'a> {
type Item = PNGChunk;
fn next(&mut self) -> Option<PNGChunk> {
if self.pos + 12 > self.data.len() {
return None;
}
let length = u32::from_be_bytes([
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
]);
let chunk_type = [
self.data[self.pos + 4],
self.data[self.pos + 5],
self.data[self.pos + 6],
self.data[self.pos + 7],
];
let chunk = PNGChunk {
chunk_type,
data_offset: self.pos + 8,
data_length: length,
};
self.pos += 12 + length as usize;
Some(chunk)
}
}
impl X86ImageCodecs {
pub fn new() -> Self {
Self
}
pub fn decode_bmp(&self, data: &[u8]) -> Option<(usize, usize, Vec<u8>)> {
if data.len() < 54 {
return None;
}
let signature = [data[0], data[1]];
if &signature != b"BM" {
return None;
}
let file_size = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
let pixel_offset = u32::from_le_bytes([data[10], data[11], data[12], data[13]]);
let header_size = u32::from_le_bytes([data[14], data[15], data[16], data[17]]);
let width = i32::from_le_bytes([data[18], data[19], data[20], data[21]]) as usize;
let height_abs =
i32::from_le_bytes([data[22], data[23], data[24], data[25]]).unsigned_abs() as usize;
let bit_count = u16::from_le_bytes([data[28], data[29]]);
if bit_count != 24 && bit_count != 32 {
return None;
}
let bytes_per_pixel = (bit_count / 8) as usize;
let row_size = ((bit_count as usize * width + 31) / 32) * 4;
let mut pixels = vec![0u8; width * height_abs * 4];
for y in 0..height_abs {
let row_offset = pixel_offset as usize + y * row_size;
for x in 0..width {
let src_off = row_offset + x * bytes_per_pixel;
let dst_off = (y * width + x) * 4;
if src_off + bytes_per_pixel <= data.len() {
pixels[dst_off + 2] = data[src_off]; pixels[dst_off + 1] = data[src_off + 1]; pixels[dst_off] = data[src_off + 2]; if bytes_per_pixel == 4 {
pixels[dst_off + 3] = data[src_off + 3]; } else {
pixels[dst_off + 3] = 255;
}
}
}
}
Some((width, height_abs, pixels))
}
pub fn encode_bmp(&self, width: usize, height: usize, pixels: &[u8]) -> Vec<u8> {
let row_size = ((24 * width + 31) / 32) * 4;
let pixel_data_size = row_size * height;
let file_size = 54 + pixel_data_size;
let mut data = vec![0u8; file_size];
data[0] = b'B';
data[1] = b'M';
data[2..6].copy_from_slice(&u32::to_le_bytes(file_size as u32));
data[10..14].copy_from_slice(&u32::to_le_bytes(54));
data[14..18].copy_from_slice(&u32::to_le_bytes(40));
data[18..22].copy_from_slice(&i32::to_le_bytes(width as i32));
data[22..26].copy_from_slice(&i32::to_le_bytes(height as i32));
data[26..28].copy_from_slice(&u16::to_le_bytes(1)); data[28..30].copy_from_slice(&u16::to_le_bytes(24)); data[34..38].copy_from_slice(&u32::to_le_bytes(pixel_data_size as u32));
for y in 0..height {
let src_row = (height - 1 - y) * width * 4;
let dst_row = 54 + y * row_size;
for x in 0..width {
let si = src_row + x * 4;
let di = dst_row + x * 3;
data[di] = pixels[si + 2]; data[di + 1] = pixels[si + 1]; data[di + 2] = pixels[si]; }
}
data
}
pub fn parse_png_ihdr(&self, data: &[u8]) -> Option<PNGHeader> {
if data.len() < 13 {
return None;
}
Some(PNGHeader {
width: u32::from_be_bytes([data[0], data[1], data[2], data[3]]),
height: u32::from_be_bytes([data[4], data[5], data[6], data[7]]),
bit_depth: data[8],
color_type: data[9],
compression: data[10],
filter: data[11],
interlace: data[12],
})
}
pub fn validate_png_signature(&self, data: &[u8]) -> bool {
const PNG_SIG: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
data.len() >= 8 && &data[..8] == &PNG_SIG
}
pub fn png_chunks_iter<'a>(&self, data: &'a [u8]) -> PNGChunkIter<'a> {
PNGChunkIter {
data: &data[8..],
pos: 0,
}
}
pub fn png_crc32(&self, data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFFFFFF;
for &byte in data {
crc ^= byte as u32;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
!crc
}
pub fn validate_jpeg_soi(&self, data: &[u8]) -> bool {
data.len() >= 2 && data[0] == 0xFF && data[1] == jpeg_marker::SOI
}
pub fn find_jpeg_markers(&self, data: &[u8]) -> Vec<JPEGMarker> {
let mut markers = Vec::new();
let mut pos = 0;
while pos + 1 < data.len() {
if data[pos] == 0xFF && data[pos + 1] != 0x00 && data[pos + 1] != 0xFF {
let marker = data[pos + 1];
let length = if pos + 3 < data.len() {
u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize
} else {
0
};
markers.push(JPEGMarker {
marker,
offset: pos,
length,
});
if marker == jpeg_marker::SOS {
break;
}
pos += 2 + length;
} else {
pos += 1;
}
}
markers
}
pub fn build_default_luma_dc_huffman(&self) -> JPEGHuffmanTable {
let counts: [u8; 16] = [0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
let symbols: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
JPEGHuffmanTable {
table_class: 0,
table_id: 0,
counts,
symbols,
}
}
pub fn build_default_luma_quant_table(&self) -> JPEGQuantTable {
let values: [u8; 64] = [
16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57,
69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55,
64, 81, 104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100,
103, 99,
];
JPEGQuantTable {
table_id: 0,
precision: 0,
values,
}
}
pub const TIFF_LITTLE_ENDIAN: u16 = 0x4949; pub const TIFF_BIG_ENDIAN: u16 = 0x4D4D; pub const TIFF_MAGIC: u16 = 42;
pub fn parse_tiff_ifd(
&self,
data: &[u8],
offset: usize,
little_endian: bool,
) -> Option<Vec<TIFFIFDEntry>> {
let u16_at = |off: usize| -> u16 {
if off + 1 >= data.len() {
return 0;
}
if little_endian {
u16::from_le_bytes([data[off], data[off + 1]])
} else {
u16::from_be_bytes([data[off], data[off + 1]])
}
};
let u32_at = |off: usize| -> u32 {
if off + 3 >= data.len() {
return 0;
}
if little_endian {
u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
} else {
u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
}
};
let num_entries = u16_at(offset);
let mut entries = Vec::with_capacity(num_entries as usize);
for i in 0..num_entries as usize {
let entry_off = offset + 2 + i * 12;
entries.push(TIFFIFDEntry {
tag: u16_at(entry_off),
field_type: u16_at(entry_off + 2),
count: u32_at(entry_off + 4),
value_offset: u32_at(entry_off + 8),
});
}
Some(entries)
}
pub fn find_tiff_tag<'a>(
&self,
entries: &'a [TIFFIFDEntry],
tag: u16,
) -> Option<&'a TIFFIFDEntry> {
entries.iter().find(|e| e.tag == tag)
}
pub fn validate_tiff_header(&self, data: &[u8]) -> Option<(bool, u32)> {
if data.len() < 8 {
return None;
}
let byte_order = u16::from_le_bytes([data[0], data[1]]);
let little_endian = match byte_order {
Self::TIFF_LITTLE_ENDIAN => true,
Self::TIFF_BIG_ENDIAN => false,
_ => return None,
};
let magic = if little_endian {
u16::from_le_bytes([data[2], data[3]])
} else {
u16::from_be_bytes([data[2], data[3]])
};
if magic != Self::TIFF_MAGIC {
return None;
}
let ifd_offset = if little_endian {
u32::from_le_bytes([data[4], data[5], data[6], data[7]])
} else {
u32::from_be_bytes([data[4], data[5], data[6], data[7]])
};
Some((little_endian, ifd_offset))
}
pub const GIF87A_SIG: [u8; 6] = [b'G', b'I', b'F', b'8', b'7', b'a'];
pub const GIF89A_SIG: [u8; 6] = [b'G', b'I', b'F', b'8', b'9', b'a'];
pub fn parse_gif_screen_descriptor(&self, data: &[u8]) -> Option<GIFScreenDescriptor> {
if data.len() < 7 {
return None;
}
let packed = data[4];
Some(GIFScreenDescriptor {
width: u16::from_le_bytes([data[0], data[1]]),
height: u16::from_le_bytes([data[2], data[3]]),
global_color_table: (packed & 0x80) != 0,
color_resolution: ((packed >> 4) & 0x07) + 1,
sort: (packed & 0x08) != 0,
global_color_table_size: packed & 0x07,
background_color_index: data[5],
pixel_aspect_ratio: data[6],
})
}
pub fn gif_lzw_decompress_stub(&self, compressed: &[u8], min_code_size: u8) -> Vec<u8> {
let _ = compressed;
let _ = min_code_size;
Vec::new()
}
pub const WEBP_RIFF_FOURCC: [u8; 4] = [b'R', b'I', b'F', b'F'];
pub const WEBP_WEBP_FOURCC: [u8; 4] = [b'W', b'E', b'B', b'P'];
pub const WEBP_VP8_FOURCC: [u8; 4] = [b'V', b'P', b'8', b' '];
pub const WEBP_VP8L_FOURCC: [u8; 4] = [b'V', b'P', b'8', b'L'];
pub const WEBP_VP8X_FOURCC: [u8; 4] = [b'V', b'P', b'8', b'X'];
pub const WEBP_ANIM_FOURCC: [u8; 4] = [b'A', b'N', b'I', b'M'];
pub const WEBP_ANMF_FOURCC: [u8; 4] = [b'A', b'N', b'M', b'F'];
pub const WEBP_ICCP_FOURCC: [u8; 4] = [b'I', b'C', b'C', b'P'];
pub const WEBP_EXIF_FOURCC: [u8; 4] = [b'E', b'X', b'I', b'F'];
pub const WEBP_XMP_FOURCC: [u8; 4] = [b'X', b'M', b'P', b' '];
pub fn validate_webp_header(&self, data: &[u8]) -> bool {
data.len() >= 12
&& &data[0..4] == &Self::WEBP_RIFF_FOURCC
&& &data[8..12] == &Self::WEBP_WEBP_FOURCC
}
pub fn detect_webp_format(&self, data: &[u8]) -> Option<&'static [u8; 4]> {
if data.len() < 16 {
return None;
}
let chunk_fourcc = &data[12..16];
if chunk_fourcc == &Self::WEBP_VP8_FOURCC[..] {
Some(&Self::WEBP_VP8_FOURCC)
} else if chunk_fourcc == &Self::WEBP_VP8L_FOURCC[..] {
Some(&Self::WEBP_VP8L_FOURCC)
} else if chunk_fourcc == &Self::WEBP_VP8X_FOURCC[..] {
Some(&Self::WEBP_VP8X_FOURCC)
} else {
None
}
}
pub fn parse_vp8_header_stub(&self, data: &[u8]) -> Option<(u32, u32)> {
if data.len() < 10 {
return None;
}
let key_frame = data[0] & 0x01;
if key_frame == 0 {
return None; }
let w = u16::from_le_bytes([data[6], data[7]]) & 0x3FFF;
let h = u16::from_le_bytes([data[8], data[9]]) & 0x3FFF;
Some((w as u32, h as u32))
}
pub fn parse_vp8l_header_stub(&self, data: &[u8]) -> Option<(u32, u32)> {
if data.len() < 5 {
return None;
}
let signature = data[0];
if signature != 0x2F {
return None;
}
let bits = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
let width = (bits & 0x3FFF) + 1;
let height = ((bits >> 14) & 0x3FFF) + 1;
Some((width, height))
}
pub const QOI_MAGIC: [u8; 4] = [b'q', b'o', b'i', b'f'];
pub fn decode_qoi(&self, data: &[u8]) -> Option<(usize, usize, Vec<u8>)> {
if data.len() < 22 {
return None;
}
if &data[0..4] != &Self::QOI_MAGIC {
return None;
}
let width = u32::from_be_bytes([data[4], data[5], data[6], data[7]]) as usize;
let height = u32::from_be_bytes([data[8], data[9], data[10], data[11]]) as usize;
let channels = data[12];
let _colorspace = data[13];
if width == 0 || height == 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION
{
return None;
}
let mut pixels = vec![0u8; width * height * 4];
let mut index = [[0u8; 4]; 64];
let mut px = QOIPixel {
r: 0,
g: 0,
b: 0,
a: 255,
};
let mut pos = 14;
let mut run = 0u8;
let num_pixels = width * height;
let mut pixel_idx = 0usize;
while pixel_idx < num_pixels && pos < data.len() {
if run > 0 {
run -= 1;
} else if pos >= data.len() {
break;
} else {
let b1 = data[pos];
pos += 1;
if b1 == 0b11111110 {
if pos + 2 > data.len() {
break;
}
px.r = data[pos];
px.g = data[pos + 1];
px.b = data[pos + 2];
pos += 3;
} else if b1 == 0b11111111 {
if pos + 3 > data.len() {
break;
}
px.r = data[pos];
px.g = data[pos + 1];
px.b = data[pos + 2];
px.a = data[pos + 3];
pos += 4;
} else if (b1 & 0b11000000) == 0b00000000 {
let idx = (b1 & 0x3F) as usize;
px.r = index[idx][0];
px.g = index[idx][1];
px.b = index[idx][2];
px.a = index[idx][3];
} else if (b1 & 0b11000000) == 0b01000000 {
px.r = px.r.wrapping_add(((b1 >> 4) & 0x03).wrapping_sub(2));
px.g = px.g.wrapping_add(((b1 >> 2) & 0x03).wrapping_sub(2));
px.b = px.b.wrapping_add((b1 & 0x03).wrapping_sub(2));
} else if (b1 & 0b11000000) == 0b10000000 {
if pos >= data.len() {
break;
}
let b2 = data[pos];
pos += 1;
let dg = (b1 & 0x3F).wrapping_sub(32);
let dr_dg = ((b2 >> 4) & 0x0F).wrapping_sub(8);
let db_dg = (b2 & 0x0F).wrapping_sub(8);
px.r = px.r.wrapping_add(dg.wrapping_add(dr_dg));
px.g = px.g.wrapping_add(dg);
px.b = px.b.wrapping_add(dg.wrapping_add(db_dg));
} else if (b1 & 0b11000000) == 0b11000000 {
run = b1 & 0x3F;
}
let idx_pos = (px.r as usize * 3
+ px.g as usize * 5
+ px.b as usize * 7
+ px.a as usize * 11)
% 64;
index[idx_pos] = [px.r, px.g, px.b, px.a];
}
let di = pixel_idx * 4;
pixels[di] = px.r;
pixels[di + 1] = px.g;
pixels[di + 2] = px.b;
pixels[di + 3] = if channels == 3 { 255 } else { px.a };
pixel_idx += 1;
}
if pixel_idx < num_pixels {
return None;
}
Some((width, height, pixels))
}
pub fn encode_qoi(&self, width: usize, height: usize, pixels: &[u8], channels: u8) -> Vec<u8> {
let header_size = 14;
let end_size = 8; let max_size = header_size + width * height * 5 + end_size;
let mut data = Vec::with_capacity(max_size);
data.extend_from_slice(&Self::QOI_MAGIC);
data.extend_from_slice(&(width as u32).to_be_bytes());
data.extend_from_slice(&(height as u32).to_be_bytes());
data.push(channels);
data.push(0);
let mut index = [[0u8; 4]; 64];
let mut prev = QOIPixel {
r: 0,
g: 0,
b: 0,
a: 255,
};
let mut run = 0u8;
let num_pixels = width * height;
let has_alpha = channels == 4;
for i in 0..num_pixels {
let si = i * 4;
let px = QOIPixel {
r: pixels[si],
g: pixels[si + 1],
b: pixels[si + 2],
a: if has_alpha { pixels[si + 3] } else { 255 },
};
if px.r == prev.r && px.g == prev.g && px.b == prev.b && px.a == prev.a {
run += 1;
if run == 62 || i == num_pixels - 1 {
data.push(0b11000000 | (run - 1));
run = 0;
}
} else {
if run > 0 {
data.push(0b11000000 | (run - 1));
run = 0;
}
let idx_pos = (px.r as usize * 3
+ px.g as usize * 5
+ px.b as usize * 7
+ px.a as usize * 11)
% 64;
if index[idx_pos] == [px.r, px.g, px.b, px.a] {
data.push(idx_pos as u8);
} else if px.a == prev.a {
let dr = px.r.wrapping_sub(prev.r).wrapping_add(2);
let dg = px.g.wrapping_sub(prev.g).wrapping_add(2);
let db = px.b.wrapping_sub(prev.b).wrapping_add(2);
if dr < 4 && dg < 4 && db < 4 {
data.push(0b01000000 | (dr << 4) | (dg << 2) | db);
} else {
let dg2 = px.g.wrapping_sub(prev.g).wrapping_add(32);
let drdg =
px.r.wrapping_sub(prev.r)
.wrapping_sub(dg2.wrapping_sub(32))
.wrapping_add(8);
let dbdg =
px.b.wrapping_sub(prev.b)
.wrapping_sub(dg2.wrapping_sub(32))
.wrapping_add(8);
if dg2 < 64 && drdg < 16 && dbdg < 16 {
data.push(0b10000000 | dg2);
data.push((drdg << 4) | dbdg);
} else {
data.push(0b11111110);
data.push(px.r);
data.push(px.g);
data.push(px.b);
}
}
} else {
data.push(0b11111111);
data.push(px.r);
data.push(px.g);
data.push(px.b);
data.push(px.a);
}
index[idx_pos] = [px.r, px.g, px.b, px.a];
}
prev = px;
}
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
data
}
pub fn detect_format(&self, data: &[u8]) -> ImageFormat {
if data.len() < 2 {
return ImageFormat::Unknown;
}
if data.len() >= 2 && &data[0..2] == b"BM" {
ImageFormat::BMP
} else if data.len() >= 8 && self.validate_png_signature(data) {
ImageFormat::PNG
} else if self.validate_jpeg_soi(data) {
ImageFormat::JPEG
} else if data.len() >= 4 && (&data[0..4] == b"II\x2A\x00" || &data[0..4] == b"MM\x00\x2A")
{
ImageFormat::TIFF
} else if data.len() >= 6
&& (&data[0..6] == &Self::GIF87A_SIG || &data[0..6] == &Self::GIF89A_SIG)
{
ImageFormat::GIF
} else if self.validate_webp_header(data) {
ImageFormat::WebP
} else if data.len() >= 4 && &data[0..4] == &Self::QOI_MAGIC {
ImageFormat::QOI
} else {
ImageFormat::Unknown
}
}
pub fn format_name(&self, format: ImageFormat) -> &'static str {
match format {
ImageFormat::BMP => "BMP",
ImageFormat::PNG => "PNG",
ImageFormat::JPEG => "JPEG",
ImageFormat::TIFF => "TIFF",
ImageFormat::GIF => "GIF",
ImageFormat::WebP => "WebP",
ImageFormat::QOI => "QOI",
ImageFormat::Unknown => "Unknown",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_x86_graphics_creation() {
let gfx = X86Graphics::new();
assert!(gfx.capabilities().vector_width_f32 >= 1);
}
#[test]
fn test_x86_graphics_with_simd_level() {
let gfx = X86Graphics::with_simd_level(X86SIMDGraphicsLevel::SSE2);
assert_eq!(gfx.simd_level, X86SIMDGraphicsLevel::SSE2);
assert_eq!(gfx.capabilities().vector_width_f32, 4);
}
#[test]
fn test_x86_graphics_capabilities_display() {
let gfx = X86Graphics::new();
let caps = gfx.capabilities();
let s = format!("{}", caps);
assert!(s.contains("X86Graphics"));
assert!(s.contains("simd"));
}
#[test]
fn test_x86_graphics_default() {
let gfx = X86Graphics::default();
assert!(gfx.capabilities().has_fma || !gfx.capabilities().has_fma); }
#[test]
fn test_simd_level_ordering() {
assert!(X86SIMDGraphicsLevel::AVX512 > X86SIMDGraphicsLevel::AVX2);
assert!(X86SIMDGraphicsLevel::AVX2 > X86SIMDGraphicsLevel::SSE42);
assert!(X86SIMDGraphicsLevel::SSE42 > X86SIMDGraphicsLevel::SSE2);
assert!(X86SIMDGraphicsLevel::SSE2 > X86SIMDGraphicsLevel::Scalar);
}
#[test]
fn test_simd_level_vector_widths() {
assert_eq!(X86SIMDGraphicsLevel::Scalar.vector_width_f32(), 1);
assert_eq!(X86SIMDGraphicsLevel::SSE2.vector_width_f32(), 4);
assert_eq!(X86SIMDGraphicsLevel::AVX2.vector_width_f32(), 8);
assert_eq!(X86SIMDGraphicsLevel::AVX512.vector_width_f32(), 16);
}
#[test]
fn test_simd_level_vector_bytes() {
assert_eq!(X86SIMDGraphicsLevel::SSE2.vector_bytes(), 16);
assert_eq!(X86SIMDGraphicsLevel::AVX2.vector_bytes(), 32);
assert_eq!(X86SIMDGraphicsLevel::AVX512.vector_bytes(), 64);
}
#[test]
fn test_pixel_format_bytes_per_pixel() {
assert_eq!(PixelFormat::R8.bytes_per_pixel(), 1);
assert_eq!(PixelFormat::R8G8B8A8.bytes_per_pixel(), 4);
assert_eq!(PixelFormat::R32G32B32A32F.bytes_per_pixel(), 16);
assert_eq!(PixelFormat::R16F.bytes_per_pixel(), 2);
assert_eq!(PixelFormat::R16G16B16A16F.bytes_per_pixel(), 8);
}
#[test]
fn test_pixel_format_channels() {
assert_eq!(PixelFormat::R8.channels(), 1);
assert_eq!(PixelFormat::R8G8B8.channels(), 3);
assert_eq!(PixelFormat::R8G8B8A8.channels(), 4);
}
#[test]
fn test_pixel_format_is_float() {
assert!(PixelFormat::R16F.is_float());
assert!(PixelFormat::R32G32B32A32F.is_float());
assert!(!PixelFormat::R8G8B8A8.is_float());
}
#[test]
fn test_pixel_format_names() {
assert_eq!(PixelFormat::R8G8B8A8.name(), "R8G8B8A8_UNORM");
assert_eq!(PixelFormat::B8G8R8A8.name(), "B8G8R8A8_UNORM");
assert_eq!(PixelFormat::R32G32B32A32F.name(), "R32G32B32A32_FLOAT");
}
#[test]
fn test_pack_rgba_f32_to_u32() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let packed = gfx.pack_rgba_f32_to_u32(1.0, 0.5, 0.0, 1.0);
assert_eq!(packed & 0xFF, 255); assert_eq!((packed >> 8) & 0xFF, 128); assert_eq!((packed >> 16) & 0xFF, 0); assert_eq!((packed >> 24) & 0xFF, 255); }
#[test]
fn test_unpack_rgba_u32_to_f32() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let (r, g, b, a) = gfx.unpack_rgba_u32_to_f32(0xFF804000);
assert!((r - 0.0).abs() < 0.01);
assert!((g - 0.25).abs() < 0.01);
assert!((b - 0.5).abs() < 0.01);
assert!((a - 1.0).abs() < 0.01);
}
#[test]
fn test_pack_unpack_roundtrip() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let original = gfx.pack_rgba_f32_to_u32(0.2, 0.4, 0.6, 0.8);
let (r, g, b, a) = gfx.unpack_rgba_u32_to_f32(original);
assert!((r - 0.2).abs() < 0.01);
assert!((g - 0.4).abs() < 0.01);
assert!((b - 0.6).abs() < 0.01);
assert!((a - 0.8).abs() < 0.01);
}
#[test]
fn test_premultiply_alpha() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [255u8, 128, 64, 128]; gfx.premultiply_alpha(&mut pixels, PixelFormat::R8G8B8A8);
assert!((pixels[0] as i32 - 128).abs() <= 1);
assert!((pixels[1] as i32 - 64).abs() <= 1);
assert_eq!(pixels[3], 128); }
#[test]
fn test_unpremultiply_alpha() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [128u8, 64, 32, 128];
gfx.unpremultiply_alpha(&mut pixels, PixelFormat::R8G8B8A8);
assert!((pixels[0] as i32 - 255).abs() <= 2);
assert_eq!(pixels[3], 128);
}
#[test]
fn test_scale_rgba() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [100u8, 150, 200, 255];
gfx.scale_rgba(&mut pixels, 0.5);
assert_eq!(pixels[0], 50);
assert_eq!(pixels[1], 75);
assert_eq!(pixels[2], 100);
assert_eq!(pixels[3], 255); }
#[test]
fn test_extract_alpha() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let rgba = [10, 20, 30, 100, 40, 50, 60, 200];
let mut alpha = [0u8; 2];
gfx.extract_alpha(&rgba, &mut alpha);
assert_eq!(alpha[0], 100);
assert_eq!(alpha[1], 200);
}
#[test]
fn test_set_alpha() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut rgba = [10, 20, 30, 100, 40, 50, 60, 200];
gfx.set_alpha(&mut rgba, 255);
assert_eq!(rgba[3], 255);
assert_eq!(rgba[7], 255);
}
#[test]
fn test_rgba8_to_bgra8() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [10, 20, 30, 255, 40, 50, 60, 128];
gfx.rgba8_to_bgra8(&mut pixels);
assert_eq!(pixels[0], 30); assert_eq!(pixels[2], 10); assert_eq!(pixels[4], 60); assert_eq!(pixels[6], 40); }
#[test]
fn test_rgb8_to_bgr8() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [10, 20, 30, 40, 50, 60];
gfx.rgb8_to_bgr8(&mut pixels);
assert_eq!(pixels[0], 30);
assert_eq!(pixels[2], 10);
assert_eq!(pixels[3], 60);
assert_eq!(pixels[5], 40);
}
#[test]
fn test_rgb8_to_rgba8() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [10, 20, 30, 40, 50, 60];
let mut dst = [0u8; 8];
gfx.rgb8_to_rgba8(&src, &mut dst);
assert_eq!(dst[0], 10);
assert_eq!(dst[1], 20);
assert_eq!(dst[2], 30);
assert_eq!(dst[3], 255); assert_eq!(dst[4], 40);
assert_eq!(dst[5], 50);
assert_eq!(dst[6], 60);
assert_eq!(dst[7], 255);
}
#[test]
fn test_rgba8_to_rgb8() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [10, 20, 30, 255, 40, 50, 60, 128];
let mut dst = [0u8; 6];
gfx.rgba8_to_rgb8(&src, &mut dst);
assert_eq!(dst, [10, 20, 30, 40, 50, 60]);
}
#[test]
fn test_float_rgba_to_u8_rgba() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [1.0f32, 0.5, 0.0, 1.0];
let mut dst = [0u8; 4];
gfx.float_rgba_to_u8_rgba(&src, &mut dst);
assert_eq!(dst[0], 255);
assert_eq!(dst[1], 128);
assert_eq!(dst[2], 0);
assert_eq!(dst[3], 255);
}
#[test]
fn test_u8_rgba_to_float_rgba() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [255u8, 128, 0, 255];
let mut dst = [0.0f32; 4];
gfx.u8_rgba_to_float_rgba(&src, &mut dst);
assert!((dst[0] - 1.0).abs() < 0.01);
assert!((dst[1] - 0.5).abs() < 0.01);
assert!((dst[2] - 0.0).abs() < 0.01);
assert!((dst[3] - 1.0).abs() < 0.01);
}
#[test]
fn test_resize_nearest() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![255u8; 4 * 4 * 4]; let mut dst = vec![0u8; 8 * 8 * 4];
gfx.resize_rgba8(&src, 4, 4, &mut dst, 8, 8, ResizeFilter::Nearest);
assert_eq!(dst[0], 255);
assert_eq!(dst[3], 255);
assert_eq!(dst[7 * 8 * 4], 255);
}
#[test]
fn test_resize_bilinear() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut src = vec![0u8; 2 * 2 * 4];
src[0] = 255;
src[1] = 255;
src[2] = 255;
src[3] = 255;
let mut dst = vec![0u8; 4 * 4 * 4];
gfx.resize_rgba8(&src, 2, 2, &mut dst, 4, 4, ResizeFilter::Bilinear);
assert_eq!(dst[0], 255);
assert!(dst[12] < 255); }
#[test]
fn test_resize_bicubic() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![128u8; 4 * 4 * 4];
let mut dst = vec![0u8; 2 * 2 * 4];
gfx.resize_rgba8(&src, 4, 4, &mut dst, 2, 2, ResizeFilter::Bicubic);
assert_eq!(dst[0], 128);
}
#[test]
fn test_rotate_90() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![1, 2, 3, 255, 4, 5, 6, 255];
let mut dst = vec![0u8; 8];
gfx.rotate_rgba8(&src, 1, 2, &mut dst, 90);
assert_eq!(dst[0], 1);
assert_eq!(dst[1], 2);
assert_eq!(dst[2], 3);
assert_eq!(dst[3], 255);
assert_eq!(dst[4], 4);
assert_eq!(dst[5], 5);
assert_eq!(dst[6], 6);
assert_eq!(dst[7], 255);
}
#[test]
fn test_rotate_180() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![10, 20, 30, 255, 40, 50, 60, 255];
let mut dst = vec![0u8; 8];
gfx.rotate_rgba8(&src, 2, 1, &mut dst, 180);
assert_eq!(dst[0], 40);
assert_eq!(dst[1], 50);
}
#[test]
fn test_flip_horizontal() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![10, 20, 30, 255, 40, 50, 60, 128];
gfx.flip_horizontal(&mut pixels, 2, 1);
assert_eq!(pixels[0], 40);
assert_eq!(pixels[1], 50);
assert_eq!(pixels[2], 60);
assert_eq!(pixels[3], 128);
assert_eq!(pixels[4], 10);
assert_eq!(pixels[5], 20);
assert_eq!(pixels[6], 30);
assert_eq!(pixels[7], 255);
}
#[test]
fn test_flip_vertical() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![
10, 20, 30, 255, 40, 50, 60, 128, 70, 80, 90, 200, 100, 110, 120, 64,
];
gfx.flip_vertical(&mut pixels, 1, 4);
assert_eq!(pixels[0], 100);
assert_eq!(pixels[12], 10);
}
#[test]
fn test_crop_rgba8() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![1, 2, 3, 255, 4, 5, 6, 255, 7, 8, 9, 255, 10, 11, 12, 255];
let mut dst = vec![0u8; 4];
gfx.crop_rgba8(&src, 2, 0, 1, 1, 1, &mut dst);
assert_eq!(dst[0], 7);
assert_eq!(dst[1], 8);
assert_eq!(dst[2], 9);
assert_eq!(dst[3], 255);
}
#[test]
fn test_pad_rgba8() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![10, 20, 30, 255];
let mut dst = vec![0u8; 9 * 4]; gfx.pad_rgba8(&src, 1, 1, 1, 1, 1, 1, [0, 0, 0, 0], &mut dst);
assert_eq!(dst[4 * 4], 10); assert_eq!(dst[4 * 4 + 3], 255);
assert_eq!(dst[0], 0);
}
#[test]
fn test_box_blur() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![100u8; 3 * 3 * 4];
let mut dst = vec![0u8; 3 * 3 * 4];
gfx.box_blur_rgba8(&src, &mut dst, 3, 3, 1);
assert_eq!(dst[0], 100);
assert_eq!(dst[4], 100);
}
#[test]
fn test_gaussian_blur() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut src = vec![0u8; 5 * 5 * 4];
src[2 * 5 * 4 + 2 * 4] = 255;
src[2 * 5 * 4 + 2 * 4 + 1] = 255;
src[2 * 5 * 4 + 2 * 4 + 2] = 255;
let mut dst = vec![0u8; 5 * 5 * 4];
gfx.gaussian_blur_rgba8(&src, &mut dst, 5, 5, 1.0);
assert!(dst[2 * 5 * 4 + 2 * 4] > 0);
}
#[test]
fn test_median_filter() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut src = vec![10u8; 3 * 3 * 4];
src[1 * 3 * 4 + 1 * 4] = 200; src[1 * 3 * 4 + 1 * 4 + 1] = 200;
src[1 * 3 * 4 + 1 * 4 + 2] = 200;
let mut dst = vec![0u8; 3 * 3 * 4];
gfx.median_filter_rgba8(&src, &mut dst, 3, 3, 1);
assert_eq!(dst[1 * 3 * 4 + 1 * 4], 10);
}
#[test]
fn test_sharpen_laplacian() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![128u8; 5 * 5 * 4];
let mut dst = vec![0u8; 5 * 5 * 4];
gfx.sharpen_laplacian(&src, &mut dst, 5, 5);
assert_eq!(dst[2 * 5 * 4 + 2 * 4], 128);
}
#[test]
fn test_blend_over() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [255, 0, 0, 128]; let dst = [0, 255, 0, 255]; let result = gfx.blend_pixel(src, dst, BlendMode::Over);
assert!(result[0] > result[1]); }
#[test]
fn test_blend_add() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [100, 50, 25, 255];
let dst = [50, 100, 150, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Add);
assert_eq!(result[0], 150);
assert_eq!(result[1], 150);
assert_eq!(result[2], 175);
}
#[test]
fn test_blend_multiply() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [128, 128, 128, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Multiply);
assert!((result[0] as i32 - 64).abs() <= 1); }
#[test]
fn test_blend_screen() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [128, 128, 128, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Screen);
assert!(result[0] > 128); }
#[test]
fn test_blend_difference() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [200, 100, 50, 255];
let dst = [100, 200, 150, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Difference);
assert_eq!(result[0], 100); assert_eq!(result[1], 100); assert_eq!(result[2], 100); }
#[test]
fn test_blend_darken() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [100, 200, 100, 255];
let dst = [200, 100, 100, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Darken);
assert_eq!(result[0], 100);
assert_eq!(result[1], 100);
}
#[test]
fn test_blend_lighten() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [100, 200, 100, 255];
let dst = [200, 100, 100, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Lighten);
assert_eq!(result[0], 200);
assert_eq!(result[1], 200);
}
#[test]
fn test_blend_images() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![128, 128, 128, 255, 64, 64, 64, 128];
let mut dst = vec![64, 64, 64, 255, 192, 192, 192, 255];
gfx.blend_images(&src, &mut dst, 2, 1, BlendMode::Add);
assert_eq!(dst[0], 192);
assert_eq!(dst[1], 192);
}
#[test]
fn test_dither_floyd_steinberg() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![128u8; 4 * 4 * 4];
gfx.dither_rgba8(&mut pixels, 4, 4, 2, DitherKind::FloydSteinberg);
for i in 0..4 * 4 {
for c in 0..3 {
let val = pixels[i * 4 + c];
assert!(
val == 0 || val == 255,
"Value {} at pixel {} channel {}",
val,
i,
c
);
}
}
}
#[test]
fn test_dither_ordered() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![128u8; 8 * 8 * 4];
gfx.dither_rgba8(&mut pixels, 8, 8, 2, DitherKind::Ordered);
let mut found_zero = false;
let mut found_255 = false;
for chunk in pixels.chunks(4) {
if chunk[0] == 0 {
found_zero = true;
}
if chunk[0] == 255 {
found_255 = true;
}
}
assert!(found_zero);
assert!(found_255);
}
#[test]
fn test_dither_atkinson() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![128u8; 4 * 4 * 4];
gfx.dither_rgba8(&mut pixels, 4, 4, 2, DitherKind::Atkinson);
for i in 0..4 * 4 {
for c in 0..3 {
assert!(pixels[i * 4 + c] == 0 || pixels[i * 4 + c] == 255);
}
}
}
#[test]
fn test_brightness_contrast() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [100u8, 150, 200, 255];
gfx.brightness_contrast(&mut pixels, 10.0, 1.0);
assert!(pixels[0] > 100); }
#[test]
fn test_gamma_correct() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = [128u8, 128, 128, 255];
gfx.gamma_correct(&mut pixels, 2.2);
assert!(pixels[0] < 128);
}
#[test]
fn test_dot_product() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.dot(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]), 32.0);
}
#[test]
fn test_dot_product_f64() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.dot_f64(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]), 32.0);
}
#[test]
fn test_mm_dp_ps() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si._mm_dp_ps([1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]);
assert_eq!(r[0], 70.0);
assert_eq!(r[3], 70.0); }
#[test]
fn test_mm256_dp_ps() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::AVX2);
let a = [1.0; 8];
let b = [2.0; 8];
let r = si._mm256_dp_ps(a, b);
assert_eq!(r[0], 16.0);
}
#[test]
fn test_cross3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.cross3([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert_eq!(r, [0.0, 0.0, 1.0]);
}
#[test]
fn test_cross3_f64() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.cross3_f64([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert_eq!(r, [0.0, 0.0, 1.0]);
}
#[test]
fn test_normalize3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.normalize3([3.0, 0.0, 0.0]);
assert!((r[0] - 1.0).abs() < 0.001);
}
#[test]
fn test_normalize3_zero() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.normalize3([0.0, 0.0, 0.0]);
assert_eq!(r, [0.0, 0.0, 0.0]);
}
#[test]
fn test_length3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert!((si.length3([3.0, 4.0, 0.0]) - 5.0).abs() < 0.001);
}
#[test]
fn test_lerp() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.lerp(0.0, 10.0, 0.5), 5.0);
assert_eq!(si.lerp(0.0, 10.0, 0.0), 0.0);
assert_eq!(si.lerp(0.0, 10.0, 1.0), 10.0);
}
#[test]
fn test_lerp3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.lerp3([0.0, 0.0, 0.0], [10.0, 20.0, 30.0], 0.5);
assert_eq!(r, [5.0, 10.0, 15.0]);
}
#[test]
fn test_smoothstep() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.smoothstep(0.0, 1.0, -0.5), 0.0);
assert_eq!(si.smoothstep(0.0, 1.0, 0.5), 0.5);
assert_eq!(si.smoothstep(0.0, 1.0, 1.5), 1.0);
}
#[test]
fn test_step() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.step(0.5, 0.3), 0.0);
assert_eq!(si.step(0.5, 0.8), 1.0);
}
#[test]
fn test_clamp() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.clamp(0.5, 0.0, 1.0), 0.5);
assert_eq!(si.clamp(-1.0, 0.0, 1.0), 0.0);
assert_eq!(si.clamp(2.0, 0.0, 1.0), 1.0);
}
#[test]
fn test_saturate() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
assert_eq!(si.saturate(0.5), 0.5);
assert_eq!(si.saturate(-1.0), 0.0);
assert_eq!(si.saturate(2.0), 1.0);
}
#[test]
fn test_reflect() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let i = si.normalize3([1.0, -1.0, 0.0]);
let n = [0.0, 1.0, 0.0];
let r = si.reflect(i, n);
assert!((r[0] - (2.0f32.sqrt() / 2.0)).abs() < 0.01);
assert!(r[1] > 0.0);
}
#[test]
fn test_refract() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let i = [0.0, -1.0, 0.0];
let n = [0.0, 1.0, 0.0];
let r = si.refract(i, n, 0.75);
assert!(r[1] < 0.0); }
#[test]
fn test_faceforward() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let n = [0.0, 1.0, 0.0];
let i = [0.0, -1.0, 0.0]; let nref = [0.0, 1.0, 0.0];
let r = si.faceforward(n, i, nref);
assert_eq!(r, n); }
#[test]
fn test_mat4_mul_mat4() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let a = si.mat4_identity();
let b = si.mat4_identity();
let r = si.mat4_mul_mat4(&a, &b);
assert_eq!(r, si.mat4_identity());
}
#[test]
fn test_mat4_mul_vec4() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let m = si.mat4_identity();
let v = [1.0, 2.0, 3.0, 4.0];
let r = si.mat4_mul_vec4(&m, &v);
assert_eq!(r, v);
}
#[test]
fn test_mat4_transpose() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let m = [
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
];
let t = si.mat4_transpose(&m);
assert_eq!(t[1], 5.0);
assert_eq!(t[4], 2.0);
}
#[test]
fn test_mat4_determinant_identity() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let det = si.mat4_determinant(&si.mat4_identity());
assert!((det - 1.0).abs() < EPSILON);
}
#[test]
fn test_mat4_inverse_identity() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let inv = si.mat4_inverse(&si.mat4_identity());
assert_eq!(inv, si.mat4_identity());
}
#[test]
fn test_mat4_mul_point3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let m = si.mat4_identity();
let r = si.mat4_mul_point3(&m, &[2.0, 3.0, 4.0]);
assert_eq!(r, [2.0, 3.0, 4.0]);
}
#[test]
fn test_fresnel_schlick() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.fresnel_schlick(0.0, [0.04, 0.04, 0.04]);
assert_eq!(r, [1.0, 1.0, 1.0]);
let r = si.fresnel_schlick(1.0, [0.04, 0.04, 0.04]);
assert_eq!(r, [0.04, 0.04, 0.04]);
}
#[test]
fn test_aabb_create() {
let aabb = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
assert_eq!(aabb.center(), [0.5, 0.5, 0.5]);
}
#[test]
fn test_aabb_intersects() {
let a = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
let b = X86GeometryOps::AABB::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0]);
assert!(a.intersects(&b));
}
#[test]
fn test_aabb_no_intersect() {
let a = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = X86GeometryOps::AABB::new([2.0, 2.0, 2.0], [3.0, 3.0, 3.0]);
assert!(!a.intersects(&b));
}
#[test]
fn test_aabb_contains_point() {
let aabb = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
assert!(aabb.contains_point(&[0.5, 0.5, 0.5]));
assert!(!aabb.contains_point(&[1.5, 0.5, 0.5]));
}
#[test]
fn test_aabb_intersection() {
let geo = X86GeometryOps::new();
let a = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
let b = X86GeometryOps::AABB::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0]);
let isect = geo.aabb_intersection(&a, &b);
assert!(isect.is_some());
let i = isect.unwrap();
assert_eq!(i.min, [1.0, 1.0, 1.0]);
assert_eq!(i.max, [2.0, 2.0, 2.0]);
}
#[test]
fn test_ray_triangle_intersect() {
let geo = X86GeometryOps::new();
let ray = X86GeometryOps::Ray::new([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]);
let v0 = [-1.0, -1.0, 0.0];
let v1 = [1.0, -1.0, 0.0];
let v2 = [0.0, 1.0, 0.0];
let t = geo.ray_triangle_intersect(&ray, v0, v1, v2);
assert!(t.is_some());
assert!((t.unwrap() - 1.0).abs() < 0.001);
}
#[test]
fn test_ray_triangle_miss() {
let geo = X86GeometryOps::new();
let ray = X86GeometryOps::Ray::new([5.0, 0.0, -1.0], [0.0, 0.0, 1.0]);
let v0 = [-1.0, -1.0, 0.0];
let v1 = [1.0, -1.0, 0.0];
let v2 = [0.0, 1.0, 0.0];
assert!(geo.ray_triangle_intersect(&ray, v0, v1, v2).is_none());
}
#[test]
fn test_ray_sphere_intersect() {
let geo = X86GeometryOps::new();
let ray = X86GeometryOps::Ray::new([0.0, 0.0, -5.0], [0.0, 0.0, 1.0]);
let t = geo.ray_sphere_intersect(&ray, [0.0, 0.0, 0.0], 1.0);
assert!(t.is_some());
assert!((t.unwrap() - 4.0).abs() < 0.01);
}
#[test]
fn test_ray_aabb_intersect() {
let geo = X86GeometryOps::new();
let ray = X86GeometryOps::Ray::new([0.5, 0.5, -1.0], [0.0, 0.0, 1.0]);
let aabb = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let result = geo.ray_aabb_intersect(&ray, &aabb);
assert!(result.is_some());
let (tmin, _) = result.unwrap();
assert!((tmin - 1.0).abs() < 0.01);
}
#[test]
fn test_sphere_frustum_inside() {
let geo = X86GeometryOps::new();
let vp = geo.shader.mat4_identity(); let frustum = X86GeometryOps::Frustum::from_view_projection(&vp);
assert!(geo.sphere_frustum_test(&frustum, [0.0, 0.0, 0.0], 0.5));
}
#[test]
fn test_aabb_frustum_inside() {
let geo = X86GeometryOps::new();
let vp = geo.shader.mat4_identity();
let frustum = X86GeometryOps::Frustum::from_view_projection(&vp);
let aabb = X86GeometryOps::AABB::new([-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]);
assert!(geo.aabb_frustum_test(&frustum, &aabb));
}
#[test]
fn test_translate() {
let geo = X86GeometryOps::new();
let m = geo.translate(1.0, 2.0, 3.0);
let p = geo.shader.mat4_mul_point3(&m, &[0.0, 0.0, 0.0]);
assert_eq!(p, [1.0, 2.0, 3.0]);
}
#[test]
fn test_rotate_x() {
let geo = X86GeometryOps::new();
let m = geo.rotate_x(std::f32::consts::PI / 2.0);
let p = geo.shader.mat4_mul_point3(&m, &[0.0, 1.0, 0.0]);
assert!((p[0] - 0.0).abs() < 0.001);
assert!((p[1] - 0.0).abs() < 0.001);
assert!((p[2] - 1.0).abs() < 0.001);
}
#[test]
fn test_rotate_y() {
let geo = X86GeometryOps::new();
let m = geo.rotate_y(std::f32::consts::PI / 2.0);
let p = geo.shader.mat4_mul_point3(&m, &[1.0, 0.0, 0.0]);
assert!((p[2] - (-1.0)).abs() < 0.001);
}
#[test]
fn test_rotate_z() {
let geo = X86GeometryOps::new();
let m = geo.rotate_z(std::f32::consts::PI / 2.0);
let p = geo.shader.mat4_mul_point3(&m, &[1.0, 0.0, 0.0]);
assert!((p[0] - 0.0).abs() < 0.001);
assert!((p[1] - 1.0).abs() < 0.001);
}
#[test]
fn test_scale() {
let geo = X86GeometryOps::new();
let m = geo.scale(2.0, 3.0, 4.0);
let p = geo.shader.mat4_mul_point3(&m, &[1.0, 1.0, 1.0]);
assert_eq!(p, [2.0, 3.0, 4.0]);
}
#[test]
fn test_look_at() {
let geo = X86GeometryOps::new();
let eye = [0.0, 0.0, 5.0];
let center = [0.0, 0.0, 0.0];
let up = [0.0, 1.0, 0.0];
let view = geo.look_at(eye, center, up);
let p = geo.shader.mat4_mul_point3(&view, &eye);
assert!((p[0] - 0.0).abs() < 0.01);
assert!((p[1] - 0.0).abs() < 0.01);
assert!((p[2] - 0.0).abs() < 0.01);
}
#[test]
fn test_perspective() {
let geo = X86GeometryOps::new();
let proj = geo.perspective(std::f32::consts::PI / 2.0, 1.0, 0.1, 100.0);
assert!((proj[11] - (-1.0)).abs() < 0.001);
}
#[test]
fn test_ortho() {
let geo = X86GeometryOps::new();
let ortho = geo.ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
let p = geo.shader.mat4_mul_point3(&ortho, &[0.0, 0.0, 0.0]);
assert_eq!(p, [0.0, 0.0, 0.0]);
}
#[test]
fn test_quaternion_identity() {
let q = X86GeometryOps::Quaternion::identity();
assert_eq!(q.w, 1.0);
assert_eq!(q.x, 0.0);
}
#[test]
fn test_quaternion_axis_angle() {
let q = X86GeometryOps::Quaternion::from_axis_angle(
[0.0, 0.0, 1.0],
std::f32::consts::PI / 2.0,
);
let v = q.rotate_vector(&[1.0, 0.0, 0.0]);
assert!((v[0] - 0.0).abs() < 0.001);
assert!((v[1] - 1.0).abs() < 0.001);
}
#[test]
fn test_quaternion_slerp() {
let a = X86GeometryOps::Quaternion::identity();
let b = X86GeometryOps::Quaternion::from_axis_angle([0.0, 0.0, 1.0], std::f32::consts::PI);
let mid = X86GeometryOps::Quaternion::slerp(&a, &b, 0.5);
assert!(mid.w > 0.0);
assert!(mid.z > 0.0);
}
#[test]
fn test_quaternion_nlerp() {
let a = X86GeometryOps::Quaternion::identity();
let b = X86GeometryOps::Quaternion::from_axis_angle(
[1.0, 0.0, 0.0],
std::f32::consts::PI / 2.0,
);
let mid = a.nlerp(&b, 0.5);
let mag = mid.magnitude();
assert!((mag - 1.0).abs() < 0.001);
}
#[test]
fn test_quaternion_to_mat4() {
let q = X86GeometryOps::Quaternion::identity();
let m = q.to_mat4();
assert_eq!(m, {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
si.mat4_identity()
});
}
#[test]
fn test_quaternion_conjugate() {
let q = X86GeometryOps::Quaternion {
x: 1.0,
y: 2.0,
z: 3.0,
w: 4.0,
};
let c = q.conjugate();
assert_eq!(c.x, -1.0);
assert_eq!(c.w, 4.0);
}
#[test]
fn test_bounding_sphere() {
let geo = X86GeometryOps::new();
let points = [
[-1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
];
let (center, radius) = geo.bounding_sphere(&points);
assert!((center[0] - 0.0).abs() < 0.01);
assert!(radius >= 1.0);
}
#[test]
fn test_srgb_to_linear() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.srgb_to_linear(0.5, 0.5, 0.5);
assert!(r < 0.5);
assert!(r > 0.0);
}
#[test]
fn test_linear_to_srgb() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.linear_to_srgb(0.2, 0.2, 0.2);
assert!(r > 0.2);
}
#[test]
fn test_srgb_linear_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (lr, lg, lb) = cs.srgb_to_linear(0.5, 0.3, 0.8);
let (rr, rg, rb) = cs.linear_to_srgb(lr, lg, lb);
assert!((rr - 0.5).abs() < 0.01);
assert!((rg - 0.3).abs() < 0.01);
assert!((rb - 0.8).abs() < 0.01);
}
#[test]
fn test_srgb8_to_linear_f32() {
let cs = X86ColorSpaceOps::new();
let pixels = [128u8, 64, 32, 255];
let mut out = [0.0f32; 4];
cs.srgb8_to_linear_f32(&pixels, &mut out);
assert!(out[0] < 0.5);
assert!(out[1] < out[0]);
}
#[test]
fn test_rgb_to_hsv_red() {
let cs = X86ColorSpaceOps::new();
let (h, s, v) = cs.rgb_to_hsv(1.0, 0.0, 0.0);
assert!((h - 0.0).abs() < 1.0 || (h - 360.0).abs() < 1.0);
assert!((s - 1.0).abs() < 0.01);
assert!((v - 1.0).abs() < 0.01);
}
#[test]
fn test_rgb_to_hsv_green() {
let cs = X86ColorSpaceOps::new();
let (h, _, _) = cs.rgb_to_hsv(0.0, 1.0, 0.0);
assert!((h - 120.0).abs() < 1.0);
}
#[test]
fn test_rgb_to_hsv_blue() {
let cs = X86ColorSpaceOps::new();
let (h, _, _) = cs.rgb_to_hsv(0.0, 0.0, 1.0);
assert!((h - 240.0).abs() < 1.0);
}
#[test]
fn test_hsv_to_rgb_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.hsv_to_rgb(180.0, 0.5, 0.8);
let (h, s, v) = cs.rgb_to_hsv(r, g, b);
assert!((h - 180.0).abs() < 1.0);
assert!((s - 0.5).abs() < 0.02);
assert!((v - 0.8).abs() < 0.01);
}
#[test]
fn test_rgb_to_hsl_white() {
let cs = X86ColorSpaceOps::new();
let (h, s, l) = cs.rgb_to_hsl(1.0, 1.0, 1.0);
assert_eq!(s, 0.0);
assert_eq!(l, 1.0);
}
#[test]
fn test_hsl_to_rgb_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.hsl_to_rgb(90.0, 0.7, 0.6);
let (h, s, l) = cs.rgb_to_hsl(r, g, b);
assert!((h - 90.0).abs() < 1.0);
assert!((s - 0.7).abs() < 0.02);
assert!((l - 0.6).abs() < 0.02);
}
#[test]
fn test_rgb_to_yuv() {
let cs = X86ColorSpaceOps::new();
let (y, _, _) = cs.rgb_to_yuv(1.0, 1.0, 1.0);
assert!((y - 1.0).abs() < 0.01);
}
#[test]
fn test_yuv_to_rgb_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (y, u, v) = cs.rgb_to_yuv(0.5, 0.3, 0.8);
let (r, g, b) = cs.yuv_to_rgb(y, u, v);
assert!((r - 0.5).abs() < 0.01);
assert!((g - 0.3).abs() < 0.01);
assert!((b - 0.8).abs() < 0.01);
}
#[test]
fn test_rgb_to_ycbcr_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (y, cb, cr) = cs.rgb_to_ycbcr(0.5, 0.3, 0.8);
let (r, g, b) = cs.ycbcr_to_rgb(y, cb, cr);
assert!((r - 0.5).abs() < 0.01);
}
#[test]
fn test_rgb_to_xyz() {
let cs = X86ColorSpaceOps::new();
let (x, y, z) = cs.rgb_to_xyz(1.0, 1.0, 1.0);
assert!((x - 0.9505).abs() < 0.01);
assert!((y - 1.0).abs() < 0.01);
assert!((z - 1.089).abs() < 0.01);
}
#[test]
fn test_xyz_to_rgb_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (x, y, z) = cs.rgb_to_xyz(0.5, 0.3, 0.8);
let (r, g, b) = cs.xyz_to_rgb(x, y, z);
assert!((r - 0.5).abs() < 0.01);
assert!((g - 0.3).abs() < 0.01);
assert!((b - 0.8).abs() < 0.01);
}
#[test]
fn test_xyz_to_lab() {
let cs = X86ColorSpaceOps::new();
let (l, a, b) = cs.xyz_to_lab(0.95047, 1.0, 1.08883);
assert!((l - 100.0).abs() < 0.1);
assert!((a - 0.0).abs() < 0.1);
assert!((b - 0.0).abs() < 0.1);
}
#[test]
fn test_lab_to_xyz_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (l, a_val, b_val) = cs.xyz_to_lab(0.5, 0.3, 0.4);
let (x, y, z) = cs.lab_to_xyz(l, a_val, b_val);
assert!((x - 0.5).abs() < 0.01);
assert!((y - 0.3).abs() < 0.01);
}
#[test]
fn test_lab_to_lch() {
let cs = X86ColorSpaceOps::new();
let (l, c, h) = cs.lab_to_lch(50.0, 30.0, 40.0);
assert!(c > 0.0);
assert!(h >= 0.0 && h < 360.0);
}
#[test]
fn test_lch_to_lab_roundtrip() {
let cs = X86ColorSpaceOps::new();
let (l, a, b) = cs.lch_to_lab(50.0, 50.0, 53.0);
let (l2, c, h) = cs.lab_to_lch(l, a, b);
assert!((l2 - 50.0).abs() < 0.1);
assert!((c - 50.0).abs() < 0.1);
assert!((h - 53.0).abs() < 1.0);
}
#[test]
fn test_kelvin_to_rgb_6500() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.kelvin_to_rgb(6500.0);
assert!((r - 1.0).abs() < 0.1);
assert!((g - 1.0).abs() < 0.1);
assert!((b - 1.0).abs() < 0.1);
}
#[test]
fn test_kelvin_to_rgb_3000() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.kelvin_to_rgb(3000.0);
assert!(r > b);
}
#[test]
fn test_tonemap_reinhard() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.tonemap_reinhard(1.0, 1.0, 1.0);
assert!((r - 0.5).abs() < 0.01);
}
#[test]
fn test_tonemap_aces() {
let cs = X86ColorSpaceOps::new();
let (r, _, _) = cs.tonemap_aces(0.5, 0.5, 0.5);
assert!(r >= 0.0 && r <= 1.0);
}
#[test]
fn test_tonemap_hable() {
let cs = X86ColorSpaceOps::new();
let (r, _, _) = cs.tonemap_hable(0.5, 0.5, 0.5);
assert!(r >= 0.0 && r <= 1.0);
}
#[test]
fn test_tonemap_agx() {
let cs = X86ColorSpaceOps::new();
let (r, _, _) = cs.tonemap_agx(0.5, 0.5, 0.5);
assert!(r >= 0.0 && r <= 1.0);
}
#[test]
fn test_f16_zero() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(0.0);
let unpacked = cs.f16_to_f32(packed);
assert_eq!(unpacked, 0.0);
}
#[test]
fn test_f16_one() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(1.0);
let unpacked = cs.f16_to_f32(packed);
assert!((unpacked - 1.0).abs() < 0.001);
}
#[test]
fn test_f16_half() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(0.5);
let unpacked = cs.f16_to_f32(packed);
assert!((unpacked - 0.5).abs() < 0.001);
}
#[test]
fn test_f16_negative() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(-2.0);
let unpacked = cs.f16_to_f32(packed);
assert!((unpacked + 2.0).abs() < 0.01);
}
#[test]
fn test_f16_roundtrip_array() {
let cs = X86ColorSpaceOps::new();
let src = [1.0f32, 0.5, 0.25, 0.125];
let mut f16 = [0u16; 4];
cs.rgba_f32_to_f16(&src, &mut f16);
let mut dst = [0.0f32; 4];
cs.rgba_f16_to_f32(&f16, &mut dst);
for i in 0..4 {
assert!((dst[i] - src[i]).abs() < 0.01);
}
}
#[test]
fn test_rgbe_encode_decode_roundtrip() {
let cs = X86ColorSpaceOps::new();
let encoded = cs.rgbe_encode(1.0, 0.5, 0.25);
let (r, g, b) = cs.rgbe_decode(encoded);
assert!((r - 1.0).abs() < 0.02);
assert!((g - 0.5).abs() < 0.02);
assert!((b - 0.25).abs() < 0.02);
}
#[test]
fn test_rgbe_encode_zero() {
let cs = X86ColorSpaceOps::new();
let encoded = cs.rgbe_encode(0.0, 0.0, 0.0);
assert_eq!(encoded, [0, 0, 0, 0]);
}
#[test]
fn test_luminance() {
let cs = X86ColorSpaceOps::new();
let l = cs.luminance(1.0, 1.0, 1.0);
assert!((l - 1.0).abs() < 0.01);
}
#[test]
fn test_contrast_ratio() {
let cs = X86ColorSpaceOps::new();
let cr = cs.contrast_ratio(1.0, 0.0);
assert!((cr - 21.0).abs() < 0.1); }
#[test]
fn test_parse_empty_ttf() {
let fr = X86FontRendering::new();
assert!(fr.parse_ttf_table_directory(&[]).is_none());
}
#[test]
fn test_parse_minimal_ttf_directory() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 12];
data[0] = 0;
data[1] = 1; data[4] = 0;
data[5] = 0; let tables = fr.parse_ttf_table_directory(&data);
assert!(tables.is_some());
assert_eq!(tables.unwrap().len(), 0);
}
#[test]
fn test_find_table() {
let fr = X86FontRendering::new();
let tables = vec![X86FontRendering::TTFTableRecord {
tag: *b"head",
checksum: 0,
offset: 0,
length: 0,
}];
let found = fr.find_table(&tables, b"head");
assert!(found.is_some());
let not_found = fr.find_table(&tables, b"hhea");
assert!(not_found.is_none());
}
#[test]
fn test_parse_head_table() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 54];
data[18] = 0x04;
data[19] = 0x00; let head = fr.parse_head_table(&data, 0);
assert!(head.is_some());
assert_eq!(head.unwrap().units_per_em, 1024);
}
#[test]
fn test_parse_hhea_table() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 36];
data[4] = 0x03;
data[5] = 0xE8; data[6] = 0xFF;
data[7] = 0x9C; let hhea = fr.parse_hhea_table(&data, 0);
assert!(hhea.is_some());
let h = hhea.unwrap();
assert_eq!(h.ascent, 1000);
assert_eq!(h.descent, -100);
}
#[test]
fn test_parse_hmtx_table() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 8];
data[0] = 0x01;
data[1] = 0xF4; data[2] = 0x00;
data[3] = 0x32; data[4] = 0x02;
data[5] = 0x58; data[6] = 0x00;
data[7] = 0x00; let metrics = fr.parse_hmtx_table(&data, 0, 2, 2);
assert_eq!(metrics.len(), 2);
assert_eq!(metrics[0].advance_width, 500);
assert_eq!(metrics[0].left_side_bearing, 50);
}
#[test]
fn test_parse_loca_short() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 6]; data[0] = 0x00;
data[1] = 0x00; data[2] = 0x00;
data[3] = 0x0A; data[4] = 0x00;
data[5] = 0x14; let offsets = fr.parse_loca_table(&data, 0, 2, 0);
assert_eq!(offsets.len(), 3);
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 20);
assert_eq!(offsets[2], 40);
}
#[test]
fn test_parse_cmap_table_empty() {
let fr = X86FontRendering::new();
let data = [0u8; 4];
let subs = fr.parse_cmap_table(&data, 0);
assert_eq!(subs.len(), 0);
}
#[test]
fn test_flatten_quadratic() {
let fr = X86FontRendering::new();
let p0 = X86FontRendering::GlyphPoint {
x: 0,
y: 0,
on_curve: true,
};
let p1 = X86FontRendering::GlyphPoint {
x: 50,
y: 100,
on_curve: false,
};
let p2 = X86FontRendering::GlyphPoint {
x: 100,
y: 0,
on_curve: true,
};
let mut points = Vec::new();
points.push((0.0, 0.0));
fr.flatten_quadratic(p0, p1, p2, 1.0, &mut points);
assert!(points.len() >= 2);
let last = points.last().unwrap();
assert!((last.0 - 100.0).abs() < 1.0);
assert!((last.1 - 0.0).abs() < 1.0);
}
#[test]
fn test_flatten_cubic() {
let fr = X86FontRendering::new();
let mut points = Vec::new();
points.push((0.0, 0.0));
fr.flatten_cubic(
(0.0, 0.0),
(33.0, 66.0),
(66.0, 66.0),
(100.0, 0.0),
1.0,
&mut points,
);
assert!(points.len() >= 2);
}
#[test]
fn test_rasterize_sdf_empty() {
let fr = X86FontRendering::new();
let outline = X86FontRendering::GlyphOutline {
contours: vec![],
x_min: 0,
y_min: 0,
x_max: 0,
y_max: 0,
advance_width: 0,
left_side_bearing: 0,
};
let sdf = fr.rasterize_glyph_sdf(&outline, 32, 4);
assert_eq!(sdf.len(), 40 * 40);
}
#[test]
fn test_word_segments() {
let fr = X86FontRendering::new();
let words = fr.word_segments("hello world test");
assert_eq!(words.len(), 3);
assert_eq!(words[0], "hello");
}
#[test]
fn test_line_segments() {
let fr = X86FontRendering::new();
let lines = fr.line_segments("line1\nline2\nline3");
assert_eq!(lines.len(), 3);
assert_eq!(lines[1], "line2");
}
#[test]
fn test_grapheme_clusters_simple() {
let fr = X86FontRendering::new();
let clusters = fr.grapheme_clusters("abc");
assert_eq!(clusters.len(), 3);
}
#[test]
fn test_shape_text() {
let fr = X86FontRendering::new();
let cmap = vec![X86FontRendering::CmapSubtable {
platform_id: 3,
encoding_id: 1,
format: 4,
format4_segments: vec![(65, 65, -64, 0)],
format12_groups: vec![],
}];
let hmtx = vec![
X86FontRendering::HorizontalMetric {
advance_width: 0,
left_side_bearing: 0,
},
X86FontRendering::HorizontalMetric {
advance_width: 600,
left_side_bearing: 50,
},
];
let glyphs = fr.shape_text("A", &cmap, &hmtx, 1000, 12.0);
assert_eq!(glyphs.len(), 1);
assert_eq!(glyphs[0].glyph_id, 1);
}
#[test]
fn test_spirv_version_constants() {
assert_eq!(X86GPUSupport::SPIRV_VERSION_1_6, 0x00010600);
assert_eq!(X86GPUSupport::SPIRV_VERSION_1_0, 0x00010000);
}
#[test]
fn test_spirv_addressing_model() {
assert_eq!(X86GPUSupport::spirv_addressing_model::LOGICAL, 0);
assert_eq!(X86GPUSupport::spirv_addressing_model::PHYSICAL64, 2);
}
#[test]
fn test_spirv_execution_models() {
assert_eq!(X86GPUSupport::spirv_execution_model::VERTEX, 0);
assert_eq!(X86GPUSupport::spirv_execution_model::FRAGMENT, 4);
assert_eq!(X86GPUSupport::spirv_execution_model::GL_COMPUTE, 5);
assert_eq!(X86GPUSupport::spirv_execution_model::RAY_GENERATION, 5313);
}
#[test]
fn test_spirv_storage_classes() {
assert_eq!(X86GPUSupport::spirv_storage_class::UNIFORM, 2);
assert_eq!(X86GPUSupport::spirv_storage_class::STORAGE_BUFFER, 12);
}
#[test]
fn test_opencl_builtin_flags() {
assert_eq!(
X86GPUSupport::opencl_builtin_flag("get_global_id"),
Some(0x0001)
);
assert_eq!(X86GPUSupport::opencl_builtin_flag("barrier"), Some(0x0080));
assert_eq!(X86GPUSupport::opencl_builtin_flag("nonexistent"), None);
}
#[test]
fn test_is_opencl_math_builtin() {
assert!(X86GPUSupport::is_opencl_math_builtin("sin"));
assert!(X86GPUSupport::is_opencl_math_builtin("exp2"));
assert!(X86GPUSupport::is_opencl_math_builtin("fma"));
assert!(!X86GPUSupport::is_opencl_math_builtin("foobar"));
}
#[test]
fn test_is_opencl_common_builtin() {
assert!(X86GPUSupport::is_opencl_common_builtin("clamp"));
assert!(X86GPUSupport::is_opencl_common_builtin("mix"));
assert!(!X86GPUSupport::is_opencl_common_builtin("sin"));
}
#[test]
fn test_is_opencl_integer_builtin() {
assert!(X86GPUSupport::is_opencl_integer_builtin("popcount"));
assert!(X86GPUSupport::is_opencl_integer_builtin("clz"));
assert!(!X86GPUSupport::is_opencl_integer_builtin("sin"));
}
#[test]
fn test_is_opencl_relational_builtin() {
assert!(X86GPUSupport::is_opencl_relational_builtin("isnan"));
assert!(X86GPUSupport::is_opencl_relational_builtin("select"));
assert!(!X86GPUSupport::is_opencl_relational_builtin("popcount"));
}
#[test]
fn test_cuda_builtin_flags() {
assert_eq!(X86GPUSupport::cuda_builtin_flag("threadIdx"), Some(0x0001));
assert_eq!(
X86GPUSupport::cuda_builtin_flag("__syncthreads"),
Some(0x0020)
);
assert_eq!(
X86GPUSupport::cuda_builtin_flag("__activemask"),
Some(0x8000)
);
}
#[test]
fn test_cuda_math_builtin() {
assert_eq!(X86GPUSupport::cuda_math_builtin("__cosf"), Some("cosf"));
assert_eq!(X86GPUSupport::cuda_math_builtin("__fmaf_rn"), Some("fmaf"));
assert_eq!(
X86GPUSupport::cuda_math_builtin("__saturatef"),
Some("clamp_01")
);
assert_eq!(X86GPUSupport::cuda_math_builtin("unknown"), None);
}
#[test]
fn test_spirv_builtin_values() {
assert_eq!(X86GPUSupport::spirv_builtin::POSITION, 0);
assert_eq!(X86GPUSupport::spirv_builtin::FRAG_COORD, 15);
assert_eq!(X86GPUSupport::spirv_builtin::GLOBAL_INVOCATION_ID, 28);
assert_eq!(X86GPUSupport::spirv_builtin::SUBGROUP_SIZE, 36);
}
#[test]
fn test_vulkan_limit_constants() {
assert_eq!(X86GPUSupport::VK_MAX_BOUND_DESCRIPTOR_SETS, 8);
assert_eq!(X86GPUSupport::VK_MAX_PUSH_CONSTANT_SIZE, 256);
assert_eq!(X86GPUSupport::VK_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, 1024);
}
#[test]
fn test_gpu_memory_scope_spirv() {
assert_eq!(GPUMemoryScope::Subgroup.spirv_scope(), 3);
assert_eq!(GPUMemoryScope::Workgroup.spirv_scope(), 2);
assert_eq!(GPUMemoryScope::Device.spirv_scope(), 1);
assert_eq!(GPUMemoryScope::System.spirv_scope(), 4);
}
#[test]
fn test_gpu_memory_scope_opencl() {
assert_eq!(GPUMemoryScope::Workgroup.opencl_scope(), 0x1);
assert_eq!(GPUMemoryScope::Device.opencl_scope(), 0x2);
}
#[test]
fn test_gpu_memory_scope_cuda() {
assert_eq!(GPUMemoryScope::Workgroup.cuda_scope(), 1);
assert_eq!(GPUMemoryScope::Device.cuda_scope(), 2);
}
#[test]
fn test_memory_semantics_spirv() {
let relaxed = GPUMemorySemantics::relaxed();
assert_eq!(relaxed.spirv_semantics(), 0);
let acq_rel = GPUMemorySemantics::acq_rel();
assert_eq!(acq_rel.spirv_semantics(), 0x6); let sc = GPUMemorySemantics::seq_cst();
assert_eq!(sc.spirv_semantics(), 0x10);
}
#[test]
fn test_compute_workgroup_barrier() {
let gpu = X86GPUSupport::new();
assert!(gpu
.compute_workgroup_barrier(GPUMemoryScope::Workgroup, GPUMemorySemantics::relaxed(),));
}
#[test]
fn test_decode_bmp_minimal() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 54 + 3];
data[0] = b'B';
data[1] = b'M';
data[2..6].copy_from_slice(&u32::to_le_bytes((54 + 3) as u32));
data[10..14].copy_from_slice(&u32::to_le_bytes(54));
data[14..18].copy_from_slice(&u32::to_le_bytes(40));
data[18..22].copy_from_slice(&i32::to_le_bytes(1));
data[22..26].copy_from_slice(&i32::to_le_bytes(1));
data[26..28].copy_from_slice(&u16::to_le_bytes(1));
data[28..30].copy_from_slice(&u16::to_le_bytes(24));
data[54] = 255;
data[55] = 128;
data[56] = 64;
let result = codecs.decode_bmp(&data);
assert!(result.is_some());
let (w, h, pixels) = result.unwrap();
assert_eq!(w, 1);
assert_eq!(h, 1);
assert_eq!(pixels[0], 64); assert_eq!(pixels[1], 128); assert_eq!(pixels[2], 255); assert_eq!(pixels[3], 255); }
#[test]
fn test_encode_bmp() {
let codecs = X86ImageCodecs::new();
let pixels = [255u8, 128, 64, 255];
let bmp = codecs.encode_bmp(1, 1, &pixels);
assert!(bmp.len() >= 54);
assert_eq!(&bmp[0..2], b"BM");
}
#[test]
fn test_bmp_roundtrip() {
let codecs = X86ImageCodecs::new();
let pixels = vec![100u8, 150, 200, 255, 50, 100, 150, 255];
let bmp = codecs.encode_bmp(2, 1, &pixels);
let (w, h, decoded) = codecs.decode_bmp(&bmp).unwrap();
assert_eq!(w, 2);
assert_eq!(h, 1);
assert_eq!(decoded[0], 100);
assert_eq!(decoded[1], 150);
assert_eq!(decoded[2], 200);
}
#[test]
fn test_validate_png_signature() {
let codecs = X86ImageCodecs::new();
let png_sig = [137, 80, 78, 71, 13, 10, 26, 10];
assert!(codecs.validate_png_signature(&png_sig));
assert!(!codecs.validate_png_signature(&[0u8; 8]));
}
#[test]
fn test_parse_png_ihdr() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 13];
data[0] = 0x00;
data[1] = 0x00;
data[2] = 0x01;
data[3] = 0x00; data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x01;
data[7] = 0x00; data[8] = 8; data[9] = 6; let hdr = codecs.parse_png_ihdr(&data);
assert!(hdr.is_some());
let h = hdr.unwrap();
assert_eq!(h.width, 256);
assert_eq!(h.height, 256);
assert_eq!(h.color_type, 6);
}
#[test]
fn test_png_crc32() {
let codecs = X86ImageCodecs::new();
let crc = codecs.png_crc32(b"IHDR");
assert_ne!(crc, 0);
}
#[test]
fn test_png_chunks_iter() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 8]; data[0] = 137;
data[1] = 80;
data[2] = 78;
data[3] = 71;
data[4] = 13;
data[5] = 10;
data[6] = 26;
data[7] = 10;
data.extend_from_slice(&[0, 0, 0, 0]); data.extend_from_slice(b"IEND");
data.extend_from_slice(&[0u8; 4]);
let chunks: Vec<_> = codecs.png_chunks_iter(&data).collect();
assert_eq!(chunks.len(), 1);
assert_eq!(&chunks[0].chunk_type, b"IEND");
}
#[test]
fn test_validate_jpeg_soi() {
let codecs = X86ImageCodecs::new();
assert!(codecs.validate_jpeg_soi(&[0xFF, 0xD8]));
assert!(!codecs.validate_jpeg_soi(&[0x00, 0x00]));
}
#[test]
fn test_find_jpeg_markers() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0xFF, 0xD8]; data.extend_from_slice(&[0xFF, 0xE0, 0x00, 0x10]); data.extend_from_slice(&[0u8; 14]); data.extend_from_slice(&[0xFF, 0xD9]); let markers = codecs.find_jpeg_markers(&data);
assert!(markers.len() >= 2);
assert_eq!(markers[0].marker, 0xD8);
}
#[test]
fn test_build_default_luma_dc_huffman() {
let codecs = X86ImageCodecs::new();
let table = codecs.build_default_luma_dc_huffman();
assert_eq!(table.table_class, 0);
assert_eq!(table.table_id, 0);
assert!(!table.symbols.is_empty());
}
#[test]
fn test_build_default_luma_quant_table() {
let codecs = X86ImageCodecs::new();
let table = codecs.build_default_luma_quant_table();
assert_eq!(table.table_id, 0);
assert_eq!(table.values[0], 16); }
#[test]
fn test_validate_tiff_header_little() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 8];
data[0] = 0x49;
data[1] = 0x49; data[2] = 42;
data[3] = 0; data[4] = 8;
data[5] = 0;
data[6] = 0;
data[7] = 0; let result = codecs.validate_tiff_header(&data);
assert!(result.is_some());
let (le, offset) = result.unwrap();
assert!(le);
assert_eq!(offset, 8);
}
#[test]
fn test_parse_tiff_ifd() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 14];
data[0] = 1;
data[1] = 0; data[2] = 0x00;
data[3] = 0x01; data[4] = 0x00;
data[5] = 0x03; data[6] = 0x01;
data[7] = 0x00;
data[8] = 0x00;
data[9] = 0x00; data[10] = 0x20;
data[11] = 0x03;
data[12] = 0x00;
data[13] = 0x00; let entries = codecs.parse_tiff_ifd(&data, 0, true);
assert!(entries.is_some());
let e = &entries.unwrap();
assert_eq!(e.len(), 1);
assert_eq!(e[0].tag, 256);
}
#[test]
fn test_find_tiff_tag() {
let codecs = X86ImageCodecs::new();
let entries = vec![X86ImageCodecs::TIFFIFDEntry {
tag: 256,
field_type: 3,
count: 1,
value_offset: 800,
}];
let found = codecs.find_tiff_tag(&entries, 256);
assert!(found.is_some());
assert_eq!(found.unwrap().value_offset, 800);
}
#[test]
fn test_parse_gif_screen_descriptor() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 7];
data[0] = 0x40;
data[1] = 0x01; data[2] = 0xF0;
data[3] = 0x00; data[4] = 0xF7; let sd = codecs.parse_gif_screen_descriptor(&data);
assert!(sd.is_some());
let s = sd.unwrap();
assert_eq!(s.width, 320);
assert_eq!(s.height, 240);
assert!(s.global_color_table);
}
#[test]
fn test_gif_lzw_decompress_stub() {
let codecs = X86ImageCodecs::new();
let result = codecs.gif_lzw_decompress_stub(&[0u8; 10], 8);
assert!(result.is_empty());
}
#[test]
fn test_validate_webp_header() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 16];
data[0..4].copy_from_slice(b"RIFF");
data[4..8].copy_from_slice(&100u32.to_le_bytes());
data[8..12].copy_from_slice(b"WEBP");
data[12..16].copy_from_slice(b"VP8 ");
assert!(codecs.validate_webp_header(&data));
}
#[test]
fn test_detect_webp_format() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 16];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WEBP");
data[12..16].copy_from_slice(b"VP8 ");
assert_eq!(codecs.detect_webp_format(&data).unwrap(), b"VP8 ");
}
#[test]
fn test_parse_vp8_header_stub() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 10];
data[0] = 0x01; data[6] = 0x40;
data[7] = 0x01; data[8] = 0xF0;
data[9] = 0x00; let result = codecs.parse_vp8_header_stub(&data);
assert!(result.is_some());
assert_eq!(result.unwrap(), (320, 240));
}
#[test]
fn test_parse_vp8l_header_stub() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 5];
data[0] = 0x2F; data[1] = 0x3F;
data[2] = 0x00;
data[3] = 0x00;
data[4] = 0xF0;
let result = codecs.parse_vp8l_header_stub(&data);
assert!(result.is_some());
}
#[test]
fn test_qoi_encode_decode_roundtrip_rgba() {
let codecs = X86ImageCodecs::new();
let width = 4;
let height = 4;
let mut pixels = vec![0u8; width * height * 4];
for y in 0..height {
for x in 0..width {
let i = (y * width + x) * 4;
pixels[i] = (x * 64) as u8;
pixels[i + 1] = (y * 64) as u8;
pixels[i + 2] = 128;
pixels[i + 3] = 255;
}
}
let encoded = codecs.encode_qoi(width, height, &pixels, 4);
let result = codecs.decode_qoi(&encoded);
assert!(result.is_some());
let (w, h, decoded) = result.unwrap();
assert_eq!(w, width);
assert_eq!(h, height);
assert_eq!(decoded.len(), pixels.len());
for i in 0..pixels.len() {
assert_eq!(decoded[i], pixels[i], "Mismatch at index {}", i);
}
}
#[test]
fn test_qoi_encode_decode_roundtrip_rgb() {
let codecs = X86ImageCodecs::new();
let pixels = vec![255u8, 128, 64, 0, 10, 50, 100, 0]; let encoded = codecs.encode_qoi(2, 1, &pixels, 3);
let (w, h, decoded) = codecs.decode_qoi(&encoded).unwrap();
assert_eq!(w, 2);
assert_eq!(h, 1);
assert_eq!(decoded[0], 255);
assert_eq!(decoded[1], 128);
assert_eq!(decoded[2], 64);
assert_eq!(decoded[3], 255); assert_eq!(decoded[4], 10);
assert_eq!(decoded[5], 50);
assert_eq!(decoded[6], 100);
assert_eq!(decoded[7], 255);
}
#[test]
fn test_qoi_solid_color() {
let codecs = X86ImageCodecs::new();
let pixels = vec![255u8, 0, 0, 255]; let encoded = codecs.encode_qoi(1, 1, &pixels, 4);
let (w, h, decoded) = codecs.decode_qoi(&encoded).unwrap();
assert_eq!(w, 1);
assert_eq!(h, 1);
assert_eq!(decoded[0], 255);
assert_eq!(decoded[1], 0);
assert_eq!(decoded[2], 0);
assert_eq!(decoded[3], 255);
}
#[test]
fn test_qoi_decode_invalid() {
let codecs = X86ImageCodecs::new();
assert!(codecs.decode_qoi(&[]).is_none());
assert!(codecs.decode_qoi(&[0u8; 10]).is_none());
}
#[test]
fn test_detect_format_bmp() {
let codecs = X86ImageCodecs::new();
assert_eq!(codecs.detect_format(b"BM"), ImageFormat::BMP);
}
#[test]
fn test_detect_format_png() {
let codecs = X86ImageCodecs::new();
let png_sig = [137, 80, 78, 71, 13, 10, 26, 10];
assert_eq!(codecs.detect_format(&png_sig), ImageFormat::PNG);
}
#[test]
fn test_detect_format_jpeg() {
let codecs = X86ImageCodecs::new();
assert_eq!(codecs.detect_format(&[0xFF, 0xD8]), ImageFormat::JPEG);
}
#[test]
fn test_detect_format_gif() {
let codecs = X86ImageCodecs::new();
assert_eq!(codecs.detect_format(b"GIF89a"), ImageFormat::GIF);
}
#[test]
fn test_detect_format_webp() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 16];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WEBP");
data[12..16].copy_from_slice(b"VP8 ");
assert_eq!(codecs.detect_format(&data), ImageFormat::WebP);
}
#[test]
fn test_detect_format_qoi() {
let codecs = X86ImageCodecs::new();
assert_eq!(codecs.detect_format(b"qoif"), ImageFormat::QOI);
}
#[test]
fn test_detect_format_unknown() {
let codecs = X86ImageCodecs::new();
assert_eq!(codecs.detect_format(&[0x00, 0x00]), ImageFormat::Unknown);
}
#[test]
fn test_format_name() {
let codecs = X86ImageCodecs::new();
assert_eq!(codecs.format_name(ImageFormat::PNG), "PNG");
assert_eq!(codecs.format_name(ImageFormat::JPEG), "JPEG");
assert_eq!(codecs.format_name(ImageFormat::Unknown), "Unknown");
}
#[test]
fn test_integration_resize_and_blend() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![200u8, 100, 50, 255, 50, 200, 100, 128]; let mut resized = vec![0u8; 4 * 1 * 4]; gfx.resize_rgba8(&src, 2, 1, &mut resized, 4, 1, ResizeFilter::Bilinear);
let overlay = vec![
128u8, 128, 128, 128, 64, 64, 64, 64, 192, 192, 192, 192, 32, 32, 32, 32,
];
gfx.blend_images(&overlay, &mut resized, 4, 1, BlendMode::Over);
assert!(resized[0] > 0);
assert!(resized[3] > 0);
}
#[test]
fn test_integration_rotate_and_crop() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![
10, 20, 30, 255, 40, 50, 60, 200, 70, 80, 90, 180, 100, 110, 120, 160,
];
let mut rotated = vec![0u8; 8];
gfx.rotate_rgba8(&src, 2, 2, &mut rotated, 90);
let mut cropped = vec![0u8; 4];
gfx.crop_rgba8(&rotated, 2, 0, 0, 1, 1, &mut cropped);
assert_eq!(cropped[3], 180);
}
#[test]
fn test_integration_filter_pipeline() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut src = vec![128u8; 8 * 8 * 4];
src[4 * 8 * 4 + 4 * 4] = 255;
src[4 * 8 * 4 + 4 * 4 + 1] = 0;
src[4 * 8 * 4 + 4 * 4 + 2] = 0;
let mut blurred = vec![0u8; 8 * 8 * 4];
gfx.gaussian_blur_rgba8(&src, &mut blurred, 8, 8, 1.5);
let mut sharpened = vec![0u8; 8 * 8 * 4];
gfx.sharpen_unsharp_mask(&blurred, &mut sharpened, 8, 8, 0.5, 1.0, 0);
assert!(sharpened[0] > 0);
}
#[test]
fn test_integration_color_and_tone() {
let cs = X86ColorSpaceOps::new();
let (tr, tg, tb) = cs.tonemap_aces(3.0, 2.0, 1.0);
let (sr, sg, sb) = cs.linear_to_srgb(tr, tg, tb);
assert!(sr >= 0.0 && sr <= 1.0);
assert!(sg >= 0.0 && sg <= 1.0);
assert!(sb >= 0.0 && sb <= 1.0);
}
#[test]
fn test_integration_color_pipeline() {
let cs = X86ColorSpaceOps::new();
let (lr, lg, lb) = cs.srgb_to_linear(0.8, 0.4, 0.2);
let (x, y, z) = cs.rgb_to_xyz(lr, lg, lb);
let (l, a_val, b_val) = cs.xyz_to_lab(x, y, z);
let (l2, c, h) = cs.lab_to_lch(l, a_val, b_val);
let (la, aa, ba) = cs.lch_to_lab(l2, c, h);
let (x2, y2, z2) = cs.lab_to_xyz(la, aa, ba);
let (rr, rg, rb) = cs.xyz_to_rgb(x2, y2, z2);
let (sr, sg, sb) = cs.linear_to_srgb(rr, rg, rb);
assert!((sr - 0.8).abs() < 0.02);
assert!((sg - 0.4).abs() < 0.02);
assert!((sb - 0.2).abs() < 0.02);
}
#[test]
fn test_integration_hdr_pipeline() {
let cs = X86ColorSpaceOps::new();
let original = [0.25f32, 1.5, 3.0, 0.125];
let mut f16 = [0u16; 4];
cs.rgba_f32_to_f16(&original, &mut f16);
let mut decoded = [0.0f32; 4];
cs.rgba_f16_to_f32(&f16, &mut decoded);
for i in 0..4 {
assert!((decoded[i] - original[i]).abs() < 0.02 * original[i].max(1.0));
}
}
#[test]
fn test_integration_geometry_and_shader() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let geo = X86GeometryOps::new();
let t = geo.translate(3.0, 0.0, 0.0);
let r = geo.rotate_z(std::f32::consts::PI / 2.0);
let s = geo.scale(2.0, 2.0, 2.0);
let m1 = si.mat4_mul_mat4(&r, &s);
let m = si.mat4_mul_mat4(&t, &m1);
let p = si.mat4_mul_point3(&m, &[1.0, 0.0, 0.0]);
assert!((p[0] - 3.0).abs() < 0.01);
assert!((p[1] - 2.0).abs() < 0.01);
}
#[test]
fn test_integration_frustum_and_ray() {
let geo = X86GeometryOps::new();
let vp = geo.shader.mat4_identity();
let frustum = X86GeometryOps::Frustum::from_view_projection(&vp);
let ray = X86GeometryOps::Ray::new([0.5, 0.5, -1.0], [0.0, 0.0, 1.0]);
let aabb = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
assert!(geo.aabb_frustum_test(&frustum, &aabb));
let result = geo.ray_aabb_intersect(&ray, &aabb);
assert!(result.is_some());
}
#[test]
fn test_integration_quaternion_chain() {
let q1 = X86GeometryOps::Quaternion::from_axis_angle(
[1.0, 0.0, 0.0],
std::f32::consts::PI / 4.0,
);
let q2 = X86GeometryOps::Quaternion::from_axis_angle(
[0.0, 1.0, 0.0],
std::f32::consts::PI / 4.0,
);
let q = q1.mul(&q2).normalize();
let v = q.rotate_vector(&[0.0, 0.0, 1.0]);
let mag = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
assert!((mag - 1.0).abs() < 0.01);
}
#[test]
fn test_integration_bmp_to_qoi_roundtrip() {
let codecs = X86ImageCodecs::new();
let pixels = vec![
255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 128, 128, 128, 255,
];
let bmp = codecs.encode_bmp(2, 2, &pixels);
let (w, h, decoded_bmp) = codecs.decode_bmp(&bmp).unwrap();
let qoi = codecs.encode_qoi(w, h, &decoded_bmp, 4);
let (w2, h2, decoded_qoi) = codecs.decode_qoi(&qoi).unwrap();
assert_eq!(w2, w);
assert_eq!(h2, h);
for i in 0..decoded_bmp.len() {
assert!(
(decoded_qoi[i] as i32 - decoded_bmp[i] as i32).abs() <= 1,
"Mismatch at {}",
i
);
}
}
#[test]
fn test_integration_font_glyph_pipeline() {
let fr = X86FontRendering::new();
let outline = X86FontRendering::GlyphOutline {
contours: vec![vec![
X86FontRendering::GlyphPoint {
x: -100,
y: -100,
on_curve: true,
},
X86FontRendering::GlyphPoint {
x: 100,
y: -100,
on_curve: true,
},
X86FontRendering::GlyphPoint {
x: 0,
y: 100,
on_curve: true,
},
]],
x_min: -100,
y_min: -100,
x_max: 100,
y_max: 100,
advance_width: 500,
left_side_bearing: 10,
};
let sdf = fr.rasterize_glyph_sdf(&outline, 32, 8);
assert_eq!(sdf.len(), 48 * 48);
let center_val = sdf[24 * 48 + 24];
assert!(center_val <= 0.0, "Center should be inside the glyph");
}
#[test]
fn test_integration_gpu_memory_pipeline() {
let gpu = X86GPUSupport::new();
let semantics = GPUMemorySemantics::acq_rel();
assert!(gpu.compute_workgroup_barrier(GPUMemoryScope::Workgroup, semantics));
let seq = GPUMemorySemantics::seq_cst();
assert!(gpu.compute_workgroup_barrier(GPUMemoryScope::Device, seq));
}
#[test]
fn test_integration_all_subsystems() {
let gfx = X86Graphics::new();
let caps = gfx.capabilities();
assert!(caps.vector_width_f32 >= 1);
assert!(caps.max_image_dimension > 0);
let packed = gfx.simd_graphics.pack_rgba_f32_to_u32(0.5, 0.5, 0.5, 1.0);
assert_eq!(packed & 0xFF, 128);
let dot = gfx.shader_intrinsics.dot(&[1.0, 2.0], &[3.0, 4.0]);
assert_eq!(dot, 11.0);
let aabb = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
assert!(gfx.geometry_ops.aabb_intersects(&aabb, &aabb));
let (l, _, _) = gfx.color_space_ops.srgb_to_linear(0.5, 0.5, 0.5);
assert!(l < 0.5);
}
#[test]
fn test_pixel_format_has_alpha() {
assert!(!PixelFormat::R8.has_alpha());
assert!(!PixelFormat::R8G8B8.has_alpha());
assert!(PixelFormat::B8G8R8A8.has_alpha());
assert!(PixelFormat::R32G32B32A32F.has_alpha());
}
#[test]
fn test_all_pixel_format_names() {
for fmt in &[
PixelFormat::R8,
PixelFormat::R8G8,
PixelFormat::R8G8B8,
PixelFormat::R8G8B8A8,
PixelFormat::B8G8R8A8,
PixelFormat::A8R8G8B8,
PixelFormat::R16F,
PixelFormat::R16G16B16A16F,
PixelFormat::R32F,
PixelFormat::R32G32B32A32F,
PixelFormat::RGBE8,
PixelFormat::R10G10B10A2,
PixelFormat::R11G11B10F,
] {
assert!(!fmt.name().is_empty());
}
}
#[test]
fn test_blend_color_dodge() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [64, 64, 64, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::ColorDodge);
assert!(result[0] >= dst[0]);
}
#[test]
fn test_blend_color_burn() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [64, 64, 64, 255];
let dst = [192, 192, 192, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::ColorBurn);
assert!(result[0] <= dst[0]);
}
#[test]
fn test_blend_hard_light() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [200, 150, 100, 255];
let dst = [100, 150, 200, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::HardLight);
assert!(result[0] >= 0);
}
#[test]
fn test_blend_soft_light() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [128, 128, 128, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::SoftLight);
assert!((result[0] as i32 - 128).abs() <= 10);
}
#[test]
fn test_blend_exclusion() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [255, 128, 0, 255];
let dst = [255, 128, 0, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Exclusion);
assert_eq!(result[0], 0); }
#[test]
fn test_blend_subtract() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [50, 100, 150, 255];
let dst = [200, 200, 200, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Subtract);
assert_eq!(result[0], 150);
assert_eq!(result[1], 100);
assert_eq!(result[2], 50);
}
#[test]
fn test_blend_divide() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [64, 64, 64, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::Divide);
assert!(result[0] > dst[0]); }
#[test]
fn test_blend_linear_burn() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [128, 128, 128, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::LinearBurn);
assert_eq!(result[0], 0); }
#[test]
fn test_blend_linear_dodge() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [128, 128, 128, 255];
let dst = [128, 128, 128, 255];
let result = gfx.blend_pixel(src, dst, BlendMode::LinearDodge);
assert_eq!(result[0], 255); }
#[test]
fn test_dp4_masked() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let a = [1.0, 2.0, 3.0, 4.0];
let b = [5.0, 6.0, 7.0, 8.0];
let r = si.dp4_masked(a, b, 0xFF);
assert_eq!(r[0], 70.0);
assert_eq!(r[3], 70.0);
let r2 = si.dp4_masked(a, b, 0x11);
assert_eq!(r2[0], 5.0);
}
#[test]
fn test_normalize4() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.normalize4([3.0, 4.0, 0.0, 0.0]);
assert!((r[0] - 0.6).abs() < 0.01);
assert!((r[1] - 0.8).abs() < 0.01);
}
#[test]
fn test_length4() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let len = si.length4([3.0, 4.0, 0.0, 0.0]);
assert!((len - 5.0).abs() < 0.001);
}
#[test]
fn test_lerp4() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.lerp4([0.0, 0.0, 0.0, 0.0], [2.0, 4.0, 6.0, 8.0], 0.5);
assert_eq!(r, [1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn test_clamp3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let r = si.clamp3([-1.0, 0.5, 2.0], 0.0, 1.0);
assert_eq!(r, [0.0, 0.5, 1.0]);
}
#[test]
fn test_distance3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let d = si.distance3([0.0, 0.0, 0.0], [3.0, 4.0, 0.0]);
assert!((d - 5.0).abs() < 0.001);
}
#[test]
fn test_mat4_mul_dir3() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let m = si.mat4_identity();
let r = si.mat4_mul_dir3(&m, &[1.0, 2.0, 3.0]);
assert_eq!(r, [1.0, 2.0, 3.0]);
}
#[test]
fn test_mat4_determinant_non_identity() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let mut m = si.mat4_identity();
m[0] = 2.0; m[5] = 3.0; m[10] = 4.0; let det = si.mat4_determinant(&m);
assert!((det - 24.0).abs() < 0.001);
}
#[test]
fn test_mat4_inverse_non_identity() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let mut m = si.mat4_identity();
m[0] = 2.0;
m[5] = 2.0;
m[10] = 2.0;
let inv = si.mat4_inverse(&m);
let result = si.mat4_mul_mat4(&m, &inv);
for i in 0..16 {
let expected = if i % 5 == 0 { 1.0 } else { 0.0 };
assert!((result[i] - expected).abs() < 0.001, "Mismatch at {}", i);
}
}
#[test]
fn test_refract_total_internal() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let i = [0.9, -0.1, 0.0];
let n = [0.0, 1.0, 0.0];
let r = si.refract(i, n, 1.5);
assert_eq!(r, [0.0, 0.0, 0.0]);
}
#[test]
fn test_faceforward_opposite() {
let si = X86ShaderIntrinsics::new(X86SIMDGraphicsLevel::SSE2);
let n = [0.0, 1.0, 0.0];
let i = [0.0, 1.0, 0.0];
let nref = [0.0, 1.0, 0.0];
let r = si.faceforward(n, i, nref);
assert_eq!(r, [0.0, -1.0, 0.0]);
}
#[test]
fn test_aabb_empty() {
let aabb = X86GeometryOps::AABB::empty();
assert!(aabb.min[0] > aabb.max[0]); }
#[test]
fn test_aabb_extend() {
let mut aabb = X86GeometryOps::AABB::empty();
aabb.extend(&[1.0, 2.0, 3.0]);
aabb.extend(&[-1.0, -2.0, -3.0]);
assert_eq!(aabb.min, [-1.0, -2.0, -3.0]);
assert_eq!(aabb.max, [1.0, 2.0, 3.0]);
}
#[test]
fn test_aabb_merge() {
let mut a = X86GeometryOps::AABB::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = X86GeometryOps::AABB::new([2.0, 2.0, 2.0], [3.0, 3.0, 3.0]);
a.merge(&b);
assert_eq!(a.min, [0.0, 0.0, 0.0]);
assert_eq!(a.max, [3.0, 3.0, 3.0]);
}
#[test]
fn test_ray_at() {
let ray = X86GeometryOps::Ray::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
let p = ray.at(5.0);
assert_eq!(p, [5.0, 0.0, 0.0]);
}
#[test]
fn test_ray_sphere_inside() {
let geo = X86GeometryOps::new();
let ray = X86GeometryOps::Ray::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
let t = geo.ray_sphere_intersect(&ray, [0.0, 0.0, 0.0], 2.0);
assert!(t.is_some());
}
#[test]
fn test_ray_sphere_miss() {
let geo = X86GeometryOps::new();
let ray = X86GeometryOps::Ray::new([10.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let t = geo.ray_sphere_intersect(&ray, [0.0, 0.0, 0.0], 1.0);
assert!(t.is_none());
}
#[test]
fn test_rotate_euler() {
let geo = X86GeometryOps::new();
let m = geo.rotate_euler(std::f32::consts::PI / 2.0, 0.0, 0.0);
let p = geo.shader.mat4_mul_point3(&m, &[1.0, 0.0, 0.0]);
assert!((p[0] - 0.0).abs() < 0.001);
assert!((p[1] - 0.0).abs() < 0.001);
assert!((p[2] - (-1.0)).abs() < 0.001);
}
#[test]
fn test_compose_mvp() {
let geo = X86GeometryOps::new();
let model = geo.translate(1.0, 0.0, 0.0);
let view = geo.look_at([0.0, 0.0, 5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let proj = geo.perspective(std::f32::consts::PI / 2.0, 1.0, 0.1, 100.0);
let mvp = geo.compose_mvp(&model, &view, &proj);
let p = geo.shader.mat4_mul_point3(&mvp, &[0.0, 0.0, 0.0]);
assert!(p[2] < 1.0);
}
#[test]
fn test_quaternion_from_euler() {
let q = X86GeometryOps::Quaternion::from_euler(0.0, 0.0, std::f32::consts::PI / 2.0);
let v = q.rotate_vector(&[1.0, 0.0, 0.0]);
assert!((v[0] - 0.0).abs() < 0.001);
assert!((v[1] - 1.0).abs() < 0.001);
}
#[test]
fn test_quaternion_inverse() {
let q = X86GeometryOps::Quaternion::from_axis_angle(
[0.0, 1.0, 0.0],
std::f32::consts::PI / 4.0,
);
let qi = q.inverse();
let qid = q.mul(&qi);
assert!((qid.w - 1.0).abs() < 0.001);
assert!((qid.x - 0.0).abs() < 0.001);
}
#[test]
fn test_quaternion_slerp_endpoints() {
let a = X86GeometryOps::Quaternion::identity();
let b = X86GeometryOps::Quaternion::from_axis_angle([1.0, 0.0, 0.0], std::f32::consts::PI);
let t0 = X86GeometryOps::Quaternion::slerp(&a, &b, 0.0);
assert!((t0.w - a.w).abs() < 0.001);
let t1 = X86GeometryOps::Quaternion::slerp(&a, &b, 1.0);
assert!((t1.x - b.x).abs() < 0.001);
}
#[test]
fn test_bounding_sphere_single_point() {
let geo = X86GeometryOps::new();
let (center, radius) = geo.bounding_sphere(&[[1.0, 2.0, 3.0]]);
assert_eq!(center, [1.0, 2.0, 3.0]);
assert_eq!(radius, 0.0);
}
#[test]
fn test_perceived_brightness() {
let cs = X86ColorSpaceOps::new();
let b = cs.perceived_brightness(255, 255, 255);
assert!((b - 1.0).abs() < 0.01);
let d = cs.perceived_brightness(0, 0, 0);
assert!((d - 0.0).abs() < 0.01);
}
#[test]
fn test_rgb_to_hsv_gray() {
let cs = X86ColorSpaceOps::new();
let (h, s, v) = cs.rgb_to_hsv(0.5, 0.5, 0.5);
assert_eq!(s, 0.0);
assert_eq!(v, 0.5);
}
#[test]
fn test_rgb_to_hsl_gray() {
let cs = X86ColorSpaceOps::new();
let (h, s, l) = cs.rgb_to_hsl(0.5, 0.5, 0.5);
assert_eq!(s, 0.0);
assert_eq!(l, 0.5);
}
#[test]
fn test_tonemap_reinhard_ext() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.tonemap_reinhard_ext(2.0, 2.0, 2.0, 4.0);
assert!(r > 0.0 && r < 1.0);
}
#[test]
fn test_kelvin_to_rgb_10000() {
let cs = X86ColorSpaceOps::new();
let (r, g, b) = cs.kelvin_to_rgb(10000.0);
assert!(b > r);
}
#[test]
fn test_spirv_word_count_various() {
assert_eq!(X86GPUSupport::spirv_word_count(0), 1);
assert_eq!(X86GPUSupport::spirv_word_count(15), 4);
assert_eq!(X86GPUSupport::spirv_word_count(60), 4);
assert_eq!(X86GPUSupport::spirv_word_count(105), 4);
}
#[test]
fn test_spirv_capability_values() {
assert_eq!(X86GPUSupport::spirv_capability::SHADER, 1);
assert_eq!(X86GPUSupport::spirv_capability::KERNEL, 6);
assert_eq!(X86GPUSupport::spirv_capability::FLOAT64, 10);
assert_eq!(X86GPUSupport::spirv_capability::INT16, 40);
}
#[test]
fn test_vulkan_all_limits_nonzero() {
assert!(X86GPUSupport::VK_MAX_DESCRIPTOR_SET_BINDINGS > 0);
assert!(X86GPUSupport::VK_MAX_VIEWPORTS > 0);
assert!(X86GPUSupport::VK_MAX_SAMPLER_ALLOCATION_COUNT > 0);
assert!(X86GPUSupport::VK_MAX_COMPUTE_WORK_GROUP_SIZE_Z > 0);
}
#[test]
fn test_memory_semantics_make_visible() {
let mut semantics = GPUMemorySemantics::relaxed();
semantics.make_visible = true;
assert_eq!(semantics.spirv_semantics(), 0x4000);
semantics.make_available = true;
assert_eq!(semantics.spirv_semantics(), 0x6000);
}
#[test]
fn test_all_gpu_scopes_have_valid_spirv() {
let scopes = [
GPUMemoryScope::Subgroup,
GPUMemoryScope::Workgroup,
GPUMemoryScope::Device,
GPUMemoryScope::System,
GPUMemoryScope::Invocation,
GPUMemoryScope::CrossWorkgroup,
];
for scope in &scopes {
let sv = scope.spirv_scope();
assert!(sv <= 4, "Invalid SPIR-V scope for {:?}", scope);
}
}
#[test]
fn test_bmp_reject_non_bmp() {
let codecs = X86ImageCodecs::new();
assert!(codecs.decode_bmp(b"XX").is_none());
}
#[test]
fn test_png_crc32_known_values() {
let codecs = X86ImageCodecs::new();
let crc_empty = codecs.png_crc32(b"");
assert_eq!(crc_empty, 0);
let crc1 = codecs.png_crc32(b"test");
let crc2 = codecs.png_crc32(b"test");
assert_eq!(crc1, crc2);
}
#[test]
fn test_jpeg_markers_handles_truncated() {
let codecs = X86ImageCodecs::new();
let markers = codecs.find_jpeg_markers(&[0xFF]);
assert!(markers.is_empty());
}
#[test]
fn test_tiff_validate_header_big_endian() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 8];
data[0] = 0x4D;
data[1] = 0x4D; data[2] = 0x00;
data[3] = 42; data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x00;
data[7] = 8; let result = codecs.validate_tiff_header(&data);
assert!(result.is_some());
let (le, offset) = result.unwrap();
assert!(!le);
assert_eq!(offset, 8);
}
#[test]
fn test_tiff_tag_constants() {
assert_eq!(X86ImageCodecs::tiff_tag::IMAGE_WIDTH, 256);
assert_eq!(X86ImageCodecs::tiff_tag::COMPRESSION, 259);
assert_eq!(X86ImageCodecs::tiff_tag::SAMPLES_PER_PIXEL, 277);
}
#[test]
fn test_gif_constants() {
assert_eq!(&X86ImageCodecs::GIF87A_SIG, b"GIF87a");
assert_eq!(&X86ImageCodecs::GIF89A_SIG, b"GIF89a");
}
#[test]
fn test_webp_constants() {
assert_eq!(&X86ImageCodecs::WEBP_RIFF_FOURCC, b"RIFF");
assert_eq!(&X86ImageCodecs::WEBP_WEBP_FOURCC, b"WEBP");
}
#[test]
fn test_qoi_encode_decode_large_uniform() {
let codecs = X86ImageCodecs::new();
let w = 64;
let h = 64;
let pixels = vec![128u8; w * h * 4];
let encoded = codecs.encode_qoi(w, h, &pixels, 4);
let (dw, dh, decoded) = codecs.decode_qoi(&encoded).unwrap();
assert_eq!(dw, w);
assert_eq!(dh, h);
assert_eq!(decoded[0], 128);
assert_eq!(decoded[100], 128);
assert_eq!(decoded[decoded.len() - 4], 128);
}
#[test]
fn test_detect_all_formats_coverage() {
let codecs = X86ImageCodecs::new();
let formats = [
(b"BM\x00\x00" as &[u8], ImageFormat::BMP),
(&[137, 80, 78, 71, 13, 10, 26, 10], ImageFormat::PNG),
(&[0xFF, 0xD8], ImageFormat::JPEG),
(b"II\x2A\x00\x08\x00\x00\x00", ImageFormat::TIFF),
(b"GIF89a", ImageFormat::GIF),
(b"qoif", ImageFormat::QOI),
];
for (data, expected) in &formats {
assert_eq!(codecs.detect_format(data), *expected);
}
}
#[test]
fn test_fill_rgba8_large_buffer() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![0u8; 1024 * 1024 * 4]; gfx.fill_rgba8(&mut pixels, [255, 128, 64, 32]);
assert_eq!(pixels[0], 255);
assert_eq!(pixels[pixels.len() - 4], 255);
assert_eq!(pixels[pixels.len() - 1], 32);
}
#[test]
fn test_resize_identity() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = [10u8, 20, 30, 255, 40, 50, 60, 128];
let mut dst = [0u8; 8];
gfx.resize_rgba8(&src, 2, 1, &mut dst, 2, 1, ResizeFilter::Nearest);
assert_eq!(dst, src);
}
#[test]
fn test_large_median_filter() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![100u8; 16 * 16 * 4];
let mut dst = vec![0u8; 16 * 16 * 4];
gfx.median_filter_rgba8(&src, &mut dst, 16, 16, 2);
assert_eq!(dst[0], 100);
}
#[test]
fn test_simd_level_detect_is_valid() {
let level = X86SIMDGraphicsLevel::detect();
assert!(level.vector_width_f32() >= 1);
assert!(level.vector_bytes() >= 4);
}
#[test]
fn test_x86_graphics_repeatability() {
let gfx1 = X86Graphics::new();
let gfx2 = X86Graphics::new();
assert_eq!(gfx1.simd_level, gfx2.simd_level);
}
#[test]
fn test_bilateral_filter_basic() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![128u8; 5 * 5 * 4];
let mut dst = vec![0u8; 5 * 5 * 4];
gfx.bilateral_filter_rgba8(&src, &mut dst, 5, 5, 1.0, 0.1);
assert_eq!(dst[2 * 5 * 4 + 2 * 4], 128);
}
#[test]
fn test_sharpen_unsharp_mask_basic() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let src = vec![100u8; 8 * 8 * 4];
let mut dst = vec![0u8; 8 * 8 * 4];
gfx.sharpen_unsharp_mask(&src, &mut dst, 8, 8, 1.0, 0.5, 0);
assert_eq!(dst[0], 100);
}
#[test]
fn test_dither_jarvis() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![128u8; 4 * 4 * 4];
gfx.dither_rgba8(&mut pixels, 4, 4, 2, DitherKind::JarvisJudiceNinke);
for i in 0..4 * 4 {
for c in 0..3 {
assert!(pixels[i * 4 + c] == 0 || pixels[i * 4 + c] == 255);
}
}
}
#[test]
fn test_dither_stucki() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![128u8; 4 * 4 * 4];
gfx.dither_rgba8(&mut pixels, 4, 4, 2, DitherKind::Stucki);
for i in 0..4 * 4 {
for c in 0..3 {
assert!(pixels[i * 4 + c] == 0 || pixels[i * 4 + c] == 255);
}
}
}
#[test]
fn test_dither_blue_noise() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let mut pixels = vec![128u8; 4 * 4 * 4];
gfx.dither_rgba8(&mut pixels, 4, 4, 2, DitherKind::BlueNoise);
assert_eq!(pixels.len(), 64);
}
#[test]
fn test_bayer_matrix_recursive() {
let gfx = X86SIMDGraphics::new(X86SIMDGraphicsLevel::SSE2);
let m2 = X86SIMDGraphics::bayer_matrix(2);
assert_eq!(m2.len(), 4);
let m4 = X86SIMDGraphics::bayer_matrix(4);
assert_eq!(m4.len(), 16);
let m8 = X86SIMDGraphics::bayer_matrix(8);
assert_eq!(m8.len(), 64);
}
#[test]
fn test_rgb_to_hsv_cyan() {
let cs = X86ColorSpaceOps::new();
let (h, _, _) = cs.rgb_to_hsv(0.0, 1.0, 1.0);
assert!((h - 180.0).abs() < 1.0 || h < 1.0);
}
#[test]
fn test_rgb_to_hsv_magenta() {
let cs = X86ColorSpaceOps::new();
let (h, _, _) = cs.rgb_to_hsv(1.0, 0.0, 1.0);
assert!((h - 300.0).abs() < 1.0 || h < 1.0);
}
#[test]
fn test_rgb_to_hsl_black() {
let cs = X86ColorSpaceOps::new();
let (h, s, l) = cs.rgb_to_hsl(0.0, 0.0, 0.0);
assert_eq!(s, 0.0);
assert_eq!(l, 0.0);
}
#[test]
fn test_rgb_to_hsv_black() {
let cs = X86ColorSpaceOps::new();
let (h, s, v) = cs.rgb_to_hsv(0.0, 0.0, 0.0);
assert_eq!(s, 0.0);
assert_eq!(v, 0.0);
}
#[test]
fn test_lab_to_lch_gray() {
let cs = X86ColorSpaceOps::new();
let (l, c, _) = cs.lab_to_lch(50.0, 0.0, 0.0);
assert_eq!(c, 0.0);
assert_eq!(l, 50.0);
}
#[test]
fn test_lch_to_lab_gray() {
let cs = X86ColorSpaceOps::new();
let (l, a, b) = cs.lch_to_lab(50.0, 0.0, 180.0);
assert!((a - 0.0).abs() < 0.01);
assert!((b - 0.0).abs() < 0.01);
}
#[test]
fn test_f16_overflow_to_inf() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(1e10);
let unpacked = cs.f16_to_f32(packed);
assert!(unpacked.is_infinite());
}
#[test]
fn test_f16_nan() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(f32::NAN);
let unpacked = cs.f16_to_f32(packed);
assert!(unpacked.is_nan());
}
#[test]
fn test_f16_infinity() {
let cs = X86ColorSpaceOps::new();
let packed = cs.f32_to_f16(f32::INFINITY);
let unpacked = cs.f16_to_f32(packed);
assert!(unpacked.is_infinite());
}
#[test]
fn test_rgbe_hdr_values() {
let cs = X86ColorSpaceOps::new();
let encoded = cs.rgbe_encode(10.0, 5.0, 2.0);
let (r, g, b) = cs.rgbe_decode(encoded);
assert!((r - 10.0).abs() < 0.5);
assert!((g - 5.0).abs() < 0.3);
assert!((b - 2.0).abs() < 0.2);
}
#[test]
fn test_linear_f32_to_srgb8() {
let cs = X86ColorSpaceOps::new();
let src = [0.5f32, 0.3, 0.8, 1.0];
let mut dst = [0u8; 4];
cs.linear_f32_to_srgb8(&src, &mut dst);
assert!(dst[0] > 128); assert!(dst[3] == 255);
}
#[test]
fn test_contrast_ratio_min() {
let cs = X86ColorSpaceOps::new();
let cr = cs.contrast_ratio(0.5, 0.5);
assert!((cr - 1.0).abs() < 0.01);
}
#[test]
fn test_cmap_lookup_format4() {
let fr = X86FontRendering::new();
let subs = vec![X86FontRendering::CmapSubtable {
platform_id: 3,
encoding_id: 1,
format: 4,
format4_segments: vec![(32, 126, -32, 0)], format12_groups: vec![],
}];
assert_eq!(fr.cmap_lookup(&subs, 65), Some(33));
assert_eq!(fr.cmap_lookup(&subs, 0x10000), None); }
#[test]
fn test_cmap_lookup_format12() {
let fr = X86FontRendering::new();
let subs = vec![X86FontRendering::CmapSubtable {
platform_id: 3,
encoding_id: 10,
format: 12,
format4_segments: vec![],
format12_groups: vec![(0x10000, 0x100FF, 1)], }];
assert_eq!(fr.cmap_lookup(&subs, 0x10000), Some(1));
assert_eq!(fr.cmap_lookup(&subs, 0x10050), Some(81));
assert_eq!(fr.cmap_lookup(&subs, 65), None);
}
#[test]
fn test_parse_loca_long() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 12];
data[0] = 0x00;
data[1] = 0x00;
data[2] = 0x00;
data[3] = 0x00;
data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x00;
data[7] = 0x64;
data[8] = 0x00;
data[9] = 0x00;
data[10] = 0x00;
data[11] = 0xC8;
let offsets = fr.parse_loca_table(&data, 0, 2, 1);
assert_eq!(offsets.len(), 3);
assert_eq!(offsets[1], 100);
assert_eq!(offsets[2], 200);
}
#[test]
fn test_sdf_empty_contours() {
let fr = X86FontRendering::new();
let outline = X86FontRendering::GlyphOutline {
contours: vec![],
x_min: 0,
y_min: 0,
x_max: 100,
y_max: 100,
advance_width: 0,
left_side_bearing: 0,
};
let sdf = fr.rasterize_glyph_sdf(&outline, 16, 2);
assert_eq!(sdf.len(), 20 * 20);
assert!(sdf[0] > 0.9);
}
#[test]
fn test_point_in_polygon_quad() {
let poly = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
assert!(X86FontRendering::point_in_polygon(5.0, 5.0, &poly));
assert!(!X86FontRendering::point_in_polygon(15.0, 5.0, &poly));
}
#[test]
fn test_point_to_segment_distance() {
let d = X86FontRendering::point_to_segment_distance(5.0, 5.0, 0.0, 0.0, 10.0, 0.0);
assert!((d - 5.0).abs() < 0.01);
let d2 = X86FontRendering::point_to_segment_distance(0.0, 0.0, 0.0, 0.0, 10.0, 10.0);
assert!((d2 - 0.0).abs() < 0.01);
}
#[test]
fn test_parse_simple_glyph_stub() {
let fr = X86FontRendering::new();
let mut data = vec![0u8; 12];
data[0] = 0;
data[1] = 0; data[2] = 0x00;
data[3] = 0x00; data[4] = 0x00;
data[5] = 0x00; data[6] = 0x03;
data[7] = 0x20; data[8] = 0x02;
data[9] = 0x58; let glyph = fr.parse_simple_glyph(&data, 0, 12);
assert!(glyph.is_some());
let g = glyph.unwrap();
assert_eq!(g.x_max, 800);
assert_eq!(g.y_max, 600);
assert!(g.contours.is_empty());
}
#[test]
fn test_is_combining_mark() {
assert!(X86FontRendering::is_combining_mark('\u{0301}')); assert!(!X86FontRendering::is_combining_mark('A'));
}
#[test]
fn test_is_regional_indicator() {
assert!(X86FontRendering::is_regional_indicator('\u{1F1FA}')); assert!(!X86FontRendering::is_regional_indicator('A'));
}
#[test]
fn test_shape_text_multiple_chars() {
let fr = X86FontRendering::new();
let cmap = vec![X86FontRendering::CmapSubtable {
platform_id: 3,
encoding_id: 1,
format: 4,
format4_segments: vec![(65, 90, -64, 0)],
format12_groups: vec![],
}];
let hmtx = (0..27)
.map(|_| X86FontRendering::HorizontalMetric {
advance_width: 500,
left_side_bearing: 0,
})
.collect::<Vec<_>>();
let glyphs = fr.shape_text("ABC", &cmap, &hmtx, 1000, 16.0);
assert_eq!(glyphs.len(), 3);
assert!(glyphs[2].x_offset > glyphs[0].x_offset);
}
#[test]
fn test_spirv_memory_models() {
assert_eq!(X86GPUSupport::spirv_memory_model::SIMPLE, 0);
assert_eq!(X86GPUSupport::spirv_memory_model::VULKAN, 3);
}
#[test]
fn test_spirv_magic_constant() {
assert_eq!(X86GPUSupport::SPIRV_MAGIC, 0x07230203);
}
#[test]
fn test_spirv_version_major_minor() {
assert_eq!(X86GPUSupport::SPIRV_VERSION_MAJOR, 1);
assert_eq!(X86GPUSupport::SPIRV_VERSION_MINOR, 6);
}
#[test]
fn test_gpu_memory_semantics_default() {
let s = GPUMemorySemantics::relaxed();
assert!(!s.acquire);
assert!(!s.release);
assert!(!s.sequentially_consistent);
}
#[test]
fn test_bmp_encode_larger_image() {
let codecs = X86ImageCodecs::new();
let pixels = vec![100u8; 32 * 32 * 4];
let bmp = codecs.encode_bmp(32, 32, &pixels);
let (w, h, decoded) = codecs.decode_bmp(&bmp).unwrap();
assert_eq!(w, 32);
assert_eq!(h, 32);
assert_eq!(decoded.len(), 32 * 32 * 4);
}
#[test]
fn test_bmp_decode_32bit() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 54 + 4];
data[0] = b'B';
data[1] = b'M';
data[2..6].copy_from_slice(&u32::to_le_bytes(58));
data[10..14].copy_from_slice(&u32::to_le_bytes(54));
data[14..18].copy_from_slice(&u32::to_le_bytes(40));
data[18..22].copy_from_slice(&i32::to_le_bytes(1));
data[22..26].copy_from_slice(&i32::to_le_bytes(1));
data[26..28].copy_from_slice(&u16::to_le_bytes(1));
data[28..30].copy_from_slice(&u16::to_le_bytes(32)); data[54] = 255;
data[55] = 128;
data[56] = 64;
data[57] = 200;
let result = codecs.decode_bmp(&data);
assert!(result.is_some());
let (_, _, pixels) = result.unwrap();
assert_eq!(pixels[3], 200); }
#[test]
fn test_bmp_decode_16bit_unsupported() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 54];
data[0] = b'B';
data[1] = b'M';
data[18..22].copy_from_slice(&i32::to_le_bytes(1));
data[22..26].copy_from_slice(&i32::to_le_bytes(1));
data[28..30].copy_from_slice(&u16::to_le_bytes(16)); assert!(codecs.decode_bmp(&data).is_none());
}
#[test]
fn test_tiff_validate_header_invalid_magic() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 8];
data[0] = 0x49;
data[1] = 0x49; data[2] = 0x00;
data[3] = 0x00; assert!(codecs.validate_tiff_header(&data).is_none());
}
#[test]
fn test_tiff_validate_header_short() {
let codecs = X86ImageCodecs::new();
assert!(codecs.validate_tiff_header(&[0u8; 4]).is_none());
}
#[test]
fn test_parse_tiff_ifd_multiple_entries() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 26];
data[0] = 2;
data[1] = 0; data[2] = 0x01;
data[3] = 0x00;
data[4] = 0x00;
data[5] = 0x03;
data[6] = 0x01;
data[7] = 0x00;
data[8] = 0x00;
data[9] = 0x00;
data[10] = 0x40;
data[11] = 0x01;
data[12] = 0x00;
data[13] = 0x00;
data[14] = 0x01;
data[15] = 0x01;
data[16] = 0x00;
data[17] = 0x03;
data[18] = 0x01;
data[19] = 0x00;
data[20] = 0x00;
data[21] = 0x00;
data[22] = 0xF0;
data[23] = 0x00;
data[24] = 0x00;
data[25] = 0x00;
let entries = codecs.parse_tiff_ifd(&data, 0, true);
assert!(entries.is_some());
let e = entries.unwrap();
assert_eq!(e.len(), 2);
assert_eq!(e[1].tag, 257);
}
#[test]
fn test_jpeg_marker_skips_ff00() {
let codecs = X86ImageCodecs::new();
let data = [0xFF, 0x00, 0xFF, 0xD9];
let markers = codecs.find_jpeg_markers(&data);
assert_eq!(markers.len(), 1);
assert_eq!(markers[0].marker, 0xD9);
}
#[test]
fn test_default_quant_table_values() {
let codecs = X86ImageCodecs::new();
let table = codecs.build_default_luma_quant_table();
assert_eq!(table.values.len(), 64);
assert!(table.values.iter().all(|&v| v > 0));
}
#[test]
fn test_default_huffman_table() {
let codecs = X86ImageCodecs::new();
let table = codecs.build_default_luma_dc_huffman();
let total: u32 = table.counts.iter().map(|&c| c as u32).sum();
assert_eq!(total as usize, table.symbols.len());
}
#[test]
fn test_webp_detect_vp8l() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 16];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WEBP");
data[12..16].copy_from_slice(b"VP8L");
assert_eq!(codecs.detect_webp_format(&data).unwrap(), b"VP8L");
}
#[test]
fn test_webp_detect_vp8x() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 16];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WEBP");
data[12..16].copy_from_slice(b"VP8X");
assert_eq!(codecs.detect_webp_format(&data).unwrap(), b"VP8X");
}
#[test]
fn test_webp_detect_invalid() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 16];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WEBP");
data[12..16].copy_from_slice(b"XXXX");
assert!(codecs.detect_webp_format(&data).is_none());
}
#[test]
fn test_vp8_header_non_keyframe() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 10];
data[0] = 0x00; assert!(codecs.parse_vp8_header_stub(&data).is_none());
}
#[test]
fn test_vp8l_header_bad_signature() {
let codecs = X86ImageCodecs::new();
let data = [0x00, 0, 0, 0, 0];
assert!(codecs.parse_vp8l_header_stub(&data).is_none());
}
#[test]
fn test_gif_screen_descriptor_no_global_table() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 7];
data[0] = 0x40;
data[1] = 0x01; data[2] = 0xF0;
data[3] = 0x00; data[4] = 0x07; let sd = codecs.parse_gif_screen_descriptor(&data).unwrap();
assert!(!sd.global_color_table);
assert_eq!(sd.color_resolution, 1);
}
#[test]
fn test_gif_screen_descriptor_short() {
let codecs = X86ImageCodecs::new();
assert!(codecs.parse_gif_screen_descriptor(&[0u8; 3]).is_none());
}
#[test]
fn test_qoi_gradient() {
let codecs = X86ImageCodecs::new();
let w = 16;
let h = 16;
let mut pixels = vec![0u8; w * h * 4];
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 4;
let v = ((x + y) * 8) as u8;
pixels[i] = v;
pixels[i + 1] = v.wrapping_add(85);
pixels[i + 2] = v.wrapping_add(170);
pixels[i + 3] = 255;
}
}
let encoded = codecs.encode_qoi(w, h, &pixels, 4);
let (dw, dh, decoded) = codecs.decode_qoi(&encoded).unwrap();
assert_eq!(dw, w);
assert_eq!(dh, h);
for i in 0..pixels.len() {
assert_eq!(decoded[i], pixels[i], "Mismatch at pixel {}", i / 4);
}
}
#[test]
fn test_qoi_single_pixel_rgba() {
let codecs = X86ImageCodecs::new();
let pixels = [10, 20, 30, 40];
let encoded = codecs.encode_qoi(1, 1, &pixels, 4);
let (w, h, decoded) = codecs.decode_qoi(&encoded).unwrap();
assert_eq!((w, h), (1, 1));
assert_eq!(decoded, pixels);
}
#[test]
fn test_qoi_zero_dimension() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 14];
data[0..4].copy_from_slice(b"qoif");
data[4..8].copy_from_slice(&0u32.to_be_bytes()); data[8..12].copy_from_slice(&1u32.to_be_bytes());
data[12] = 4;
assert!(codecs.decode_qoi(&data).is_none());
}
#[test]
fn test_qoi_excessive_dimension() {
let codecs = X86ImageCodecs::new();
let mut data = vec![0u8; 14];
data[0..4].copy_from_slice(b"qoif");
data[4..8].copy_from_slice(&100000u32.to_be_bytes()); data[8..12].copy_from_slice(&1u32.to_be_bytes());
data[12] = 4;
assert!(codecs.decode_qoi(&data).is_none());
}
}