# Arithmetic on colors

Hello there,
I want to find the total Sum of Squared Errors of a set of colors from their mean. `Using Colors`.
The colors and the mean look something like this, where `m = mean(cols)`.

``````cols[1:10] = RGB{FixedPointNumbers.Normed{UInt8,8}}[RGB{N0f8}(0.086,0.051,0.024), RGB{N0f8}(0.094,0.059,0.031), RGB{N0f8}(0.106,0.071,0.051), RGB{N0f8}(0.141,0.106,0.086), RGB{N0f8}(0.102,0.067,0.047), RGB{N0f8}(0.141,0.106,0.086), RGB{N0f8}(0.176,0.149,0.125), RGB{N0f8}(0.078,0.051,0.027), RGB{N0f8}(0.114,0.086,0.063), RGB{N0f8}(0.149,0.122,0.098)]
m = RGB{Float64}(0.5797770398481967,0.5710550284629976,0.5525899746995568)
``````

But

``````sse = sum(abs2, cols .- m)
``````

throws error
`
ERROR: LoadError: MethodError: no method matching depwarn(::String, ::Symbol; force=false)

`

I am wondering how to do it? I will be finding SSE of sections of image over and over again. What is the best way to do it? Convert the original image to Floats? I will also be using `hex(m)` to save the mean color to an svg image.

Edit MWE:

``````cols = rand(RGB{Float64}, 4, 4)
m = mean(cols)
cols .- m              # Also a Matrix of RGB{Float64} with some negative values!!!
sum(abs2, cols .- m)  # Errors out
``````

Thanks.

The error has nothing to do with colors. Itâs just that you cannot take `abs2` of an array, it only works on scalars:

``````julia> abs2([-2, 0, 1])
ERROR: MethodError: no method matching abs2(::Vector{Int64})
``````

In order to work on arrays, you must broadcast or map the function over the array (or loop):

``````julia> abs2.([-2, 0, 1])
3-element Vector{Int64}:
4
0
1

julia> map(abs2, [-2, 0, 1])
3-element Vector{Int64}:
4
0
1
``````

Sorry, just realized my mistake and updated the post as you were typing yours.

Yeah, I saw that. Just poor timing. But maybe you can make the example self contained? Make a code snippet with `using Colors` and then generate a random color vector. Itâs so much easer if I can just copy-paste your example directly into my REPL. Itâs called âmaking a minimum working exampleâ, and is extremely useful.

Did not feel like this deserved an MWE. Just that `RGB{Float64} does not have abs2 defined!` Thatâs it. If I do

``````abs3(a) = a.r*a.r+a.g*a.g+a.b*a.b
sum(abs3, cols)
``````

works. I feel I should not have to do this.

I donât think `abs2` makes sense for colors. What is the absolute value of a color? Are there negative colors?

There should almost always be an MWE. Especially this time, it would have simplified the interaction considerably.

Sorry again. Back to the original use case.
It definitely makes sense to calculate the SSE of a bunch of colors from their mean.

While I cannot answer this question off the top of my head, in the sense of explaining why `abs2` doesnât work, I perhaps could if I looked at the data. But without an MWE, Iâm not getting anywhere.

If I do

``````julia> using Colors

julia> cols = RGB{FixedPointNumbers.Normed{UInt8,8}}[RGB{N0f8}(0.086,0.051,0.024), RGB{N0f8}(0.094,0.059,0.031), RGB{N0f8}(0.106,0.071,0.051), RGB{N0f8}(0.141,0.106,0.086), RGB{N0f8}(0.102,0.067,0.047), RGB{N0f8}(0.141,0.106,0.086), RGB{N0f8}(0.176,0.149,0.125), RGB{N0f8}(0.078,0.051,0.027), RGB{N0f8}(0.114,0.086,0.063), RGB{N0f8}(0.149,0.122,0.098)]
ERROR: UndefVarError: FixedPointNumbers not defined
``````

I just get an error.

There really needs to be an MWE here.

So, I installed Images.jl, and took a look. Now I can show you why `abs2` doesnât make sense:

``````julia> using Images

