Skip to content

Commit 66dda94

Browse files
Redesign color arithmetic (#131)
This notably defines 3 multiplication operators for RGB colors. It also un-defines `abs2`, because how that should work is a bit ambiguous. Finally, it defines a new `varmult` function, which allows one to compute variance using a specific multiplication operator. There are some compatibility definitions for current releases of ColorTypes. Closes #126 Co-authored-by: Johnny Chen <johnnychen94@hotmail.com>
1 parent 757d52e commit 66dda94

File tree

4 files changed

+504
-241
lines changed

4 files changed

+504
-241
lines changed

Project.toml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
name = "ColorVectorSpace"
22
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
3-
version = "0.8.7"
3+
version = "0.9.0"
44

55
[deps]
66
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
7-
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
87
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
98
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
109
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
1110
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
12-
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
11+
TensorCore = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
1312

1413
[compat]
15-
ColorTypes = "0.8, 0.9, 0.10"
16-
Colors = "0.9, 0.10, 0.11, 0.12"
17-
FixedPointNumbers = "0.6, 0.7, 0.8"
18-
SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0"
19-
StatsBase = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33"
14+
ColorTypes = "0.10"
15+
FixedPointNumbers = "0.8"
16+
SpecialFunctions = "0.7, 0.8, 0.9, 0.10"
17+
TensorCore = "0.1"
2018
julia = "1"
2119

2220
[extras]
21+
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
2322
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
2423
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
25-
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
2624
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2725

2826
[targets]
29-
test = ["Statistics", "StatsBase", "LinearAlgebra", "Test"]
27+
test = ["Colors", "Statistics", "LinearAlgebra", "Test"]

README.md

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,27 @@
66
This package is an add-on to [ColorTypes](https://github.com/JuliaGraphics/ColorTypes.jl), and provides fast
77
mathematical operations for objects with types such as `RGB` and
88
`Gray`.
9+
Specifically, with this package both grayscale and `RGB` colors are treated as if they are points
10+
in a normed vector space.
911

1012
## Introduction
1113

12-
Colorspaces such as RGB, unlike XYZ, are technically non-linear; the
13-
"colorimetrically correct" approach when averaging two RGBs is to
14+
Colorspaces such as RGB, unlike XYZ, are technically non-linear;
15+
perhaps the most "colorimetrically correct" approach when averaging two RGBs is to
1416
first convert each to XYZ, average them, and then convert back to RGB.
17+
Nor is there a clear definition of computing the sum of two colors.
18+
As a consequence, Julia's base color package,
19+
[ColorTypes](https://github.com/JuliaGraphics/ColorTypes.jl),
20+
does not support mathematical operations on colors.
1521

1622
However, particularly in image processing it is common to ignore this
1723
concern, and for the sake of performance treat an RGB as if it were a
18-
3-vector. This package provides such operations.
24+
3-vector. The role of this package is to extend ColorTypes to support such mathematical operations.
25+
Specifically, it defines `+` and multiplication by a scalar (and by extension, `-` and division by a scalar) for grayscale and `AbstractRGB` colors.
26+
These are the requirements of a [vector space](https://en.wikipedia.org/wiki/Vector_space).
1927

20-
If you're curious about how much difference it makes, the following
28+
If you're curious about how much the "colorimetrically correct" and
29+
"vector space" views differ, the following
2130
diagram might help. The first 10 `distinguishable_colors` were
2231
generated, and all pairs were averaged. Each box represents the
2332
average of the pair of diagonal elements intersected by tracing
@@ -27,16 +36,77 @@ represents the "RGB vector space" version.
2736

2837
![ColorVectorSpace](images/comparison.png "Comparison")
2938

39+
This package also defines `norm(c)` for RGB and grayscale colors.
40+
This makes these color spaces [normed vector spaces](https://en.wikipedia.org/wiki/Normed_vector_space).
41+
Note that `norm` has been designed to satisfy equivalence of grayscale and RGB representations: if
42+
`x` is a scalar, then `norm(x) == norm(Gray(x)) == norm(RGB(x, x, x))`.
43+
Effectively, there's a division-by-3 in the `norm(::RGB)` case compared to the Euclidean interpretation of
44+
the RGB vector space.
45+
Equivalence is an important principle for the Colors ecosystem, and violations should be reported as likely bugs.
46+
3047
## Usage
3148

3249
```julia
3350
using ColorTypes, ColorVectorSpace
3451
```
3552

36-
That's it. Just by loading `ColorVectorSpace`, most basic mathematical
53+
For the most part, that's it; just by loading `ColorVectorSpace`, most basic mathematical
3754
operations will "just work" on `AbstractRGB`, `AbstractGray`
3855
(`Color{T,1}`), `TransparentRGB`, and `TransparentGray` objects.
3956
(See definitions for the latter inside of `ColorTypes`).
4057

41-
If you discover missing operations, please open an issue, or better
42-
yet submit a pull request.
58+
However, there are some additional operations that you may need to distinguish carefully.
59+
60+
### Multiplication
61+
62+
Grayscale values are conceptually similar to scalars, and consequently it seems straightforward to define multiplication of two grayscale values.
63+
RGB values present more options.
64+
This package supports three different notions of multiplication: the inner product, the hadamard (elementwise) product, and the tensor product.
65+
66+
```julia
67+
julia> c1, c2 = RGB(0.2, 0.3, 0.4), RGB(0.5, 0.3, 0.2)
68+
(RGB{Float64}(0.2,0.3,0.4), RGB{Float64}(0.5,0.3,0.1))
69+
70+
julia> c1c2 # \cdot<TAB> # or dot(c1, c2)
71+
0.09000000000000001
72+
73+
# This is equivelant to `mapc(*, c1, c2)`
74+
julia> c1c2 # \odot<TAB> # or hadamard(c1, c2)
75+
RGB{Float64}(0.1,0.09,0.08000000000000002)
76+
77+
julia> c1c2 # \otimes<TAB> # or tensor(c1, c2)
78+
RGBRGB{Float64}(
79+
0.1 0.06 0.04000000000000001
80+
0.15 0.09 0.06
81+
0.2 0.12 0.08000000000000002)
82+
```
83+
84+
Note that `c1⋅c2 = (c1.r*c2.r + c1.g*c2.g + c1.b*c2.b)/3`, where the division by 3 ensures the equivalence `norm(x) == norm(Gray(x)) == norm(RGB(x, x, x))`.
85+
86+
It is designed to not support the ordinary multiplication operation `*` because it is not obvious which one of these should be the default option.
87+
88+
However, `*` is defined for grayscale since all these three multiplication operations (i.e., ``, `` and ``) are equivalent in the 1D vector space.
89+
90+
### Variance
91+
92+
The variance `v = E((c - μ)^2)` (or its bias-corrected version) involves a multiplication,
93+
and to be consistent with the above you must specify which sense of multiplication you wish to use:
94+
95+
```julia
96+
julia> cs = [c1, c2]
97+
2-element Array{RGB{Float64},1} with eltype RGB{Float64}:
98+
RGB{Float64}(0.2,0.3,0.4)
99+
RGB{Float64}(0.5,0.3,0.2)
100+
101+
julia> varmult(, cs)
102+
0.021666666666666667
103+
104+
julia> varmult(, cs)
105+
RGB{Float64}(0.045,0.0,0.020000000000000004)
106+
107+
julia> varmult(, cs)
108+
RGBRGB{Float64}(
109+
0.045 0.0 -0.03
110+
0.0 0.0 0.0
111+
-0.03 0.0 0.020000000000000004)
112+
```

0 commit comments

Comments
 (0)