color_space
color_space copied to clipboard
major change proposal(s)
I've been working on this crate in the last few days, before it gets too different from the status quo. Let's discuss what are the changes I'm proposing.
I'm working on different Color structs now, as this is more fundamental than the comparison. And this proposal doesn't cover any functionality for comparisons.
First, different structs for representing colors are color model
s, in a given color working space
. Currently this crate assumes sRGB
working space with 2 degree observer and D65 reference white. This is okay as a start. But I think eventually we need to allow users to specify different working space.
AFAIK, no other crates provides this functionality yet. So eventually, I imagine we will have a trait
called ColorSpace
, and each structs that implements it, needs to provide the reference white and the observer degree. Since the latter one is mostly 2 degree, I think only the reference white must be provided.
So, aside from conversion between different color models, we also needs to take into account the reference white in different color space. So we need to add a PhantomData
of a ColorSpace
to each ColorModel
.
This would the eventual goal.
For now, I propose some changes to the API. The most important one is the object safety.
In my working implementation, I made all the fields private so that users cannot change those values manually. The can only create instances of colors via two methods:
- a
new
method which returnsSome(Self)
if all provided fields are within the valid ranges; returnsNone
otherwise - a unsafe
new_unchecked
method that returnsSelf
without validating the provided fields
Object safety is very important for this crate, because various conversions assumes the specific ranges on every fields.
(new
and new_unchecked
are inspired from NonZeroXXX
types from standard library.)
Currently, we have several traits which I don't think necessary:
-
FromColor
:OtherColor -> Color
-
FromRgb
:Rgb -> Color
-
ToRgb
:Color -> Rgb
I propose to use the following from standard library:
-
TryFrom<(...)> for Color
, where(...)
is a tuple. E.g.TryFrom<(f64, f64, f64)> for Rgb
. This is the underlying fallible conversion we need when constructing a new instance. -
From<Color> for (...)
andFrom<Color> for &[...]
, e.g.From<Rgb> for (f64, f64, f64)
andFrom<Rgb> for &[f64; 3]
, this is quite handy when unpacking the values. (This is infallible). -
From<Color> for OtherColor
, e.g.From<Rgb> for Hsv
.
After re-structure the API, I think we need to implement Display
traits for each color.
Since all fields have specific ranges, we need can also implement Eq
trait in addition to PartialEq
.
We should also allow types other than f64
. There are a few crates that provides wrapper types for primitive floats. We should allow any type T: num_trait::ToPrimitive
. This, together with From
tuple and slices, would make our crate easier to use with other crates.
We may just allow those generic types when constructing the color, and still use f64 in the internal implementation.
With all these work done, we should then implement the different ColorSpace
.
This sounds like a pretty awesome set of features. This is outside my capabilities, but if anyone writes a crate that works like this, I'd be super interested in using it! : )
@ChevyRay @lebensterben In the last few days I wrote colo
, which is an application that uses this crate. Some of the functionality described above I had to implement myself, e.g. TryFrom
implementations for the color types that check if the values are in a valid range. However, due to orphan rules I had to implement it for a wrapper type.
I'm unsure if it makes sense to guarantee that colors will always be in a valid range, because conversions can have numerical errors. For example, a conversion could produce 255.0000001 instead of 255, but this is probably irrelevant because the values are rounded anyway.
Furthermore, color spaces have different sizes, so converting a color from Lab to Rgb can produce an invalid color.
Object safety is very important for this crate, because various conversions assumes the specific ranges on every fields.
Object safety (the concept whether a trait can be made into a trait object) have nothing to do with this. You're right that conversations assume that the input is valid, but if it isn't, they just produce garbage output in the worst case. There's nothing unsafe about this.
I think this crate would profit from small, incremental improvements more than from radical changes. The crate has a solid foundation (the C library it was ported from) and is excellent for many use cases.