Problem with HSB

If I get the average color using RGB I get this, which looks correct:

But when I try it with HSB, no matter what I try, I can’t get it right.
I have been playing with colorMode(HSB, x, x, x), but I still end up with wrong things.

https://editor.p5js.org/clankill3r/sketches/7UNGJYvGZ

Can someone help me?


function f_of_img(img, f) {

	const buffer = [0,0,0,0]; // the pixels are Uint8ClampedArray, but hue etc. wants a 'normal' array

	const n_pixels = img.pixels.length / 4;
	let p = 0;

	for (let i = 0; i < img.pixels.length; i += 4) {
		buffer[0] = img.pixels[i+0];
		buffer[1] = img.pixels[i+1];
		buffer[2] = img.pixels[i+2];
		buffer[3] = img.pixels[i+3];
		p += f(buffer);
	}

	return p / n_pixels;
}


function hue_of_img(img) {
	return f_of_img(img, hue);
}


function saturation_of_img(img) {
	return f_of_img(img, saturation);
}


function brightness_of_img(img) {
	return f_of_img(img, brightness);
}


function red_of_img(img) {
	return f_of_img(img, red);
}

function green_of_img(img) {
	return f_of_img(img, green);
}

function blue_of_img(img) {
	return f_of_img(img, blue);
}



let img;
let ready = false;

function preload() {
  img = loadImage("https://images.unsplash.com/photo-1573615168235-5b20c7c642ce?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max", (img)=> {
    img.loadPixels();
    ready = true;
  });
}

function setup() {
  createCanvas(400, 400);
  
}

function draw() {
  background(220);
  image(img, 0, 0, width, height);
  if (ready) {
    
//     const _red = red_of_img(img);
// 	   const _green = green_of_img(img);
// 	   const _blue = blue_of_img(img);
//    
//     fill(_red, _green, _blue);
    
    colorMode(HSB);
    
    const _hue = hue_of_img(img);
	const _saturation = saturation_of_img(img);
	const _brightness = brightness_of_img(img);
    
    
    fill(_hue, _saturation, _brightness);
    
    rect(0, 0, 100, 100);
    noLoop();
  }
  
}

I think what you’re trying to do is to just average hue, saturation and value on their own, and then put them together and expect them to produce the average color, but that’s not really how it works. It might work for saturation and value, but not with hue. Think of a color wheel. If you try to average a bright, saturated yellow rgb(255, 255, 0) and a bright, saturated blue rgb(0, 0, 255), the result should be a midtone grey rgb(127, 127, 127). But with hsv the result would be either a fully saturated pink or a fully saturated green. None of these make sense. So just continue using your RGB color average, since that technique actually works in RGB space.
One last thing, keep in mind that colors are weird. Just watch this video, it explains is better than I ever could, but I’ll give you my attempt anyways.

When capturing image data, a camera doesn’t store the brightness directly, but instead stores their square root (since humans are more sensitive to slight changes in a darker color than in a bright one). In order for color to be displayed correctly, the brightness value is squared by the display device in order to output the corretct brightness. So when averaging to colors, you can’t just take their average straight away, the result would be too dark (since the average of two square roots is less than the square root of an average). Instead, you first have to undo the camera’s squarrooting by squaring the color values, then take their average, and then square root the result back. this helps to avoid unpleasant artifacts.
In short, instead of cnew = (c1+c2)/2 you should be doing cnew = sqrt((c1^2+c2^2)/2).

I hope I could help you
Simon

1 Like

Hi thanks, nice video. I hope I can remember that the they I need it, for now I stick to RGB.