1#[macro_use]
9extern crate log;
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13pub enum LuminanceColorSpace {
14 Linear,
16 Gamma(f32),
18 Srgb,
20}
21
22impl LuminanceColorSpace {
23 pub fn new(gamma: f32) -> LuminanceColorSpace {
24 if gamma == 1.0 {
25 LuminanceColorSpace::Linear
26 } else if gamma == 0.0 {
27 LuminanceColorSpace::Srgb
28 } else {
29 LuminanceColorSpace::Gamma(gamma)
30 }
31 }
32
33 pub fn to_luma(&self, luminance: f32) -> f32 {
34 match *self {
35 LuminanceColorSpace::Linear => luminance,
36 LuminanceColorSpace::Gamma(gamma) => luminance.powf(gamma),
37 LuminanceColorSpace::Srgb => {
38 if luminance <= 0.04045 {
41 luminance / 12.92
42 } else {
43 ((luminance + 0.055) / 1.055).powf(2.4)
44 }
45 }
46 }
47 }
48
49 pub fn from_luma(&self, luma: f32) -> f32 {
50 match *self {
51 LuminanceColorSpace::Linear => luma,
52 LuminanceColorSpace::Gamma(gamma) => luma.powf(1. / gamma),
53 LuminanceColorSpace::Srgb => {
54 if luma <= 0.0031308 {
57 luma * 12.92
58 } else {
59 1.055 * luma.powf(1./2.4) - 0.055
60 }
61 }
62 }
63 }
64}
65
66fn round_to_u8(x : f32) -> u8 {
68 let v = (x + 0.5).floor() as i32;
69 assert!(0 <= v && v < 0x100);
70 v as u8
71}
72
73fn scale255(n: u8, mut base: u8) -> u8 {
80 base <<= 8 - n;
81 let mut lum = base;
82 let mut i = n;
83
84 while i < 8 {
85 lum |= base >> i;
86 i += n;
87 }
88
89 lum
90}
91
92fn compute_luminance(r: u8, g: u8, b: u8) -> u8 {
95 let val: u32 = r as u32 * 54 + g as u32 * 183 + b as u32 * 19;
99 assert!(val < 0x10000);
100 (val >> 8) as u8
101}
102
103pub const LUM_BITS: u8 = 3;
105
106#[derive(Copy, Clone)]
107pub struct Color {
108 pub r: u8,
109 pub g: u8,
110 pub b: u8,
111 pub a: u8,
112}
113
114impl Color {
115 pub fn new(r: u8, g: u8, b: u8, a: u8) -> Color {
116 Color {
117 r: r,
118 g: g,
119 b: b,
120 a: a,
121 }
122 }
123
124 pub fn quantize(&self) -> Color {
127 Color::new(
128 scale255(LUM_BITS, self.r >> (8 - LUM_BITS)),
129 scale255(LUM_BITS, self.g >> (8 - LUM_BITS)),
130 scale255(LUM_BITS, self.b >> (8 - LUM_BITS)),
131 self.a,
132 )
133 }
134
135 pub fn luminance(&self) -> u8 {
138 compute_luminance(self.r, self.g, self.b)
139 }
140
141 pub fn luminance_color(&self) -> Color {
143 let lum = self.luminance();
144 Color::new(lum, lum, lum, self.a)
145 }
146}
147
148#[cfg(target_os="macos")]
153fn get_inverse_gamma_table_coregraphics_smoothing() -> [u8; 256] {
154 let mut table = [0u8; 256];
155
156 for (i, v) in table.iter_mut().enumerate() {
157 let x = i as f32 / 255.0;
158 *v = round_to_u8(x * x * 255.0);
159 }
160
161 table
162}
163
164fn apply_contrast(srca: f32, contrast: f32) -> f32 {
169 srca + ((1.0 - srca) * contrast * srca)
170}
171
172pub fn build_gamma_correcting_lut(table: &mut [u8; 256], src: u8, contrast: f32,
176 src_space: LuminanceColorSpace,
177 dst_convert: LuminanceColorSpace) {
178
179 let src = src as f32 / 255.0;
180 let lin_src = src_space.to_luma(src);
181 let dst = 1.0 - src;
186 let lin_dst = dst_convert.to_luma(dst);
187
188 let adjusted_contrast = contrast * lin_dst;
190
191 if (src - dst).abs() < (1.0 / 256.0) {
194 let mut ii : f32 = 0.0;
195 for v in table.iter_mut() {
196 let raw_srca = ii / 255.0;
197 let srca = apply_contrast(raw_srca, adjusted_contrast);
198
199 *v = round_to_u8(255.0 * srca);
200 ii += 1.0;
201 }
202 } else {
203 let mut ii : f32 = 0.0;
205 for v in table.iter_mut() {
206 let raw_srca = ii / 255.0;
211 let srca = apply_contrast(raw_srca, adjusted_contrast);
212 assert!(srca <= 1.0);
213 let dsta = 1.0 - srca;
214
215 let lin_out = lin_src * srca + dsta * lin_dst;
217 assert!(lin_out <= 1.0);
218 let out = dst_convert.from_luma(lin_out);
219
220 let result = (out - dst) / (src - dst);
224
225 *v = round_to_u8(255.0 * result);
226 debug!("Setting {:?} to {:?}", ii as u8, *v);
227
228 ii += 1.0;
229 }
230 }
231}
232
233pub struct GammaLut {
234 tables: [[u8; 256]; 1 << LUM_BITS],
235 #[cfg(target_os="macos")]
236 cg_inverse_gamma: [u8; 256],
237}
238
239impl GammaLut {
240 fn generate_tables(&mut self, contrast: f32, paint_gamma: f32, device_gamma: f32) {
243 let paint_color_space = LuminanceColorSpace::new(paint_gamma);
244 let device_color_space = LuminanceColorSpace::new(device_gamma);
245
246 for (i, entry) in self.tables.iter_mut().enumerate() {
247 let luminance = scale255(LUM_BITS, i as u8);
248 build_gamma_correcting_lut(entry,
249 luminance,
250 contrast,
251 paint_color_space,
252 device_color_space);
253 }
254 }
255
256 pub fn table_count(&self) -> usize {
257 self.tables.len()
258 }
259
260 pub fn get_table(&self, color: u8) -> &[u8; 256] {
261 &self.tables[(color >> (8 - LUM_BITS)) as usize]
262 }
263
264 pub fn new(contrast: f32, paint_gamma: f32, device_gamma: f32) -> GammaLut {
265 #[cfg(target_os="macos")]
266 let mut table = GammaLut {
267 tables: [[0; 256]; 1 << LUM_BITS],
268 cg_inverse_gamma: get_inverse_gamma_table_coregraphics_smoothing(),
269 };
270 #[cfg(not(target_os="macos"))]
271 let mut table = GammaLut {
272 tables: [[0; 256]; 1 << LUM_BITS],
273 };
274
275 table.generate_tables(contrast, paint_gamma, device_gamma);
276
277 table
278 }
279
280 pub fn preblend_default_colors_bgra(&self, pixels: &mut [u8], width: usize, height: usize) {
283 let preblend_color = Color::new(0x7f, 0x80, 0x7f, 0xff);
284 self.preblend_bgra(pixels, width, height, preblend_color);
285 }
286
287 fn replace_pixels_bgra(&self, pixels: &mut [u8], width: usize, height: usize,
288 table_r: &[u8; 256], table_g: &[u8; 256], table_b: &[u8; 256]) {
289 for y in 0..height {
290 let current_height = y * width * 4;
291
292 for pixel in pixels[current_height..current_height + (width * 4)].chunks_mut(4) {
293 pixel[0] = table_b[pixel[0] as usize];
294 pixel[1] = table_g[pixel[1] as usize];
295 pixel[2] = table_r[pixel[2] as usize];
296 }
298 }
299 }
300
301 fn replace_pixels_rgb(&self, pixels: &mut [u8], width: usize, height: usize,
303 table_r: &[u8; 256], table_g: &[u8; 256], table_b: &[u8; 256]) {
304 for y in 0..height {
305 let current_height = y * width * 3;
306
307 for pixel in pixels[current_height..current_height + (width * 3)].chunks_mut(3) {
308 pixel[0] = table_r[pixel[0] as usize];
309 pixel[1] = table_g[pixel[1] as usize];
310 pixel[2] = table_b[pixel[2] as usize];
311 }
312 }
313 }
314
315 pub fn preblend_bgra(&self, pixels: &mut [u8], width: usize, height: usize, color: Color) {
317 let table_r = self.get_table(color.r);
318 let table_g = self.get_table(color.g);
319 let table_b = self.get_table(color.b);
320
321 self.replace_pixels_bgra(pixels, width, height, table_r, table_g, table_b);
322 }
323
324 pub fn preblend_rgb(&self, pixels: &mut [u8], width: usize, height: usize, color: Color) {
327 let table_r = self.get_table(color.r);
328 let table_g = self.get_table(color.g);
329 let table_b = self.get_table(color.b);
330
331 self.replace_pixels_rgb(pixels, width, height, table_r, table_g, table_b);
332 }
333
334 #[cfg(target_os="macos")]
335 pub fn coregraphics_convert_to_linear_bgra(&self, pixels: &mut [u8], width: usize, height: usize) {
336 self.replace_pixels_bgra(pixels, width, height,
337 &self.cg_inverse_gamma,
338 &self.cg_inverse_gamma,
339 &self.cg_inverse_gamma);
340 }
341
342 pub fn preblend_grayscale_bgra(&self, pixels: &mut [u8], width: usize, height: usize, color: Color) {
344 let table_g = self.get_table(color.g);
345
346 for y in 0..height {
347 let current_height = y * width * 4;
348
349 for pixel in pixels[current_height..current_height + (width * 4)].chunks_mut(4) {
350 let luminance = compute_luminance(pixel[2], pixel[1], pixel[0]);
351 pixel[0] = table_g[luminance as usize];
352 pixel[1] = table_g[luminance as usize];
353 pixel[2] = table_g[luminance as usize];
354 pixel[3] = table_g[luminance as usize];
355 }
356 }
357 }
358
359} #[cfg(test)]
362mod tests {
363 use std::cmp;
364 use super::*;
365
366 fn over(dst: u32, src: u32, alpha: u32) -> u32 {
367 (src * alpha + dst * (255 - alpha))/255
368 }
369
370 fn overf(dst: f32, src: f32, alpha: f32) -> f32 {
371 ((src * alpha + dst * (255. - alpha))/255.) as f32
372 }
373
374
375 fn absdiff(a: u32, b: u32) -> u32 {
376 if a < b { b - a } else { a - b }
377 }
378
379 #[test]
380 fn gamma() {
381 let mut table = [0u8; 256];
382 let g = 2.0;
383 let space = LuminanceColorSpace::Gamma(g);
384 let mut src : u32 = 131;
385 while src < 256 {
386 build_gamma_correcting_lut(&mut table, src as u8, 0., space, space);
387 let mut max_diff = 0;
388 let mut dst = 0;
389 while dst < 256 {
390 for alpha in 0u32..256 {
391 let preblend = table[alpha as usize];
392 let lin_dst = (dst as f32 / 255.).powf(g) * 255.;
393 let lin_src = (src as f32 / 255.).powf(g) * 255.;
394
395 let preblend_result = over(dst, src, preblend as u32);
396 let true_result = ((overf(lin_dst, lin_src, alpha as f32) / 255.).powf(1. / g) * 255.) as u32;
397 let diff = absdiff(preblend_result, true_result);
398 max_diff = cmp::max(max_diff, diff);
400 }
401
402 assert!(max_diff <= 33);
404 dst += 1;
405
406 }
407 src += 1;
408 }
409 }
410}