Bug 1799258 - [qcms] Add query for profile data and lut tables. r=jrmuizel

Differential Revision: https://phabricator.services.mozilla.com/D163663
This commit is contained in:
Kelsey Gilbert 2023-03-13 21:04:09 +00:00
Родитель 5cf668fe9b
Коммит 3bd71468e2
6 изменённых файлов: 261 добавлений и 59 удалений

Просмотреть файл

@ -11,35 +11,35 @@ extern "C" {
#ifndef ICC_H
/* icc34 defines */
/*****************************************************************
/*****************************************************************
Copyright (c) 1994-1996 SunSoft, Inc.
Rights Reserved
Permission is hereby granted, free of charge, to any person
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restrict-
ion, including without limitation the rights to use, copy, modify,
merge, publish distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
files (the "Software"), to deal in the Software without restrict-
ion, including without limitation the rights to use, copy, modify,
merge, publish distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of SunSoft, Inc.
shall not be used in advertising or otherwise to promote the
sale, use or other dealings in this Software without written
authorization from SunSoft Inc.
INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of SunSoft, Inc.
shall not be used in advertising or otherwise to promote the
sale, use or other dealings in this Software without written
authorization from SunSoft Inc.
******************************************************************/
/*
@ -48,11 +48,11 @@ authorization from SunSoft Inc.
* don't use the same objects on different threads at the same time.
*/
/*
/*
* Color Space Signatures
* Note that only icSigXYZData and icSigLabData are valid
* Profile Connection Spaces (PCSs)
*/
*/
typedef enum {
icSigXYZData = 0x58595A20L, /* 'XYZ ' */
icSigLabData = 0x4C616220L, /* 'Lab ' */
@ -79,12 +79,13 @@ typedef enum {
icSig13colorData = 0x44434C52L, /* 'DCLR' */
icSig14colorData = 0x45434C52L, /* 'ECLR' */
icSig15colorData = 0x46434C52L, /* 'FCLR' */
icMaxEnumData = 0xFFFFFFFFL
icMaxEnumData = 0xFFFFFFFFL
} icColorSpaceSignature;
#endif
#include <stdio.h>
#include <stdbool.h>
#include <cstdint>
struct _qcms_transform;
typedef struct _qcms_transform qcms_transform;
@ -196,6 +197,42 @@ void qcms_enable_iccv4();
void qcms_enable_neon();
void qcms_enable_avx();
// -
/*
struct qcms_mat3r3 {
struct row {
float cols[3];
};
row rows[3];
};
*/
struct qcms_profile_data {
uint32_t class_type;
uint32_t color_space;
uint32_t pcs;
qcms_intent rendering_intent;
float red_colorant_xyzd50[3];
float blue_colorant_xyzd50[3];
float green_colorant_xyzd50[3];
int32_t linear_from_trc_red_samples;
int32_t linear_from_trc_blue_samples;
int32_t linear_from_trc_green_samples;
};
void qcms_profile_get_data(const qcms_profile*, qcms_profile_data* out_data);
enum class qcms_color_channel : uint8_t {
Red,
Green,
Blue,
};
void qcms_profile_get_lut(const qcms_profile*, qcms_color_channel,
float* begin, float* end);
#ifdef __cplusplus
}
#endif

Просмотреть файл

@ -7,9 +7,11 @@ use libc::{fclose, fopen, fread, free, malloc, memset, FILE};
use crate::{
double_to_s15Fixed16Number,
iccread::*,
s15Fixed16Number_to_float,
transform::get_rgb_colorants,
transform::DataType,
transform::{qcms_transform, transform_create},
transform_util,
Intent,
};
@ -375,6 +377,124 @@ pub unsafe extern "C" fn qcms_transform_data(
length,
);
}
/*
use crate::matrix;
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct qcms_mat3r3 {
pub rows: [[f32; 3] ; 3],
}
impl qcms_mat3r3 {
fn from(m: matrix::Matrix) -> qcms_mat3r3 {
qcms_mat3r3{
rows: [
m.row(0),
m.row(1),
m.row(2),
],
}
}
}
*/
#[repr(C)]
#[derive(Clone, Debug, Default)]
#[allow(clippy::upper_case_acronyms)]
pub struct qcms_profile_data {
pub class_type: u32,
pub color_space: u32,
pub pcs: u32,
pub rendering_intent: Intent,
pub red_colorant_xyzd50: [f32; 3],
pub blue_colorant_xyzd50: [f32; 3],
pub green_colorant_xyzd50: [f32; 3],
// Number of samples in the e.g. gamma->linear LUT.
pub linear_from_trc_red_samples: i32,
pub linear_from_trc_blue_samples: i32,
pub linear_from_trc_green_samples: i32,
}
pub use crate::iccread::Profile as qcms_profile;
#[no_mangle]
pub extern "C" fn qcms_profile_get_data(
profile: &qcms_profile,
out_data: &mut qcms_profile_data,
) {
out_data.class_type = profile.class_type;
out_data.color_space = profile.color_space;
out_data.pcs = profile.pcs;
out_data.rendering_intent = profile.rendering_intent;
fn colorant(c: &XYZNumber) -> [f32;3] {
[c.X, c.Y, c.Z].map(s15Fixed16Number_to_float)
}
out_data.red_colorant_xyzd50 = colorant(&profile.redColorant);
out_data.blue_colorant_xyzd50 = colorant(&profile.blueColorant);
out_data.green_colorant_xyzd50 = colorant(&profile.greenColorant);
fn trc_to_samples(trc: &Option<Box<curveType>>) -> i32 {
if let Some(ref trc) = *trc {
match &**trc {
curveType::Curve(v) => {
let len = v.len();
if len <= 1 {
-1
} else {
len as i32
}
},
curveType::Parametric(_) => -1,
}
} else {
0
}
}
out_data.linear_from_trc_red_samples = trc_to_samples(&profile.redTRC);
out_data.linear_from_trc_blue_samples = trc_to_samples(&profile.blueTRC);
out_data.linear_from_trc_green_samples = trc_to_samples(&profile.greenTRC);
}
#[repr(u8)]
pub enum qcms_color_channel {
Red,
Green,
Blue,
}
#[no_mangle]
pub extern "C" fn qcms_profile_get_lut(
profile: &qcms_profile,
channel: qcms_color_channel, // FYI: UB if you give Rust something out of range!
out_begin: *mut f32,
out_end: *mut f32,
) {
let out_slice = unsafe {
std::slice::from_raw_parts_mut(out_begin, out_end.offset_from(out_begin) as usize)
};
let trc = match channel {
qcms_color_channel::Red => &profile.redTRC,
qcms_color_channel::Green => &profile.greenTRC,
qcms_color_channel::Blue => &profile.blueTRC,
};
let samples_u16 = if let Some(trc) = trc {
let trc = &*trc;
// Yes, sub-optimal, but easier to implement, and these aren't big or hot:
// 1. Ask for a new vec<u16> lut based on the trc.
// * (eat the extra alloc)
// 2. Convert the u16s back out to f32s in our slice.
// * (eat the copy and quantization error from f32->u16->f32 roundtrip)
transform_util::build_lut_for_linear_from_tf(trc, Some(out_slice.len()))
} else {
Vec::new()
};
assert_eq!(samples_u16.len(), out_slice.len());
for (d, s) in out_slice.iter_mut().zip(samples_u16.into_iter()) {
*d = (s as f32) / (u16::MAX as f32);
}
}
pub type icColorSpaceSignature = u32;
pub const icSigGrayData: icColorSpaceSignature = 0x47524159; // 'GRAY'
@ -382,7 +502,6 @@ pub const icSigRgbData: icColorSpaceSignature = 0x52474220; // 'RGB '
pub const icSigCmykData: icColorSpaceSignature = 0x434d594b; // 'CMYK'
pub use crate::iccread::qcms_profile_is_bogus;
pub use crate::iccread::Profile as qcms_profile;
pub use crate::transform::{
qcms_enable_iccv4, qcms_profile_precache_output_transform, qcms_transform_release,
};

