1#![allow(dead_code)]
12
13use api::ColorU;
14use std::cmp::max;
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub enum LuminanceColorSpace {
19 Linear,
21 Gamma(f32),
23 Srgb,
25}
26
27impl LuminanceColorSpace {
28 pub fn new(gamma: f32) -> LuminanceColorSpace {
29 if gamma == 1.0 {
30 LuminanceColorSpace::Linear
31 } else if gamma == 0.0 {
32 LuminanceColorSpace::Srgb
33 } else {
34 LuminanceColorSpace::Gamma(gamma)
35 }
36 }
37
38 pub fn to_luma(&self, luminance: f32) -> f32 {
39 match *self {
40 LuminanceColorSpace::Linear => luminance,
41 LuminanceColorSpace::Gamma(gamma) => luminance.powf(gamma),
42 LuminanceColorSpace::Srgb => {
43 if luminance <= 0.04045 {
46 luminance / 12.92
47 } else {
48 ((luminance + 0.055) / 1.055).powf(2.4)
49 }
50 }
51 }
52 }
53
54 pub fn from_luma(&self, luma: f32) -> f32 {
55 match *self {
56 LuminanceColorSpace::Linear => luma,
57 LuminanceColorSpace::Gamma(gamma) => luma.powf(1. / gamma),
58 LuminanceColorSpace::Srgb => {
59 if luma <= 0.0031308 {
62 luma * 12.92
63 } else {
64 1.055 * luma.powf(1./2.4) - 0.055
65 }
66 }
67 }
68 }
69}
70
71fn round_to_u8(x : f32) -> u8 {
73 let v = (x + 0.5).floor() as i32;
74 assert!(0 <= v && v < 0x100);
75 v as u8
76}
77
78fn scale255(n: u8, mut base: u8) -> u8 {
85 base <<= 8 - n;
86 let mut lum = base;
87 let mut i = n;
88
89 while i < 8 {
90 lum |= base >> i;
91 i += n;
92 }
93
94 lum
95}
96
97fn compute_luminance(r: u8, g: u8, b: u8) -> u8 {
100 let val: u32 = r as u32 * 54 + g as u32 * 183 + b as u32 * 19;
104 assert!(val < 0x10000);
105 (val >> 8) as u8
106}
107
108const LUM_BITS: u8 = 3;
110const LUM_MASK: u8 = ((1 << LUM_BITS) - 1) << (8 - LUM_BITS);
112
113pub trait ColorLut {
114 fn quantize(&self) -> ColorU;
115 fn quantized_floor(&self) -> ColorU;
116 fn quantized_ceil(&self) -> ColorU;
117 fn luminance(&self) -> u8;
118 fn luminance_color(&self) -> ColorU;
119}
120
121impl ColorLut for ColorU {
122 fn quantize(&self) -> ColorU {
126 ColorU::new(
127 scale255(LUM_BITS, self.r >> (8 - LUM_BITS)),
128 scale255(LUM_BITS, self.g >> (8 - LUM_BITS)),
129 scale255(LUM_BITS, self.b >> (8 - LUM_BITS)),
130 255,
131 )
132 }
133
134 fn quantized_floor(&self) -> ColorU {
136 ColorU::new(
137 self.r & LUM_MASK,
138 self.g & LUM_MASK,
139 self.b & LUM_MASK,
140 255,
141 )
142 }
143
144 fn quantized_ceil(&self) -> ColorU {
146 ColorU::new(
147 self.r | !LUM_MASK,
148 self.g | !LUM_MASK,
149 self.b | !LUM_MASK,
150 255,
151 )
152 }
153
154 fn luminance(&self) -> u8 {
157 compute_luminance(self.r, self.g, self.b)
158 }
159
160 fn luminance_color(&self) -> ColorU {
162 let lum = self.luminance();
163 ColorU::new(lum, lum, lum, self.a)
164 }
165}
166
167#[cfg(any(target_os="macos", target_os = "ios"))]
172fn get_inverse_gamma_table_coregraphics_smoothing() -> [u8; 256] {
173 let mut table = [0u8; 256];
174
175 for (i, v) in table.iter_mut().enumerate() {
176 let x = i as f32 / 255.0;
177 *v = round_to_u8(x * x * 255.0);
178 }
179
180 table
181}
182
183fn apply_contrast(srca: f32, contrast: f32) -> f32 {
188 srca + ((1.0 - srca) * contrast * srca)
189}
190
191pub fn build_gamma_correcting_lut(table: &mut [u8; 256], src: u8, contrast: f32,
195 src_space: LuminanceColorSpace,
196 dst_convert: LuminanceColorSpace) {
197 let src = src as f32 / 255.0;
198 let lin_src = src_space.to_luma(src);
199 let dst = 1.0 - src;
204 let lin_dst = dst_convert.to_luma(dst);
205
206 let adjusted_contrast = contrast * lin_dst;
208
209 if (src - dst).abs() < (1.0 / 256.0) {
212 let mut ii : f32 = 0.0;
213 for v in table.iter_mut() {
214 let raw_srca = ii / 255.0;
215 let srca = apply_contrast(raw_srca, adjusted_contrast);
216
217 *v = round_to_u8(255.0 * srca);
218 ii += 1.0;
219 }
220 } else {
221 let mut ii : f32 = 0.0;
223 for v in table.iter_mut() {
224 let raw_srca = ii / 255.0;
229 let srca = apply_contrast(raw_srca, adjusted_contrast);
230 assert!(srca <= 1.0);
231 let dsta = 1.0 - srca;
232
233 let lin_out = lin_src * srca + dsta * lin_dst;
235 assert!(lin_out <= 1.0);
236 let out = dst_convert.from_luma(lin_out);
237
238 let result = (out - dst) / (src - dst);
242
243 *v = round_to_u8(255.0 * result);
244 debug!("Setting {:?} to {:?}", ii as u8, *v);
245
246 ii += 1.0;
247 }
248 }
249}
250
251pub struct GammaLut {
252 tables: [[u8; 256]; 1 << LUM_BITS],
253 #[cfg(any(target_os="macos", target_os="ios"))]
254 cg_inverse_gamma: [u8; 256],
255}
256
257impl GammaLut {
258 fn generate_tables(&mut self, contrast: f32, paint_gamma: f32, device_gamma: f32) {
261 let paint_color_space = LuminanceColorSpace::new(paint_gamma);
262 let device_color_space = LuminanceColorSpace::new(device_gamma);
263
264 for (i, entry) in self.tables.iter_mut().enumerate() {
265 let luminance = scale255(LUM_BITS, i as u8);
266 build_gamma_correcting_lut(entry,
267 luminance,
268 contrast,
269 paint_color_space,
270 device_color_space);
271 }
272 }
273
274 pub fn table_count(&self) -> usize {
275 self.tables.len()
276 }
277
278 pub fn get_table(&self, color: u8) -> &[u8; 256] {
279 &self.tables[(color >> (8 - LUM_BITS)) as usize]
280 }
281
282 pub fn new(contrast: f32, paint_gamma: f32, device_gamma: f32) -> GammaLut {
283 #[cfg(any(target_os="macos", target_os="ios"))]
284 let mut table = GammaLut {
285 tables: [[0; 256]; 1 << LUM_BITS],
286 cg_inverse_gamma: get_inverse_gamma_table_coregraphics_smoothing(),
287 };
288 #[cfg(not(any(target_os="macos", target_os="ios")))]
289 let mut table = GammaLut {
290 tables: [[0; 256]; 1 << LUM_BITS],
291 };
292
293 table.generate_tables(contrast, paint_gamma, device_gamma);
294
295 table
296 }
297
298 pub fn preblend(&self, pixels: &mut [u8], color: ColorU) {
300 let table_r = self.get_table(color.r);
301 let table_g = self.get_table(color.g);
302 let table_b = self.get_table(color.b);
303
304 for pixel in pixels.chunks_mut(4) {
305 let (b, g, r) = (table_b[pixel[0] as usize], table_g[pixel[1] as usize], table_r[pixel[2] as usize]);
306 pixel[0] = b;
307 pixel[1] = g;
308 pixel[2] = r;
309 pixel[3] = max(max(b, g), r);
310 }
311 }
312
313 pub fn preblend_scaled(&self, pixels: &mut [u8], color: ColorU, percent: u8) {
315 if percent >= 100 {
316 self.preblend(pixels, color);
317 return;
318 }
319
320 let table_r = self.get_table(color.r);
321 let table_g = self.get_table(color.g);
322 let table_b = self.get_table(color.b);
323 let scale = (percent as i32 * 256) / 100;
324
325 for pixel in pixels.chunks_mut(4) {
326 let (mut b, g, mut r) = (
327 table_b[pixel[0] as usize] as i32,
328 table_g[pixel[1] as usize] as i32,
329 table_r[pixel[2] as usize] as i32,
330 );
331 b = g + (((b - g) * scale) >> 8);
332 r = g + (((r - g) * scale) >> 8);
333 pixel[0] = b as u8;
334 pixel[1] = g as u8;
335 pixel[2] = r as u8;
336 pixel[3] = max(max(b, g), r) as u8;
337 }
338 }
339
340 #[cfg(any(target_os="macos", target_os="ios"))]
341 pub fn coregraphics_convert_to_linear(&self, pixels: &mut [u8]) {
342 for pixel in pixels.chunks_mut(4) {
343 pixel[0] = self.cg_inverse_gamma[pixel[0] as usize];
344 pixel[1] = self.cg_inverse_gamma[pixel[1] as usize];
345 pixel[2] = self.cg_inverse_gamma[pixel[2] as usize];
346 }
347 }
348
349 pub fn preblend_grayscale(&self, pixels: &mut [u8], color: ColorU) {
351 let table_g = self.get_table(color.g);
352
353 for pixel in pixels.chunks_mut(4) {
354 let luminance = compute_luminance(pixel[2], pixel[1], pixel[0]);
355 let alpha = table_g[luminance as usize];
356 pixel[0] = alpha;
357 pixel[1] = alpha;
358 pixel[2] = alpha;
359 pixel[3] = alpha;
360 }
361 }
362
363} #[cfg(test)]
366mod tests {
367 use super::*;
368
369 fn over(dst: u32, src: u32, alpha: u32) -> u32 {
370 (src * alpha + dst * (255 - alpha))/255
371 }
372
373 fn overf(dst: f32, src: f32, alpha: f32) -> f32 {
374 ((src * alpha + dst * (255. - alpha))/255.) as f32
375 }
376
377
378 fn absdiff(a: u32, b: u32) -> u32 {
379 if a < b { b - a } else { a - b }
380 }
381
382 #[test]
383 fn gamma() {
384 let mut table = [0u8; 256];
385 let g = 2.0;
386 let space = LuminanceColorSpace::Gamma(g);
387 let mut src : u32 = 131;
388 while src < 256 {
389 build_gamma_correcting_lut(&mut table, src as u8, 0., space, space);
390 let mut max_diff = 0;
391 let mut dst = 0;
392 while dst < 256 {
393 for alpha in 0u32..256 {
394 let preblend = table[alpha as usize];
395 let lin_dst = (dst as f32 / 255.).powf(g) * 255.;
396 let lin_src = (src as f32 / 255.).powf(g) * 255.;
397
398 let preblend_result = over(dst, src, preblend as u32);
399 let true_result = ((overf(lin_dst, lin_src, alpha as f32) / 255.).powf(1. / g) * 255.) as u32;
400 let diff = absdiff(preblend_result, true_result);
401 max_diff = max(max_diff, diff);
403 }
404
405 assert!(max_diff <= 33);
407 dst += 1;
408
409 }
410 src += 1;
411 }
412 }
413}