1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Quantization operations for converting continuous signals to discrete integers.
use crate::model::Rgb;
use crate::primaries::Primaries;
use crate::transfer::TransferFunction;
use crate::{BackingStore, ChannelMap, Color};
#[inline(always)]
fn quantize_f32_to_u8(x: f32) -> u8 {
(x * 255.0).round().clamp(0.0, 255.0) as u8
}
impl<M> Color<[f32; 3], M>
where
M: BackingStore<[f32; 3]> + BackingStore<[u8; 3]>,
{
/// Quantizes the channels from `f32` [0.0, 1.0] to `u8` [0, 255].
///
/// Uses standard scaling, half-adjust rounding, and clamping.
#[inline(always)]
pub fn to_u8(self) -> Color<[u8; 3], M> {
let [c0, c1, c2] = self.inner();
Color::new([
quantize_f32_to_u8(c0),
quantize_f32_to_u8(c1),
quantize_f32_to_u8(c2),
])
}
}
impl<M> Color<[f32; 4], M>
where
M: BackingStore<[f32; 4]> + BackingStore<[u8; 4]>,
{
/// Quantizes the channels from `f32` [0.0, 1.0] to `u8` [0, 255].
///
/// Uses standard scaling, half-adjust rounding, and clamping.
#[inline(always)]
pub fn to_u8(self) -> Color<[u8; 4], M> {
let [c0, c1, c2, c3] = self.inner();
Color::new([
quantize_f32_to_u8(c0),
quantize_f32_to_u8(c1),
quantize_f32_to_u8(c2),
quantize_f32_to_u8(c3),
])
}
}
impl<P, TF, L> Color<[f32; 3], Rgb<P, TF, L>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 3]> + BackingStore<[u8; 3]> + ChannelMap<3>,
{
/// Quantizes the color to `u8` with an applied dither to prevent banding.
///
/// The `dither` value is added to the unscaled `f32` color channels before
/// scaling and rounding. For mathematically perfect 8-bit quantization
/// (Triangular PDF), generate the dither value by subtracting two independent
/// uniform random variables in the range `[0.0, 1.0/255.0)`.
#[inline(always)]
pub fn to_u8_dithered(self, dither: f32) -> Color<[u8; 3], Rgb<P, TF, L>> {
let mut out = self.inner();
let [ri, gi, bi] = L::INDICES;
out[ri] += dither;
out[gi] += dither;
out[bi] += dither;
Color::new([
quantize_f32_to_u8(out[0]),
quantize_f32_to_u8(out[1]),
quantize_f32_to_u8(out[2]),
])
}
}
impl<P, TF, L> Color<[f32; 4], Rgb<P, TF, L>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 4]> + BackingStore<[u8; 4]> + ChannelMap<4>,
{
/// Quantizes the color to `u8` with an applied dither to prevent banding.
///
/// The `dither` value is added to the color channels (R, G, B) before scaling
/// and rounding. The alpha channel is quantized directly without dither to
/// preserve clean transparency masks.
#[inline(always)]
pub fn to_u8_dithered(self, dither: f32) -> Color<[u8; 4], Rgb<P, TF, L>> {
let mut out = self.inner();
let [ri, gi, bi, _ai] = L::INDICES;
// Dither color channels only
out[ri] += dither;
out[gi] += dither;
out[bi] += dither;
Color::new([
quantize_f32_to_u8(out[0]),
quantize_f32_to_u8(out[1]),
quantize_f32_to_u8(out[2]),
quantize_f32_to_u8(out[3]),
])
}
}