Skip to content

Commit

Permalink
Added Jzazbz and Jzczhz
Browse files Browse the repository at this point in the history
  • Loading branch information
awxkee committed Jul 20, 2024
1 parent f3ec079 commit 103c4ea
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ workspace = { members = ["src/app"] }

[package]
name = "colorutils-rs"
version = "0.4.16"
version = "0.4.17"
edition = "2021"
description = "High performance utilities for color format handling and conversion."
readme = "README.md"
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ Allows conversion between
- [x] Rgb/Rgba/Rgba1010102/Rgb565/RgbF16
- [x] HSL
- [x] HSV
- [x] LAB
- [x] LUV
- [x] LCh
- [x] CIE LAB
- [x] CIE LUV
- [x] CIE LCh
- [x] XYZ
- [x] Sigmoidal
- [x] Oklab
- [x] Jzazbz
- [x] Jzczhz

### Performance

Expand Down
17 changes: 5 additions & 12 deletions src/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,11 @@ fn main() {
let g = 126;
let b = 126;
let rgb = Rgb::<u8>::new(r, g, b);
let hsl = rgb.to_lab();
println!("RGB {:?}", rgb);
println!("RGB 0,0,0 {:?}", hsl);
println!(
"RGB 127,127,127 {:?}",
Rgb::<u8>::new(127, 127, 127).to_lab()
);
println!(
"RGB 255,255,255 {:?}",
Rgb::<u8>::new(255, 255, 255).to_lab()
);
println!("Back RGB {:?}", hsl.to_rgb8());
let jzazbz = Jzczhz::from_rgb(rgb, TransferFunction::Srgb);
println!("Jzczhz {:?}", jzazbz);
println!("Rgb {:?}", rgb);
let restored = jzazbz.to_rgb(TransferFunction::Srgb);
println!("Restored RGB {:?}", restored);

let img = ImageReader::open("./assets/beach_horizon.jpg")
.unwrap()
Expand Down
116 changes: 116 additions & 0 deletions src/jzazbz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
* //
* // Use of this source code is governed by a BSD-style
* // license that can be found in the LICENSE file.
*/
use crate::{
EuclideanDistance, Jzczhz, Rgb, TransferFunction, Xyz, SRGB_TO_XYZ_D65, XYZ_TO_SRGB_D65,
};
use erydanos::ehypot3f;

#[inline]
fn perceptual_quantizer(x: f32) -> f32 {
let xx = f32::powf(x * 1e-4, 0.1593017578125);
return f32::powf(
(0.8359375 + 18.8515625 * xx) / (1. + 18.6875 * xx),
134.034375,
);
}

#[inline]
fn perceptual_quantizer_inverse(x: f32) -> f32 {
let xx = f32::powf(x, 7.460772656268214e-03);
return 1e4
* f32::powf(
(0.8359375 - xx) / (18.6875 * xx - 18.8515625),
6.277394636015326,
);
}

#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
/// Represents Jzazbz
pub struct Jzazbz {
pub jz: f32,
pub az: f32,
pub bz: f32,
}

impl Jzazbz {
/// Constructs new instance
#[inline]
pub fn new(jz: f32, az: f32, bz: f32) -> Jzazbz {
Jzazbz { jz, az, bz }
}

#[inline]
pub fn from_xyz(xyz: Xyz) -> Jzazbz {
let lp =
perceptual_quantizer(0.674207838 * xyz.x + 0.382799340 * xyz.y - 0.047570458 * xyz.z);
let mp =
perceptual_quantizer(0.149284160 * xyz.x + 0.739628340 * xyz.y + 0.083327300 * xyz.z);
let sp =
perceptual_quantizer(0.070941080 * xyz.x + 0.174768000 * xyz.y + 0.670970020 * xyz.z);
let iz = 0.5 * (lp + mp);
let az = 3.524000 * lp - 4.066708 * mp + 0.542708 * sp;
let bz = 0.199076 * lp + 1.096799 * mp - 1.295875 * sp;
let jz = (0.44 * iz) / (1. - 0.56 * iz) - 1.6295499532821566e-11;
Jzazbz::new(jz, az, bz)
}

/// Converts Rgb to Jzazbz
#[inline]
pub fn from_rgb(rgb: Rgb<u8>, transfer_function: TransferFunction) -> Jzazbz {
let xyz = rgb.to_xyz(&SRGB_TO_XYZ_D65, transfer_function);
Self::from_xyz(xyz)
}

/// Converts Jzazbz to *Xyz*
#[inline]
pub fn to_xyz(&self) -> Xyz {
let jz = self.jz + 1.6295499532821566e-11;

let iz = jz / (0.44 + 0.56 * jz);
let l = perceptual_quantizer_inverse(
iz + 1.386050432715393e-1 * self.az + 5.804731615611869e-2 * self.bz,
);
let m = perceptual_quantizer_inverse(
iz - 1.386050432715393e-1 * self.az - 5.804731615611891e-2 * self.bz,
);
let s = perceptual_quantizer_inverse(
iz - 9.601924202631895e-2 * self.az - 8.118918960560390e-1 * self.bz,
);
let x = 1.661373055774069e+00 * l - 9.145230923250668e-01 * m + 2.313620767186147e-01 * s;
let y = -3.250758740427037e-01 * l + 1.571847038366936e+00 * m - 2.182538318672940e-01 * s;
let z = -9.098281098284756e-02 * l - 3.127282905230740e-01 * m + 1.522766561305260e+00 * s;
Xyz::new(x, y, z)
}

/// Converts to Linear RGB
#[inline]
pub fn to_linear_rgb(&self) -> Rgb<f32> {
let xyz = self.to_xyz();
xyz.to_linear_rgb(&XYZ_TO_SRGB_D65)
}

/// Converts to RGB with requested transfer function
#[inline]
pub fn to_rgb(&self, transfer_function: TransferFunction) -> Rgb<u8> {
let linear_rgb = self
.to_linear_rgb()
.apply(transfer_function.get_gamma_function());
linear_rgb.to_u8()
}

/// Converts into *Jzczhz*
#[inline]
pub fn to_jzczhz(&self) -> Jzczhz {
Jzczhz::from_jzazbz(*self)
}
}

impl EuclideanDistance for Jzazbz {
fn euclidean_distance(&self, other: Self) -> f32 {
ehypot3f(self.jz - other.jz, self.az - other.az, self.bz - other.bz)
}
}
90 changes: 90 additions & 0 deletions src/jzczhz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
* //
* // Use of this source code is governed by a BSD-style
* // license that can be found in the LICENSE file.
*/
use crate::{EuclideanDistance, Jzazbz, Rgb, TransferFunction, Xyz};
use erydanos::{ehypot3f, ehypotf, Cosine, Euclidean2DDistance, Sine};

/// Represents Jzazbz in polar coordinates as Jzczhz
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub struct Jzczhz {
pub jz: f32,
pub cz: f32,
pub hz: f32,
}

impl Jzczhz {
/// Creates new instance of Jzczhz
#[inline]
pub fn new(jz: f32, cz: f32, hz: f32) -> Jzczhz {
Jzczhz { jz, cz, hz }
}

/// Converts Rgb to polar coordinates Jzczhz
///
/// # Arguments
/// `transfer_function` - Transfer function to convert into linear colorspace and backwards
#[inline]
pub fn from_rgb(rgb: Rgb<u8>, transfer_function: TransferFunction) -> Jzczhz {
let jzazbz = rgb.to_jzazbz(transfer_function);
Jzczhz::from_jzazbz(jzazbz)
}

/// Converts Jzazbz to polar coordinates Jzczhz
#[inline]
pub fn from_jzazbz(jzazbz: Jzazbz) -> Jzczhz {
let cz = ehypotf(jzazbz.az, jzazbz.bz);
let hz = jzazbz.bz.ehypot(jzazbz.az);
Jzczhz::new(jzazbz.jz, cz, hz)
}

/// Converts Jzczhz into Jzazbz
#[inline]
pub fn to_jzazbz(&self) -> Jzazbz {
let az = self.cz * self.hz.ecos();
let bz = self.cz * self.hz.esin();
Jzazbz::new(self.jz, az, bz)
}

/// Converts Jzczhz to Rgb
///
/// # Arguments
/// `transfer_function` - Transfer function to convert into linear colorspace and backwards
#[inline]
pub fn to_rgb(&self, transfer_function: TransferFunction) -> Rgb<u8> {
let jzazbz = self.to_jzazbz();
jzazbz.to_rgb(transfer_function)
}

/// Converts Jzczhz to *Xyz*
#[inline]
pub fn to_xyz(&self) -> Xyz {
let jzazbz = self.to_jzazbz();
jzazbz.to_xyz()
}

/// Converts *Xyz* to *Jzczhz*
#[inline]
pub fn from_xyz(xyz: Xyz) -> Jzczhz {
let jzazbz = Jzazbz::from_xyz(xyz);
Jzczhz::from_jzazbz(jzazbz)
}

/// Computes distance for *Jzczhz*
#[inline]
pub fn distance(&self, other: Jzczhz) -> f32 {
let djz = self.jz - other.jz;
let dcz = self.cz - other.cz;
let dhz = self.hz - other.hz;
let dh = 2f32 * (self.cz * other.cz).sqrt() * (dhz * 0.5f32).esin();
ehypot3f(djz, dcz, dh)
}
}

impl EuclideanDistance for Jzczhz {
fn euclidean_distance(&self, other: Self) -> f32 {
ehypot3f(self.jz - other.jz, self.hz - other.hz, self.cz - other.cz)
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ mod image_to_oklab;
mod image_to_sigmoidal;
mod image_to_xyz_lab;
mod image_xyza_laba;
mod jzazbz;
mod jzczhz;
mod lab;
mod linear_to_image;
mod linear_to_image_u8;
Expand Down Expand Up @@ -128,6 +130,8 @@ pub use image_to_oklab::rgba_to_oklab;
pub use image_to_sigmoidal::bgra_to_sigmoidal;
pub use image_to_sigmoidal::rgb_to_sigmoidal;
pub use image_to_sigmoidal::rgba_to_sigmoidal;
pub use jzazbz::Jzazbz;
pub use jzczhz::Jzczhz;
pub use oklab::Oklab;
pub use oklab_to_image::oklab_to_bgr;
pub use oklab_to_image::oklab_to_bgra;
Expand Down
2 changes: 1 addition & 1 deletion src/oklab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
};
use erydanos::ehypot3f;

#[derive(Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
/// Struct that represent *Oklab* colorspace
pub struct Oklab {
/// All values in Oklab intended to be normalized [0;1]
Expand Down
21 changes: 20 additions & 1 deletion src/rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::euclidean::EuclideanDistance;
use crate::hsv::Hsv;
use crate::lab::Lab;
use crate::luv::Luv;
use crate::{Hsl, LCh, Sigmoidal};
use crate::{Hsl, Jzazbz, LCh, Sigmoidal, TransferFunction, Xyz};
use erydanos::Euclidean3DDistance;

#[derive(Debug, PartialOrd, PartialEq, Clone, Copy)]
Expand All @@ -23,31 +23,49 @@ pub struct Rgb<T> {
}

impl Rgb<u8> {
/// Converts rgb to Jzazbz
#[inline]
pub fn to_jzazbz(&self, transfer_function: TransferFunction) -> Jzazbz {
Jzazbz::from_rgb(*self, transfer_function)
}

/// Converts rgb to XYZ
#[inline]
pub fn to_xyz(&self, matrix: &[[f32; 3]; 3], transfer_function: TransferFunction) -> Xyz {
Xyz::from_rgb(self, matrix, transfer_function)
}

/// Converts rgb to HSL
#[inline]
pub fn to_hsl(&self) -> Hsl {
Hsl::from_rgb(self)
}

/// Converts rgb to HSV
#[inline]
pub fn to_hsv(&self) -> Hsv {
Hsv::from(self)
}

/// Converts rgb to CIELAB
#[inline]
pub fn to_lab(&self) -> Lab {
Lab::from_rgb(self)
}

/// Converts rgb to CIELUV
#[inline]
pub fn to_luv(&self) -> Luv {
Luv::from_rgb(self)
}

/// Converts rgb to CIELCH
#[inline]
pub fn to_lch(&self) -> LCh {
LCh::from_rgb(self)
}

/// Converts rgb to RGB f32
#[inline]
pub fn to_rgb_f32(&self) -> Rgb<f32> {
const SCALE: f32 = 1f32 / 255f32;
Expand All @@ -58,6 +76,7 @@ impl Rgb<u8> {
)
}

/// Converts rgb to S-shaped sigmoidized components
#[inline]
pub fn to_sigmoidal(&self) -> Sigmoidal {
Sigmoidal::from_rgb(self)
Expand Down
8 changes: 7 additions & 1 deletion src/xyz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
use crate::gamma_curves::TransferFunction;
use crate::rgb::Rgb;
use crate::{EuclideanDistance, SRGB_TO_XYZ_D65, XYZ_TO_SRGB_D65};
use crate::{EuclideanDistance, Jzazbz, SRGB_TO_XYZ_D65, XYZ_TO_SRGB_D65};
use erydanos::Euclidean3DDistance;

/// A CIE 1931 XYZ color.
Expand Down Expand Up @@ -45,6 +45,12 @@ static XYZ_SCALE_U8: f32 = 1f32 / 255f32;
/// Normalized values are speeding up computing.
/// if you need this multiply by yourself or use `scaled`
impl Xyz {
/// Converts into *Xyz*
#[inline]
pub fn to_jzazbz(&self) -> Jzazbz {
Jzazbz::from_xyz(*self)
}

/// This functions always use sRGB transfer function and Rec.601 primaries with D65 White point
#[inline]
pub fn from_srgb(rgb: &Rgb<u8>) -> Self {
Expand Down

0 comments on commit 103c4ea

Please sign in to comment.