Skip to content

Commit

Permalink
Merge #236
Browse files Browse the repository at this point in the history
236: Pre-release polishing r=Ogeon a=Ogeon

This is a catch-all PR for anything I find while preparing for the release. Nothing that should affect already released functionality.

Changes:

* `Oklab` and `Oklch` no longer implements `ColorDifference`, since I can't find any sign of CIEDE2000 being correct there.
* Separated the first sentence for named gradients as its own paragraph. This makes nicer looking docs.
* Added an example of the difference between "naive" randomization and our implementation.

Co-authored-by: Erik Hedvall <erikwhedvall@gmail.com>
  • Loading branch information
bors[bot] and Ogeon committed Jul 12, 2021
2 parents c84fb4e + b2d5d66 commit ad33812
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 79 deletions.
1 change: 1 addition & 0 deletions palette/build/named.rs
Expand Up @@ -76,6 +76,7 @@ pub fn build_gradients(writer: &mut File) {
.parse()
.unwrap_or_else(|_| panic!("couldn't parse the number of colors for color {}", name));
writeln!(writer, "/// New matplotlib colormap by Nathaniel J. Smith, Stefan van der Walt, and (in the case of viridis) Eric Firing.").unwrap();
writeln!(writer, "///").unwrap();
writeln!(writer, "/// This gradient is perfectly perceptually-uniform, both in regular form and also when converted to black-and-white.").unwrap();
writeln!(
writer,
Expand Down
116 changes: 116 additions & 0 deletions palette/examples/random.rs
@@ -0,0 +1,116 @@
#[cfg(not(feature = "random"))]
fn main() {
println!("You can't use the `rand` integration without the \"random\" feature");
}

#[cfg(feature = "random")]
fn main() {
use palette::{FromColor, Hsl, Hsv, Hwb, Pixel, RgbHue, Srgb};

use image::{GenericImage, GenericImageView, RgbImage};
use rand::Rng;

let mut image = RgbImage::new(512, 256);
let mut rng = rand_mt::Mt::default();

// RGB
{
let mut sub_image = image.sub_image(0, 0, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color = Srgb::<f32>::new(rng.gen(), rng.gen(), rng.gen());
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

{
let mut sub_image = image.sub_image(0, 128, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color = rng.gen::<Srgb>();
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

// HSV
{
let mut sub_image = image.sub_image(128, 0, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color =
Srgb::from_color(Hsv::new(rng.gen::<RgbHue>(), rng.gen(), rng.gen()));
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

{
let mut sub_image = image.sub_image(128, 128, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color = Srgb::from_color(rng.gen::<Hsv>());
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

// HSL
{
let mut sub_image = image.sub_image(256, 0, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color =
Srgb::from_color(Hsl::new(rng.gen::<RgbHue>(), rng.gen(), rng.gen()));
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

{
let mut sub_image = image.sub_image(256, 128, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color = Srgb::from_color(rng.gen::<Hsl>());
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

// HWB
{
let mut sub_image = image.sub_image(384, 0, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color =
Srgb::from_color(Hwb::new(rng.gen::<RgbHue>(), rng.gen(), rng.gen()));
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

{
let mut sub_image = image.sub_image(384, 128, 128, 128);
let (width, height) = sub_image.dimensions();
for x in 0..width {
for y in 0..height {
let random_color = Srgb::from_color(rng.gen::<Hwb>());
sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into_raw()));
}
}
}

let _ = std::fs::create_dir("example-data/output");
match image.save("example-data/output/random.png") {
Ok(()) => println!("see 'example-data/output/random.png' for the result"),
Err(e) => println!("failed to write 'example-data/output/random.png': {}", e),
}
}
35 changes: 3 additions & 32 deletions palette/src/oklab.rs
Expand Up @@ -8,8 +8,6 @@ use rand::distributions::{Distribution, Standard};
#[cfg(feature = "random")]
use rand::Rng;

use crate::color_difference::ColorDifference;
use crate::color_difference::{get_ciede_difference, LabColorDiff};
use crate::convert::FromColorUnclamped;
use crate::encoding::pixel::RawPixel;
use crate::matrix::multiply_xyz;
Expand Down Expand Up @@ -61,9 +59,9 @@ pub type Oklaba<T = f32> = Alpha<Oklab<T>, T>;

/// The [Oklab color space](https://bottosson.github.io/posts/oklab/).
///
/// Oklab is a perceptually-uniform color space similar in structure to [L\*a\*b\*](crate::Lab), but
/// with better perceptual uniformity. It assumes a D65 whitepoint and normal well-lit viewing
/// conditions.
/// Oklab is a perceptually-uniform color space similar in structure to
/// [L\*a\*b\*](crate::Lab), but tries to have a better perceptual uniformity.
/// It assumes a D65 whitepoint and normal well-lit viewing conditions.
#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)]
#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
#[palette(
Expand Down Expand Up @@ -370,33 +368,6 @@ where
}
}

impl<T> ColorDifference for Oklab<T>
where
T: FloatComponent,
{
type Scalar = T;

fn get_color_difference(&self, other: &Self) -> Self::Scalar {
// Color difference calculation requires Lab and chroma components. This
// function handles the conversion into those components which are then
// passed to `get_ciede_difference()` where calculation is completed.
let self_params = LabColorDiff {
l: self.l,
a: self.a,
b: self.b,
chroma: (self.a * self.a + self.b * self.b).sqrt(),
};
let other_params = LabColorDiff {
l: other.l,
a: other.a,
b: other.b,
chroma: (other.a * other.a + other.b * other.b).sqrt(),
};

get_ciede_difference(&self_params, &other_params)
}
}

impl<T> ComponentWise for Oklab<T>
where
T: FloatComponent,
Expand Down
47 changes: 0 additions & 47 deletions palette/src/oklch.rs
Expand Up @@ -8,7 +8,6 @@ use rand::distributions::{Distribution, Standard};
#[cfg(feature = "random")]
use rand::Rng;

use crate::color_difference::{get_ciede_difference, ColorDifference, LabColorDiff};
use crate::convert::{FromColorUnclamped, IntoColorUnclamped};
use crate::encoding::pixel::RawPixel;
use crate::white_point::D65;
Expand Down Expand Up @@ -355,52 +354,6 @@ where
}
}

/// CIEDE2000 distance metric for color difference.
impl<T> ColorDifference for Oklch<T>
where
T: FloatComponent,
{
type Scalar = T;

fn get_color_difference(&self, other: &Oklch<T>) -> Self::Scalar {
// Prepare a* and b* from Oklch components to calculate color difference
let self_a = clamp(
self.chroma.max(T::zero()) * self.hue.to_radians().cos(),
from_f64(0.0),
from_f64(1.0),
);
let self_b = clamp(
self.chroma.max(T::zero()) * self.hue.to_radians().sin(),
from_f64(0.0),
from_f64(1.0),
);
let other_a = clamp(
other.chroma.max(T::zero()) * other.hue.to_radians().cos(),
from_f64(0.0),
from_f64(1.0),
);
let other_b = clamp(
other.chroma.max(T::zero()) * other.hue.to_radians().sin(),
from_f64(0.0),
from_f64(1.0),
);
let self_params = LabColorDiff {
l: self.l,
a: self_a,
b: self_b,
chroma: self.chroma,
};
let other_params = LabColorDiff {
l: other.l,
a: other_a,
b: other_b,
chroma: other.chroma,
};

get_ciede_difference(&self_params, &other_params)
}
}

impl<T> Saturate for Oklch<T>
where
T: FloatComponent,
Expand Down

0 comments on commit ad33812

Please sign in to comment.