Просмотреть файл

@ -50,6 +50,8 @@ pub struct Profile {
pub(crate) redColorant: XYZNumber,
pub(crate) blueColorant: XYZNumber,
pub(crate) greenColorant: XYZNumber,
// "TRC" is EOTF, e.g. gamma->linear transfer function.
// Because ICC profiles are phrased as decodings to the xyzd50-linear PCS.
pub(crate) redTRC: Option<Box<curveType>>,
pub(crate) blueTRC: Option<Box<curveType>>,
pub(crate) greenTRC: Option<Box<curveType>>,
@ -93,7 +95,7 @@ pub(crate) struct lutmABType {
}
#[derive(Clone, Debug)]
pub(crate) enum curveType {
Curve(Vec<uInt16Number>),
Curve(Vec<uInt16Number>), // len=0 => Linear, len=1 => Gamma(v[0]), _ => lut
/// The ICC parametricCurveType is specified in terms of s15Fixed16Number,
/// so it's possible to use this variant to specify greater precision than
/// any raw ICC profile could

Просмотреть файл

@ -11,10 +11,11 @@
#![cfg_attr(
feature = "neon",
feature(arm_target_feature, raw_ref_op)
)]
/// These values match the Rendering Intent values from the ICC spec
#[repr(u32)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum Intent {
AbsoluteColorimetric = 3,

