# Getting the average HSB from pixels

**URL:** <https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965>\
**Category:** Coding Questions\
**Created:** [August 27, 2021, 8:47am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965 "2021-08-27T08:47:57Z")\
**Posts on this page:** 1\
**Showing post:** 27

<div class="post-metadata">

**Author:** ![jeremydouglass](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/jeremydouglass/32/20_2.png) [@jeremydouglass](https://discourse.processing.org/u/jeremydouglass)\
**Post date:** [August 30, 2021, 12:29am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/27 "2021-08-30T00:29:30Z")

</div>

> [@paulstgeorge](#):
>
> Can I post your code there? Or do you want to?

I was going to say “yes”… but…

After taking another look at the [MeanAngle](https://rosettacode.org/wiki/Averages/Mean_angle) Rosetta page, mean angle is a more complex problem than I thought. The example questions get at common corner cases on either general floating point precision or the behavior of common atan2() implementations.

The most common answers given are 0.0, -90.0, and 20.0 – but even though most languages agree, the second answer is clearly a strange artifact, when a more meaningful answer would be either 0 or NaN. A comment on the [XPL0 answer](https://rosettacode.org/wiki/Averages/Mean_angle#XPL0) explains:

> The second example is interesting because it computes ATan2(0.,0.), which is undefined in mathematics (like division by zero), but the floating point processor in IBM-PC-type computers defines it as zero. The reason -90 gets displayed instead is due to small rounding errors (and another extra-mathematical concept, -0). In fact almost any angle can result because of slight rounding errors as Y and X both approach zero.

Some of the answers special-case any very small values as equivalent to `atan2(0,0)` and manually return [something else](https://rosettacode.org/wiki/Averages/Mean_angle#Liberty_BASIC). But it seems like the [Julia answer](https://rosettacode.org/wiki/Averages/Mean_angle#Julia) gets it right – in Julia they implement two built-in methods, `sind()` and `cosd()`, which use a piecewise design to return good sin and cos values for degrees:

> <https://github.com/JuliaLang/julia/blob/master/base/special/trig.jl#L1200-L1218>

In particular this eliminates floating point error on common values such as:

- sin(180) degrees: 0.0, not -8.742278E-8
- sin(360) degrees: 0.0, not 1.7484555E-7

I feel like the need for a good piece-wise solution comes up over and over with winding. Anyway… I did a quick line-by-line translation of the Julia → processing:

```processing
// function sind(x::Real)
   float sind(float x) {
// if isinf(x)
// return throw(DomainError(x, "`x` cannot be infinite."))
     if (x==Float.POSITIVE_INFINITY || x==Float.NEGATIVE_INFINITY) {
       throw new IllegalArgumentException("x cannot be infinite");
     }
// elseif isnan(x)
// return oftype(x,NaN)
// end
     else if (Float.isNaN(x)) {
       throw new IllegalArgumentException("x cannot be NaN");
     }
// rx = copysign(float(rem(x,360)),x)
     float rx = x % 360;
// arx = abs(rx)
     float arx = abs(rx);
// if rx == zero(rx)
// return rx
     if (rx == 0.0 || rx == -0.0 ) {
       return rx;
     }
// elseif arx < oftype(rx,45)
// return sin_kernel(deg2rad_ext(rx))
     else if (arx < 45) {
       return sin(radians(rx));
     }
// elseif arx <= oftype(rx,135)
// y = deg2rad_ext(oftype(rx,90) - arx)
// return copysign(cos_kernel(y),rx)
     else if (arx <= 135) {
       float y = abs(cos(radians(90 - arx)));
       if (rx < 0) return -y;
       return y;
     }
// elseif arx == oftype(rx,180)
// return copysign(zero(rx),rx)
     else if (arx == 180) {
       if (rx < 0) return -0.0;
       return 0.0;
     }
// elseif arx < oftype(rx,225)
// y = deg2rad_ext((oftype(rx,180) - arx)*sign(rx))
// return sin_kernel(y)
     else if (arx < 255) {
       float y = radians(180.0 - arx);
       if(rx < 0) y = y * -1;
       return sin(y);
     }
// elseif arx <= oftype(rx,315)
// y = deg2rad_ext(oftype(rx,270) - arx)
// return -copysign(cos_kernel(y),rx)
     else if (arx <= 315) {
       float y = abs(cos(radians(270.0 - arx)));
       if (rx < 0) return y;
       return -y;
     }
// else
// y = deg2rad_ext(rx - copysign(oftype(rx,360),rx))
// return sin_kernel(y)
// end
     else {
       float y;
       if (rx < 0) y = radians(rx - 360);
       else y = radians(rx + 360);
       return sin(y);
     }
   }
// end

```

…and the final Processing function looks like this:

```auto
float sind(float x) {
  if (x==Float.POSITIVE_INFINITY || x==Float.NEGATIVE_INFINITY) {
    throw new IllegalArgumentException("x cannot be infinite");
  }
  else if (Float.isNaN(x)) {
    throw new IllegalArgumentException("x cannot be NaN");
  }
  float rx = x % 360;
  float arx = abs(rx);
  if (rx == 0.0 || rx == -0.0 ) {
    return rx;
  }
  else if (arx < 45) {
    return sin(radians(rx));
  }
  else if (arx <= 135) {
    float y = abs(cos(radians(90 - arx)));
    if (rx < 0) return -y;
    return y;
  }
  else if (arx == 180) {
    if (rx < 0) return -0.0;
    return 0.0;
  }
  else if (arx < 255) {
    float y = radians(180.0 - arx);
    if(rx < 0) y = y * -1;
    return sin(y);
  }
  else if (arx <= 315) {
    float y = abs(cos(radians(270.0 - arx)));
    if (rx < 0) return y;
    return -y;
  }
  else {
    float y;
    if (rx < 0) y = radians(rx - 360);
    else y = radians(rx + 360);
    return sin(y);
  }
}

```

Test it with:

```auto
float[] tests = {0, 180, 360, -360,
                 Float.POSITIVE_INFINITY,
                 Float.NEGATIVE_INFINITY,
                 Float.NaN
                };
void setup(){
  for (float test : tests) {
    try {
      float good = sind(test);
      float bad = sin(radians(test));
      println(good, bad);
    } catch (IllegalArgumentException e) {
      println(e);
    }
  }
}

```

Output:

> 0.0 0.0  
> 0.0 -8.742278E-8  
> 0.0 1.7484555E-7  
> -0.0 -1.7484555E-7  
> java.lang.IllegalArgumentException: x cannot be infinite  
> java.lang.IllegalArgumentException: x cannot be infinite  
> java.lang.IllegalArgumentException: x cannot be NaN

So that looks great.

I believe that if `getMeanAngle()` uses `sind()` and `cosd()` instead of `sin()` and `cos()` it will give better answers. But I’ll finish that up a little later.

---

_[View the full topic](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965)._
