Fluv32: slightly tweak the u/v scaling constants.
This allows perfect representation of E (equal energy spectrum). It's not important from a perceptual standpoint, but it provides a simple way for Psychopath to represent E when needed for other purposes.
This commit is contained in:
parent
05f9621ac5
commit
066105b20a
|
@ -2,12 +2,16 @@
|
||||||
//!
|
//!
|
||||||
//! This encoding is based on, but is slightly different than, the 32-bit
|
//! This encoding is based on, but is slightly different than, the 32-bit
|
||||||
//! LogLuv format from the paper "Overcoming Gamut and Dynamic Range
|
//! LogLuv format from the paper "Overcoming Gamut and Dynamic Range
|
||||||
//! Limitations in Digital Images" by Greg Ward. It uses the same uv chroma
|
//! Limitations in Digital Images" by Greg Ward:
|
||||||
//! storage, but uses a floating point rather than log encoding to store
|
|
||||||
//! luminance, mainly for the sake of faster decoding. It also omits the sign
|
|
||||||
//! bit of LogLuv, foregoing negative luminance capabilities.
|
|
||||||
//!
|
//!
|
||||||
//! Compared to LogLuv, this format's chroma precision is identical and its
|
//! * It uses the same uv chroma storage approach, but with *very* slightly
|
||||||
|
//! tweaked scales to allow perfect representation of E.
|
||||||
|
//! * It uses uses a floating point rather than log encoding to store
|
||||||
|
//! luminance, mainly for the sake of faster decoding.
|
||||||
|
//! * It also omits the sign bit of LogLuv, foregoing negative luminance
|
||||||
|
//! capabilities.
|
||||||
|
//!
|
||||||
|
//! Compared to LogLuv, this format's chroma precision is the same and its
|
||||||
//! luminance precision is better, but its luminance *range* is smaller.
|
//! luminance precision is better, but its luminance *range* is smaller.
|
||||||
//! The supported luminance range is still substantial, however (see
|
//! The supported luminance range is still substantial, however (see
|
||||||
//! "Luminance details" below).
|
//! "Luminance details" below).
|
||||||
|
@ -47,8 +51,11 @@
|
||||||
|
|
||||||
const EXP_BIAS: i32 = 27;
|
const EXP_BIAS: i32 = 27;
|
||||||
|
|
||||||
/// The scale factor of the quantized UV components.
|
/// The scale factor of the quantized U component.
|
||||||
pub const UV_SCALE: f32 = 410.0;
|
pub const U_SCALE: f32 = 817.0 / 2.0;
|
||||||
|
|
||||||
|
/// The scale factor of the quantized V component.
|
||||||
|
pub const V_SCALE: f32 = 1235.0 / 3.0;
|
||||||
|
|
||||||
/// Largest representable Y component.
|
/// Largest representable Y component.
|
||||||
pub const Y_MAX: f32 = ((1u64 << (64 - EXP_BIAS)) - (1u64 << (64 - EXP_BIAS - 11))) as f32;
|
pub const Y_MAX: f32 = ((1u64 << (64 - EXP_BIAS)) - (1u64 << (64 - EXP_BIAS - 11))) as f32;
|
||||||
|
@ -86,8 +93,8 @@ pub fn encode(xyz: (f32, f32, f32)) -> u32 {
|
||||||
// The minimum value of 1.0 for v is to avoid a possible divide by zero
|
// The minimum value of 1.0 for v is to avoid a possible divide by zero
|
||||||
// when decoding. A value less than 1.0 is outside the real colors,
|
// when decoding. A value less than 1.0 is outside the real colors,
|
||||||
// so we don't need to store it anyway.
|
// so we don't need to store it anyway.
|
||||||
let u = (((4.0 * UV_SCALE) * xyz.0 / s) + 0.5).max(0.0).min(255.0);
|
let u = (((4.0 * U_SCALE) * xyz.0 / s) + 0.5).max(0.0).min(255.0);
|
||||||
let v = (((9.0 * UV_SCALE) * xyz.1 / s) + 0.5).max(1.0).min(255.0);
|
let v = (((9.0 * V_SCALE) * xyz.1 / s) + 0.5).max(1.0).min(255.0);
|
||||||
|
|
||||||
((u as u32) << 8) | (v as u32)
|
((u as u32) << 8) | (v as u32)
|
||||||
};
|
};
|
||||||
|
@ -136,13 +143,14 @@ pub fn decode(fluv32: u32) -> (f32, f32, f32) {
|
||||||
// This is re-worked from the original equations, to allow a bunch of stuff
|
// This is re-worked from the original equations, to allow a bunch of stuff
|
||||||
// to cancel out and avoid operations. It makes the underlying equations a
|
// to cancel out and avoid operations. It makes the underlying equations a
|
||||||
// bit non-obvious.
|
// bit non-obvious.
|
||||||
// We also roll the UV_SCALE application into the final x and z formulas,
|
// We also roll the U/V_SCALE application into the final x and z formulas,
|
||||||
// since most of that also cancels if we do it there.
|
// since some of that cancels out as well, and all of it can be avoided at
|
||||||
|
// runtime that way.
|
||||||
let tmp = y / v;
|
let tmp = y / v;
|
||||||
let x = tmp * (u * 2.25); // y * (9u / 4v)
|
let x = tmp * ((2.25 * V_SCALE / U_SCALE) * u); // y * (9u / 4v)
|
||||||
let z = tmp * ((3.0 * UV_SCALE) - (0.75 * u) - (5.0 * v)); // y * ((12 - 3u - 20v) / 4v)
|
let z = tmp * ((3.0 * V_SCALE) - ((0.75 * V_SCALE / U_SCALE) * u) - (5.0 * v)); // y * ((12 - 3u - 20v) / 4v)
|
||||||
|
|
||||||
(x, y, z)
|
(x, y, z.max(0.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes from 32-bit FloatLuv to Yuv.
|
/// Decodes from 32-bit FloatLuv to Yuv.
|
||||||
|
@ -150,7 +158,8 @@ pub fn decode(fluv32: u32) -> (f32, f32, f32) {
|
||||||
/// The Y component is the luminance, and is simply the Y from CIE XYZ.
|
/// The Y component is the luminance, and is simply the Y from CIE XYZ.
|
||||||
///
|
///
|
||||||
/// The u and v components are the CIE LUV u' and v' chromaticity coordinates,
|
/// The u and v components are the CIE LUV u' and v' chromaticity coordinates,
|
||||||
/// but returned as `u8`s, and scaled by `UV_SCALE` to fit the range 0-255.
|
/// but returned as `u8`s, and scaled by `U_SCALE` and `V_SCALE` respectively
|
||||||
|
/// to fit the range 0-255.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn decode_yuv(fluv32: u32) -> (f32, u8, u8) {
|
pub fn decode_yuv(fluv32: u32) -> (f32, u8, u8) {
|
||||||
// Check for zero.
|
// Check for zero.
|
||||||
|
@ -181,10 +190,24 @@ mod tests {
|
||||||
let tri = encode(fs);
|
let tri = encode(fs);
|
||||||
let fs2 = decode(tri);
|
let fs2 = decode(tri);
|
||||||
|
|
||||||
assert_eq!(0x000056c2, tri);
|
assert_eq!(0x000056c3, tri);
|
||||||
assert_eq!(fs, fs2);
|
assert_eq!(fs, fs2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_ones() {
|
||||||
|
let fs = (1.0f32, 1.0f32, 1.0f32);
|
||||||
|
|
||||||
|
let tri = encode(fs);
|
||||||
|
let fs2 = decode(tri);
|
||||||
|
|
||||||
|
assert_eq!(0x6c0056c3, tri);
|
||||||
|
|
||||||
|
assert!((fs.0 - fs2.0).abs() < 0.0000001);
|
||||||
|
assert_eq!(fs.1, fs2.1);
|
||||||
|
assert!((fs.2 - fs2.2).abs() < 0.0000001);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn powers_of_two() {
|
fn powers_of_two() {
|
||||||
let mut n = 0.25;
|
let mut n = 0.25;
|
||||||
|
@ -259,7 +282,7 @@ mod tests {
|
||||||
let fs = (1.0, 1.0, 1.0);
|
let fs = (1.0, 1.0, 1.0);
|
||||||
let a = encode(fs);
|
let a = encode(fs);
|
||||||
|
|
||||||
assert_eq!((1.0, 0x56, 0xc2), decode_yuv(a));
|
assert_eq!((1.0, 0x56, 0xc3), decode_yuv(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -276,7 +299,7 @@ mod tests {
|
||||||
let fs = (INFINITY, INFINITY, INFINITY);
|
let fs = (INFINITY, INFINITY, INFINITY);
|
||||||
|
|
||||||
assert_eq!(Y_MAX, round_trip(fs).1);
|
assert_eq!(Y_MAX, round_trip(fs).1);
|
||||||
assert_eq!(0xffff56c2, encode(fs));
|
assert_eq!(0xffff56c3, encode(fs));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -290,17 +313,17 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn underflow() {
|
fn underflow() {
|
||||||
let fs = (Y_MIN * 0.99, Y_MIN * 0.99, Y_MIN * 0.99);
|
let fs = (Y_MIN * 0.99, Y_MIN * 0.99, Y_MIN * 0.99);
|
||||||
assert_eq!(0x000056c2, encode(fs));
|
assert_eq!(0x000056c3, encode(fs));
|
||||||
assert_eq!((0.0, 0.0, 0.0), round_trip(fs));
|
assert_eq!((0.0, 0.0, 0.0), round_trip(fs));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn negative_z_impossible() {
|
fn negative_z_impossible() {
|
||||||
// These are very specific values, which should result in smallest
|
for y in 0..1024 {
|
||||||
// possible z value (specifically z = 0.0 with no quantization) while
|
let fs = (1.0, 1.0 + (y as f32 / 4096.0), 0.0);
|
||||||
// still having positive values in x and y.
|
let fs2 = round_trip(fs);
|
||||||
let fs = (248.0 / 565.0, 9827.0 / 8475.0, 0.0);
|
assert!(fs2.2 >= 0.0);
|
||||||
assert!(round_trip(fs).2 >= 0.0);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user