Просмотреть файл

@ -22,7 +22,7 @@
#[derive(Copy, Clone, Debug, Default)]
pub struct Matrix {
pub m: [[f32; 3]; 3],
pub m: [[f32; 3]; 3], // Three rows of three elems.
}
#[derive(Copy, Clone)]
@ -39,6 +39,10 @@ impl Matrix {
result
}
pub fn row(&self, r: usize) -> [f32; 3] {
self.m[r]
}
//probably reuse this computation in matrix_invert
pub fn det(&self) -> f32 {
let det: f32 = self.m[0][0] * self.m[1][1] * self.m[2][2]

Просмотреть файл

@ -440,10 +440,10 @@ invert_lut will produce an inverse of:
which has an maximum error of about 9855 (pixel difference of ~38.346)
For now, we punt the decision of output size to the caller. */
fn invert_lut(table: &[u16], out_length: i32) -> Vec<u16> {
fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
/* for now we invert the lut by creating a lut of size out_length
* and attempting to lookup a value for each entry using lut_inverse_interp16 */
let mut output = Vec::with_capacity(out_length as usize);
let mut output = Vec::with_capacity(out_length);
for i in 0..out_length {
let x: f64 = i as f64 * 65535.0f64 / (out_length - 1) as f64;
let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
@ -476,7 +476,7 @@ pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPU
curveType::Parametric(params) => {
let mut gamma_table_uint: [u16; 256] = [0; 256];
let mut inverted_size: i32 = 256;
let mut inverted_size: usize = 256;
let gamma_table = compute_curve_gamma_table_type_parametric(params);
let mut i: u16 = 0u16;
while (i as i32) < 256 {
@ -498,7 +498,7 @@ pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPU
0 => compute_precache_linear(output),
1 => compute_precache_pow(output, 1. / u8Fixed8Number_to_float(data[0])),
_ => {
let mut inverted_size = data.len() as i32;
let mut inverted_size = data.len();
//XXX: the choice of a minimum of 256 here is not backed by any theory,
// measurement or data, however it is what lcms uses.
// the maximum number we would need is 65535 because that's the
@ -514,8 +514,8 @@ pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPU
}
true
}
fn build_linear_table(length: i32) -> Vec<u16> {
let mut output = Vec::with_capacity(length as usize);
fn build_linear_table(length: usize) -> Vec<u16> {
let mut output = Vec::with_capacity(length);
for i in 0..length {
let x: f64 = i as f64 * 65535.0f64 / (length - 1) as f64;
let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
@ -523,8 +523,8 @@ fn build_linear_table(length: i32) -> Vec<u16> {
}
output
}
fn build_pow_table(gamma: f32, length: i32) -> Vec<u16> {
let mut output = Vec::with_capacity(length as usize);
fn build_pow_table(gamma: f32, length: usize) -> Vec<u16> {
let mut output = Vec::with_capacity(length);
for i in 0..length {
let mut x: f64 = i as f64 / (length - 1) as f64;
x = x.powf(gamma as f64);
@ -534,36 +534,75 @@ fn build_pow_table(gamma: f32, length: i32) -> Vec<u16> {
output
}
pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> {
fn to_lut(params: &Param, len: usize) -> Vec<u16> {
let mut output = Vec::with_capacity(len);
for i in 0..len {
let X = i as f32 / (len-1) as f32;
output.push((params.eval(X) * 65535.) as u16);
}
output
}
pub(crate) fn build_lut_for_linear_from_tf(trc: &curveType,
lut_len: Option<usize>) -> Vec<u16> {
match trc {
curveType::Parametric(params) => {
let lut_len = lut_len.unwrap_or(256);
let params = Param::new(params);
let inv_params = params.invert()?;
let mut output = Vec::with_capacity(256);
for i in 0..256 {
let X = i as f32 / 255.;
output.push((inv_params.eval(X) * 65535.) as u16);
}
Some(output)
}
to_lut(&params, lut_len)
},
curveType::Curve(data) => {
let autogen_lut_len = lut_len.unwrap_or(4096);
match data.len() {
0 => Some(build_linear_table(4096)),
0 => build_linear_table(autogen_lut_len),
1 => {
let gamma = 1. / u8Fixed8Number_to_float(data[0]);
Some(build_pow_table(gamma, 4096))
let gamma = u8Fixed8Number_to_float(data[0]);
build_pow_table(gamma, autogen_lut_len)
}
_ => {
//XXX: the choice of a minimum of 256 here is not backed by any theory,
// measurement or data, however it is what lcms uses.
let mut output_gamma_lut_length = data.len();
if output_gamma_lut_length < 256 {
output_gamma_lut_length = 256
}
Some(invert_lut(data, output_gamma_lut_length as i32))
let lut_len = lut_len.unwrap_or(data.len());
assert_eq!(lut_len, data.len());
data.clone() // I feel bad about this.
}
}
}
},
}
}
pub(crate) fn build_lut_for_tf_from_linear(trc: &curveType) -> Option<Vec<u16>> {
match trc {
curveType::Parametric(params) => {
let lut_len = 256;
let params = Param::new(params);
if let Some(inv_params) = params.invert() {
return Some(to_lut(&inv_params, lut_len));
}
// else return None instead of fallthrough to generic lut inversion.
return None;
},
curveType::Curve(data) => {
let autogen_lut_len = 4096;
match data.len() {
0 => {
return Some(build_linear_table(autogen_lut_len));
},
1 => {
let gamma = 1. / u8Fixed8Number_to_float(data[0]);
return Some(build_pow_table(gamma, autogen_lut_len));
},
_ => {},
}
},
}
let linear_from_tf = build_lut_for_linear_from_tf(trc, None);
//XXX: the choice of a minimum of 256 here is not backed by any theory,
// measurement or data, however it is what lcms uses.
let inverted_lut_len = std::cmp::max(linear_from_tf.len(), 256);
Some(invert_lut(&linear_from_tf, inverted_lut_len))
}
pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> {
build_lut_for_tf_from_linear(trc)
}