julia> cols = [RGB{N0f8}(0.086,0.051,0.024), RGB{N0f8}(0.094,0.059,0.031)]
2-element Array{RGB{N0f8},1} with eltype RGB{N0f8}:
RGB{N0f8}(0.086,0.051,0.024)
RGB{N0f8}(0.094,0.059,0.031)

julia> x = cols[1] - cols[2]
RGB{N0f8}(0.996,0.996,0.996)

julia> y = cols[2] - cols[1]
RGB{N0f8}(0.008,0.008,0.008)

julia> x.r^2 + x.g^2 + x.b^2
0.969N0f8

julia> y.r^2 + y.g^2 + y.b^2
0.0N0f8
``````

As you can see, there are no negative colors, the values just wrap around. As a consequence `abs2(cols[1] - cols[2])` is different from `abs2(cols[2] - cols[1])` (if we defined `abs2` as the sum of the squares of the rgb channels). Thatâs not what you want.

If you want a distance measure, it needs to be defined differently, because subtraction is not behaving as you expect on colors.

As you can see, there are a few things going on here. This is what MWEs are for.

1 Like

This is provided by ColorVectorSpace.jl:

``````julia> sse = sum(abs2, cols .- m)
6.888337554125739
``````

Better yet, use the `norm` function (which is the same thing divided by `3` for RGB colors, because it is normalized to be the same for grayscale and color images):

``````julia> norm(cols .- m)^2 * 3
6.888337554125741
``````
2 Likes

But I get:

``````julia> ColorVectorSpace.norm(cols[2] .- cols[1])^2 * 3
0.0

julia> ColorVectorSpace.norm(cols[1] .- cols[2])^2 * 3
2.976470717669955
``````

Is this the right behaviour for color distance?

1 Like

Looks like this is underflow due to the fixed-point precision:

``````julia> cols[2] - cols[1]
RGB{N0f8}(0.008,0.008,0.008)

julia> norm(cols[2] - cols[1])
0.0

julia> norm(RGB{Float64}(cols[2] - cols[1]))
0.00784313725490196
``````

In particular, the problem stems from the underflow:

``````julia> N0f8(0.008)^2
0.0N0f8
``````

Arguably, this should be changed â since the output of `norm` is a `Float64`, this computation should be performed by promoting operands to the output precision. Or at the very least some scaling should be done to prevent spurious overflow/underflow (similar to what `norm` and `hypot` do for floating-point vectors).

(Update: Filed issue ColorVectorSpace.jl#183.)

The concept of arithmetic on colors is a bit fuzzy to me. It makes sense to add (mix) colors. So I guess subtraction also might work. But what should happen when you subtract a strong color from a weak color? Should it wrap around, or saturate, or return negative values?

A distance function seems like it should do something like subtracting the minimum from the maximum instead of taking the absolute or square of the difference.

I guess that in the physical world, if we have no red and try to subtract red, we should always end up with no red. However, using the ColorVectorSpace, we get a ânegativeâ red:

` RGB{Float64}(0,1,1) - RGB{Float64}(1,1,1) == RGB{Float64}(-1.0,0.0,0.0)`

Such colors live only in the âmathematical worldâ and are invalid for display purposes? They would need to be saturated/confined to 0-1 limits, in order to be plotted.

Arthmetic on colors, especially the use-case given here, is very fundamental to image and video storage. All your jpgs and pngs do it thousands or millions of times. They replace a blob with a single color and see how well it fits (at some level). Wavelets, compressed sensing they all do this. Not every mathematical operation needs to map to the real world. Case in point: âImaginaryâ numbers.
I feel this is a bug in Colours.jl and need to be thought through.

That doesnât mean that one shouldnât strive to make the operations internally consistent.

So whatâs the right behavior of `RGB{N0f8}` under subtraction, then? They are based on `UInt8` which are unsigned, and therefore incapable of holding negative values. Which makes sense for storing color values, but makes subtraction (and many other types of arithmetic) problematic.

That was exactly my point.

I now see you made an MWE using `RGB{Float64}`. Thatâs probably the right approach. Doing what you want in unsigned arithmetic is probably needlessly complicated.

1 Like