transform_util.rs (21507B)
1 // qcms 2 // Copyright (C) 2009 Mozilla Foundation 3 // Copyright (C) 1998-2007 Marti Maria 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining 6 // a copy of this software and associated documentation files (the "Software"), 7 // to deal in the Software without restriction, including without limitation 8 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 // and/or sell copies of the Software, and to permit persons to whom the Software 10 // is furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 17 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 23 use crate::{ 24 iccread::{curveType, Profile}, 25 s15Fixed16Number_to_float, 26 }; 27 use crate::{matrix::Matrix, transform::PRECACHE_OUTPUT_MAX, transform::PRECACHE_OUTPUT_SIZE}; 28 29 //XXX: could use a bettername 30 pub type uint16_fract_t = u16; 31 32 #[inline] 33 fn u8Fixed8Number_to_float(x: u16) -> f32 { 34 // 0x0000 = 0. 35 // 0x0100 = 1. 36 // 0xffff = 255 + 255/256 37 (x as i32 as f64 / 256.0) as f32 38 } 39 #[inline] 40 pub fn clamp_float(a: f32) -> f32 { 41 /* One would naturally write this function as the following: 42 if (a > 1.) 43 return 1.; 44 else if (a < 0) 45 return 0; 46 else 47 return a; 48 49 However, that version will let NaNs pass through which is undesirable 50 for most consumers. 51 */ 52 if a > 1. { 53 1. 54 } else if a >= 0. { 55 a 56 } else { 57 // a < 0 or a is NaN 58 0. 59 } 60 } 61 /* value must be a value between 0 and 1 */ 62 //XXX: is the above a good restriction to have? 63 // the output range of this functions is 0..1 64 pub fn lut_interp_linear(mut input_value: f64, table: &[u16]) -> f32 { 65 if table.is_empty() { 66 return input_value as f32; 67 } 68 69 input_value *= (table.len() - 1) as f64; 70 71 let upper: i32 = input_value.ceil() as i32; 72 let lower: i32 = input_value.floor() as i32; 73 let value: f32 = ((table[(upper as usize).min(table.len() - 1)] as f64) 74 * (1. - (upper as f64 - input_value)) 75 + (table[(lower as usize).min(table.len() - 1)] as f64 * (upper as f64 - input_value))) 76 as f32; 77 /* scale the value */ 78 value * (1.0 / 65535.0) 79 } 80 /* same as above but takes and returns a uint16_t value representing a range from 0..1 */ 81 #[no_mangle] 82 pub fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 { 83 /* Start scaling input_value to the length of the array: 65535*(length-1). 84 * We'll divide out the 65535 next */ 85 let mut value: u32 = (input_value as i32 * (table.len() as i32 - 1)) as u32; /* equivalent to ceil(value/65535) */ 86 let upper: u32 = (value + 65534) / 65535; /* equivalent to floor(value/65535) */ 87 let lower: u32 = value / 65535; 88 /* interp is the distance from upper to value scaled to 0..65535 */ 89 let interp: u32 = value % 65535; // 0..65535*65535 90 value = (table[upper as usize] as u32 * interp 91 + table[lower as usize] as u32 * (65535 - interp)) 92 / 65535; 93 value as u16 94 } 95 /* same as above but takes an input_value from 0..PRECACHE_OUTPUT_MAX 96 * and returns a uint8_t value representing a range from 0..1 */ 97 fn lut_interp_linear_precache_output(input_value: u32, table: &[u16]) -> u8 { 98 /* Start scaling input_value to the length of the array: PRECACHE_OUTPUT_MAX*(length-1). 99 * We'll divide out the PRECACHE_OUTPUT_MAX next */ 100 let mut value: u32 = input_value * (table.len() - 1) as u32; 101 /* equivalent to ceil(value/PRECACHE_OUTPUT_MAX) */ 102 let upper: u32 = (value + PRECACHE_OUTPUT_MAX as u32 - 1) / PRECACHE_OUTPUT_MAX as u32; 103 /* equivalent to floor(value/PRECACHE_OUTPUT_MAX) */ 104 let lower: u32 = value / PRECACHE_OUTPUT_MAX as u32; 105 /* interp is the distance from upper to value scaled to 0..PRECACHE_OUTPUT_MAX */ 106 let interp: u32 = value % PRECACHE_OUTPUT_MAX as u32; 107 /* the table values range from 0..65535 */ 108 value = table[upper as usize] as u32 * interp 109 + table[lower as usize] as u32 * (PRECACHE_OUTPUT_MAX as u32 - interp); // 0..(65535*PRECACHE_OUTPUT_MAX) 110 /* round and scale */ 111 value += (PRECACHE_OUTPUT_MAX * 65535 / 255 / 2) as u32; // scale to 0..255 112 value /= (PRECACHE_OUTPUT_MAX * 65535 / 255) as u32; 113 value as u8 114 } 115 /* value must be a value between 0 and 1 */ 116 //XXX: is the above a good restriction to have? 117 pub fn lut_interp_linear_float(mut value: f32, table: &[f32]) -> f32 { 118 value *= (table.len() - 1) as f32; 119 120 let upper: i32 = value.ceil() as i32; 121 let lower: i32 = value.floor() as i32; 122 //XXX: can we be more performant here? 123 value = (table[upper as usize] as f64 * (1.0f64 - (upper as f32 - value) as f64) 124 + (table[lower as usize] * (upper as f32 - value)) as f64) as f32; 125 /* scale the value */ 126 value 127 } 128 129 fn compute_curve_gamma_table_type1(gamma_table: &mut [f32; 256], gamma: u16) { 130 let gamma_float: f32 = u8Fixed8Number_to_float(gamma); 131 for (i, g) in gamma_table.iter_mut().enumerate() { 132 // 0..1^(0..255 + 255/256) will always be between 0 and 1 133 *g = (i as f64 / 255.0).powf(gamma_float as f64) as f32; 134 } 135 } 136 137 fn compute_curve_gamma_table_type2(gamma_table: &mut [f32; 256], table: &[u16]) { 138 for (i, g) in gamma_table.iter_mut().enumerate() { 139 *g = lut_interp_linear(i as f64 / 255.0, table); 140 } 141 } 142 143 fn compute_curve_gamma_table_type_parametric(gamma_table: &mut [f32; 256], params: &[f32]) { 144 let params = Param::new(params); 145 for (i, g) in gamma_table.iter_mut().enumerate() { 146 let X = i as f32 / 255.; 147 *g = clamp_float(params.eval(X)); 148 } 149 } 150 151 fn compute_curve_gamma_table_type0(gamma_table: &mut [f32; 256]) { 152 for (i, g) in gamma_table.iter_mut().enumerate() { 153 *g = (i as f64 / 255.0) as f32; 154 } 155 } 156 157 #[inline(always)] 158 pub(crate) fn build_input_gamma_table(TRC: &curveType) -> [f32; 256] { 159 let mut gamma_table = [0.; 256]; 160 match TRC { 161 curveType::Parametric(params) => { 162 compute_curve_gamma_table_type_parametric(&mut gamma_table, params) 163 } 164 curveType::Curve(data) => match data.len() { 165 0 => compute_curve_gamma_table_type0(&mut gamma_table), 166 1 => compute_curve_gamma_table_type1(&mut gamma_table, data[0]), 167 _ => compute_curve_gamma_table_type2(&mut gamma_table, data), 168 }, 169 }; 170 gamma_table 171 } 172 173 pub fn build_colorant_matrix(p: &Profile) -> Matrix { 174 let mut result: Matrix = Matrix { m: [[0.; 3]; 3] }; 175 result.m[0][0] = s15Fixed16Number_to_float(p.redColorant.X); 176 result.m[0][1] = s15Fixed16Number_to_float(p.greenColorant.X); 177 result.m[0][2] = s15Fixed16Number_to_float(p.blueColorant.X); 178 result.m[1][0] = s15Fixed16Number_to_float(p.redColorant.Y); 179 result.m[1][1] = s15Fixed16Number_to_float(p.greenColorant.Y); 180 result.m[1][2] = s15Fixed16Number_to_float(p.blueColorant.Y); 181 result.m[2][0] = s15Fixed16Number_to_float(p.redColorant.Z); 182 result.m[2][1] = s15Fixed16Number_to_float(p.greenColorant.Z); 183 result.m[2][2] = s15Fixed16Number_to_float(p.blueColorant.Z); 184 result 185 } 186 187 /** Parametric representation of transfer function */ 188 #[derive(Debug)] 189 struct Param { 190 g: f32, 191 a: f32, 192 b: f32, 193 c: f32, 194 d: f32, 195 e: f32, 196 f: f32, 197 } 198 199 impl Param { 200 #[allow(clippy::many_single_char_names)] 201 fn new(params: &[f32]) -> Param { 202 // convert from the variable number of parameters 203 // contained in profiles to a unified representation. 204 let g: f32 = params[0]; 205 match params[1..] { 206 [] => Param { 207 g, 208 a: 1., 209 b: 0., 210 c: 1., 211 d: 0., 212 e: 0., 213 f: 0., 214 }, 215 [a, b] => Param { 216 g, 217 a, 218 b, 219 c: 0., 220 d: -b / a, 221 e: 0., 222 f: 0., 223 }, 224 [a, b, c] => Param { 225 g, 226 a, 227 b, 228 c: 0., 229 d: -b / a, 230 e: c, 231 f: c, 232 }, 233 [a, b, c, d] => Param { 234 g, 235 a, 236 b, 237 c, 238 d, 239 e: 0., 240 f: 0., 241 }, 242 [a, b, c, d, e, f] => Param { 243 g, 244 a, 245 b, 246 c, 247 d, 248 e, 249 f, 250 }, 251 _ => panic!(), 252 } 253 } 254 255 fn eval(&self, x: f32) -> f32 { 256 if x < self.d { 257 self.c * x + self.f 258 } else { 259 (self.a * x + self.b).powf(self.g) + self.e 260 } 261 } 262 #[allow(clippy::many_single_char_names)] 263 fn invert(&self) -> Option<Param> { 264 // First check if the function is continuous at the cross-over point d. 265 let d1 = (self.a * self.d + self.b).powf(self.g) + self.e; 266 let d2 = self.c * self.d + self.f; 267 268 if (d1 - d2).abs() > 0.1 { 269 return None; 270 } 271 let d = d1; 272 273 // y = (a * x + b)^g + e 274 // y - e = (a * x + b)^g 275 // (y - e)^(1/g) = a*x + b 276 // (y - e)^(1/g) - b = a*x 277 // (y - e)^(1/g)/a - b/a = x 278 // ((y - e)/a^g)^(1/g) - b/a = x 279 // ((1/(a^g)) * y - e/(a^g))^(1/g) - b/a = x 280 let a = 1. / self.a.powf(self.g); 281 let b = -self.e / self.a.powf(self.g); 282 let g = 1. / self.g; 283 let e = -self.b / self.a; 284 285 // y = c * x + f 286 // y - f = c * x 287 // y/c - f/c = x 288 let (c, f); 289 if d <= 0. { 290 c = 1.; 291 f = 0.; 292 } else { 293 c = 1. / self.c; 294 f = -self.f / self.c; 295 } 296 297 // if self.d > 0. and self.c == 0 as is likely with type 1 and 2 parametric function 298 // then c and f will not be finite. 299 if !(g.is_finite() 300 && a.is_finite() 301 && b.is_finite() 302 && c.is_finite() 303 && d.is_finite() 304 && e.is_finite() 305 && f.is_finite()) 306 { 307 return None; 308 } 309 310 Some(Param { 311 g, 312 a, 313 b, 314 c, 315 d, 316 e, 317 f, 318 }) 319 } 320 } 321 322 #[test] 323 fn param_invert() { 324 let p3 = Param::new(&[2.4, 0.948, 0.052, 0.077, 0.04]); 325 p3.invert().unwrap(); 326 let g2_2 = Param::new(&[2.2]); 327 g2_2.invert().unwrap(); 328 let g2_2 = Param::new(&[2.2, 0.9, 0.052]); 329 g2_2.invert().unwrap(); 330 let g2_2 = dbg!(Param::new(&[2.2, 0.9, -0.52])); 331 g2_2.invert().unwrap(); 332 let g2_2 = dbg!(Param::new(&[2.2, 0.9, -0.52, 0.1])); 333 assert!(g2_2.invert().is_none()); 334 } 335 336 /* The following code is copied nearly directly from lcms. 337 * I think it could be much better. For example, Argyll seems to have better code in 338 * icmTable_lookup_bwd and icmTable_setup_bwd. However, for now this is a quick way 339 * to a working solution and allows for easy comparing with lcms. */ 340 #[no_mangle] 341 #[allow(clippy::many_single_char_names)] 342 pub fn lut_inverse_interp16(Value: u16, LutTable: &[u16]) -> uint16_fract_t { 343 let mut l: i32 = 1; // 'int' Give spacing for negative values 344 let mut r: i32 = 0x10000; 345 let mut x: i32 = 0; 346 let mut res: i32; 347 let length = LutTable.len() as i32; 348 349 let mut NumZeroes: i32 = 0; 350 while LutTable[NumZeroes as usize] as i32 == 0 && NumZeroes < length - 1 { 351 NumZeroes += 1 352 } 353 // There are no zeros at the beginning and we are trying to find a zero, so 354 // return anything. It seems zero would be the less destructive choice 355 /* I'm not sure that this makes sense, but oh well... */ 356 if NumZeroes == 0 && Value as i32 == 0 { 357 return 0u16; 358 } 359 let mut NumPoles: i32 = 0; 360 while LutTable[(length - 1 - NumPoles) as usize] as i32 == 0xffff && NumPoles < length - 1 { 361 NumPoles += 1 362 } 363 // Does the curve belong to this case? 364 if NumZeroes > 1 || NumPoles > 1 { 365 let a_0: i32; 366 let b_0: i32; 367 // Identify if value fall downto 0 or FFFF zone 368 if Value as i32 == 0 { 369 return 0u16; 370 } 371 // if (Value == 0xFFFF) return 0xFFFF; 372 // else restrict to valid zone 373 if NumZeroes > 1 { 374 a_0 = (NumZeroes - 1) * 0xffff / (length - 1); 375 l = a_0 - 1 376 } 377 if NumPoles > 1 { 378 b_0 = (length - 1 - NumPoles) * 0xffff / (length - 1); 379 r = b_0 + 1 380 } 381 } 382 if r <= l { 383 // If this happens LutTable is not invertible 384 return 0u16; 385 } 386 // Seems not a degenerated case... apply binary search 387 while r > l { 388 x = (l + r) / 2; 389 res = lut_interp_linear16((x - 1) as uint16_fract_t, LutTable) as i32; 390 if res == Value as i32 { 391 // Found exact match. 392 return (x - 1) as uint16_fract_t; 393 } 394 if res > Value as i32 { 395 r = x - 1 396 } else { 397 l = x + 1 398 } 399 } 400 401 // Not found, should we interpolate? 402 403 // Get surrounding nodes 404 debug_assert!(x >= 1); 405 406 let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0); 407 let cell0: i32 = val2.floor() as i32; 408 let cell1: i32 = val2.ceil() as i32; 409 if cell0 == cell1 { 410 return x as uint16_fract_t; 411 } 412 413 let y0: f64 = LutTable[cell0 as usize] as f64; 414 let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64; 415 let y1: f64 = LutTable[cell1 as usize] as f64; 416 let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64; 417 let a: f64 = (y1 - y0) / (x1 - x0); 418 let b: f64 = y0 - a * x0; 419 if a.abs() < 0.01f64 { 420 return x as uint16_fract_t; 421 } 422 let f: f64 = (Value as i32 as f64 - b) / a; 423 if f < 0.0 { 424 return 0u16; 425 } 426 if f >= 65535.0 { 427 return 0xffffu16; 428 } 429 (f + 0.5f64).floor() as uint16_fract_t 430 } 431 /* 432 The number of entries needed to invert a lookup table should not 433 necessarily be the same as the original number of entries. This is 434 especially true of lookup tables that have a small number of entries. 435 436 For example: 437 Using a table like: 438 {0, 3104, 14263, 34802, 65535} 439 invert_lut will produce an inverse of: 440 {3, 34459, 47529, 56801, 65535} 441 which has an maximum error of about 9855 (pixel difference of ~38.346) 442 443 For now, we punt the decision of output size to the caller. */ 444 fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> { 445 /* for now we invert the lut by creating a lut of size out_length 446 * and attempting to lookup a value for each entry using lut_inverse_interp16 */ 447 let mut output = Vec::with_capacity(out_length); 448 for i in 0..out_length { 449 let x: f64 = i as f64 * 65535.0 / (out_length - 1) as f64; 450 let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t; 451 output.push(lut_inverse_interp16(input, table)); 452 } 453 output 454 } 455 #[allow(clippy::needless_range_loop)] 456 fn compute_precache_pow(output: &mut [u8; PRECACHE_OUTPUT_SIZE], gamma: f32) { 457 for v in 0..PRECACHE_OUTPUT_SIZE { 458 //XXX: don't do integer/float conversion... and round? 459 output[v] = (255. * (v as f32 / PRECACHE_OUTPUT_MAX as f32).powf(gamma)) as u8; 460 } 461 } 462 #[allow(clippy::needless_range_loop)] 463 pub fn compute_precache_lut(output: &mut [u8; PRECACHE_OUTPUT_SIZE], table: &[u16]) { 464 for v in 0..PRECACHE_OUTPUT_SIZE { 465 output[v] = lut_interp_linear_precache_output(v as u32, table); 466 } 467 } 468 #[allow(clippy::needless_range_loop)] 469 pub fn compute_precache_linear(output: &mut [u8; PRECACHE_OUTPUT_SIZE]) { 470 for v in 0..PRECACHE_OUTPUT_SIZE { 471 //XXX: round? 472 output[v] = (v / (PRECACHE_OUTPUT_SIZE / 256)) as u8; 473 } 474 } 475 pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPUT_SIZE]) { 476 match trc { 477 curveType::Parametric(params) => { 478 let mut gamma_table_uint: [u16; 256] = [0; 256]; 479 480 let mut inverted_size: usize = 256; 481 let mut gamma_table = [0.; 256]; 482 compute_curve_gamma_table_type_parametric(&mut gamma_table, params); 483 let mut i: u16 = 0u16; 484 while (i as i32) < 256 { 485 gamma_table_uint[i as usize] = (gamma_table[i as usize] * 65535f32) as u16; 486 i += 1 487 } 488 //XXX: the choice of a minimum of 256 here is not backed by any theory, 489 // measurement or data, however it is what lcms uses. 490 // the maximum number we would need is 65535 because that's the 491 // accuracy used for computing the pre cache table 492 if inverted_size < 256 { 493 inverted_size = 256 494 } 495 let inverted = invert_lut(&gamma_table_uint, inverted_size); 496 compute_precache_lut(output, &inverted); 497 } 498 curveType::Curve(data) => { 499 match data.len() { 500 0 => compute_precache_linear(output), 501 1 => compute_precache_pow(output, 1. / u8Fixed8Number_to_float(data[0])), 502 _ => { 503 let mut inverted_size = data.len(); 504 //XXX: the choice of a minimum of 256 here is not backed by any theory, 505 // measurement or data, however it is what lcms uses. 506 // the maximum number we would need is 65535 because that's the 507 // accuracy used for computing the pre cache table 508 if inverted_size < 256 { 509 inverted_size = 256 510 } //XXX turn this conversion into a function 511 let inverted = invert_lut(data, inverted_size); 512 compute_precache_lut(output, &inverted); 513 } 514 } 515 } 516 } 517 } 518 fn build_linear_table(length: usize) -> Vec<u16> { 519 let mut output = Vec::with_capacity(length); 520 for i in 0..length { 521 let x: f64 = i as f64 * 65535.0 / (length - 1) as f64; 522 let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t; 523 output.push(input); 524 } 525 output 526 } 527 fn build_pow_table(gamma: f32, length: usize) -> Vec<u16> { 528 let mut output = Vec::with_capacity(length); 529 for i in 0..length { 530 let mut x: f64 = i as f64 / (length - 1) as f64; 531 x = x.powf(gamma as f64); 532 let result: uint16_fract_t = (x * 65535.0 + 0.5f64).floor() as uint16_fract_t; 533 output.push(result); 534 } 535 output 536 } 537 538 fn to_lut(params: &Param, len: usize) -> Vec<u16> { 539 let mut output = Vec::with_capacity(len); 540 for i in 0..len { 541 let X = i as f32 / (len-1) as f32; 542 output.push((params.eval(X) * 65535.) as u16); 543 } 544 output 545 } 546 547 pub(crate) fn build_lut_for_linear_from_tf(trc: &curveType, 548 lut_len: Option<usize>) -> Vec<u16> { 549 match trc { 550 curveType::Parametric(params) => { 551 let lut_len = lut_len.unwrap_or(256); 552 let params = Param::new(params); 553 to_lut(¶ms, lut_len) 554 }, 555 curveType::Curve(data) => { 556 let autogen_lut_len = lut_len.unwrap_or(4096); 557 match data.len() { 558 0 => build_linear_table(autogen_lut_len), 559 1 => { 560 let gamma = u8Fixed8Number_to_float(data[0]); 561 build_pow_table(gamma, autogen_lut_len) 562 } 563 _ => { 564 let lut_len = lut_len.unwrap_or(data.len()); 565 assert_eq!(lut_len, data.len()); 566 data.clone() // I feel bad about this. 567 } 568 } 569 }, 570 } 571 } 572 573 pub(crate) fn build_lut_for_tf_from_linear(trc: &curveType) -> Option<Vec<u16>> { 574 match trc { 575 curveType::Parametric(params) => { 576 let lut_len = 256; 577 let params = Param::new(params); 578 if let Some(inv_params) = params.invert() { 579 return Some(to_lut(&inv_params, lut_len)); 580 } 581 // else return None instead of fallthrough to generic lut inversion. 582 return None; 583 }, 584 curveType::Curve(data) => { 585 let autogen_lut_len = 4096; 586 match data.len() { 587 0 => { 588 return Some(build_linear_table(autogen_lut_len)); 589 }, 590 1 => { 591 let gamma = 1. / u8Fixed8Number_to_float(data[0]); 592 return Some(build_pow_table(gamma, autogen_lut_len)); 593 }, 594 _ => {}, 595 } 596 }, 597 } 598 599 let linear_from_tf = build_lut_for_linear_from_tf(trc, None); 600 601 //XXX: the choice of a minimum of 256 here is not backed by any theory, 602 // measurement or data, however it is what lcms uses. 603 let inverted_lut_len = std::cmp::max(linear_from_tf.len(), 256); 604 Some(invert_lut(&linear_from_tf, inverted_lut_len)) 605 } 606 607 pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> { 608 build_lut_for_tf_from_linear(trc) 609 }