libvips/php-vips

Having trouble implementing brightness

axkirillov opened this issue · 9 comments

Hi!

I am trying to implement the following javascript algorithm in php-vips. It seems to me that the calculations are identical. But the result differs.
Here is the javascript


for (let i = 0; i < length; i += 4) {
  const avg = (r + g + b) / 765;
		
  const factor = 1 + (brightness * (1 - avg));
		
  r *= factor;
  g *= factor;
  b *= factor;
}

here is the php-vips version

$sum = $r->sum([$g, $b]);
$avg = $sum->linear(1 / (3 * 255), 0);
$factor = $avg->linear(-1 * $brightness, 1 + $brightness);
$r = $r->multiply($factor);
$g = $g->multiply($factor);
$b = $b->multiply($factor);
$imageResult = $r->bandjoin([$g, $b]);

Result fot -1 brightness:
js
image
php-vips
canvas_1679415206326

Figured it out, I was using ::sum incorrectly
the correct usage is as static function:
$sum = Vips\Image::sum([$r, $g, $b]);

Hi @axkirillov,

You could also write this as (untested):

$brightness = 0.7;
$image = Vips\Image::newFromFile("xxx");
// scrgb is linear 0-1 pixels, bandmean makes a one-band image by averaging bandwise
$factor = $image->colourspace("scrgb")->bandmean()->linear(-1 * $brightness, 1 + $brightness);
$image = $image * factor;
// back to standard 8 bit rgb
$image = $image->colourspace("srgb");

I'd expect working in linear space to give more pleasing results as well.

I wish php had operator overloads :(

Thank you for the advice!

The first part of your suggestion works great

$factor = $image->colourspace("scrgb")->bandmean()->linear(-1 * $brightness, 1 + $brightness);

doing this however results in a different image than expected

$image = $image->multiply($factor);
$image = $image->colourspace("srgb");

expected (brightness -1)
image
result
image

Another thing that probably stems from me not understanding colorspaces and such is that when the original image is png, the following implementation also produces not quite expected result. I'll paste the entire code here since there might be smth relevant I am missing.

		$start = microtime(true);
		$hasAlpha = $image->hasAlpha();

		// remove alpha, if any
		if ($hasAlpha) {
			$alpha = $image->extract_band($image->bands - 1, ['n' => 1]);
			$image = $image->extract_band(0, ['n' => $image->bands - 1]);
		}

		/** 
		 * @var Vips\Image $r 
		 * @var Vips\Image $g 
		 * @var Vips\Image $b 
		 * */
		[$r, $g, $b] = $image->bandsplit();
		$brightness = $this->brightness / 100;

		// equivalent javascript

		//for (let i = 0; i < length; i += 4) {
		//  const avg = (data[i] + data[i + 1] + data[i + 2]) / 765;
		//
		//  const factor = 1 + (valueToUse * (1 - avg));
		//
		//  data[i] *= factor;
		//  data[i + 1] *= factor;
		//  data[i + 2] *= factor;
		//
		// scrgb is linear 0-1 pixels, bandmean makes a one-band image by averaging bandwise

		$factor = $image->colourspace("scrgb")->bandmean()->linear(-1 * $brightness, 1 + $brightness);	//}
		$r = $r->multiply($factor);
		$g = $g->multiply($factor);
		$b = $b->multiply($factor);
		$image = $r->bandjoin([$g, $b]);

		// add alpha back
		if ($hasAlpha) {
			$image = $image->bandjoin($alpha);
		}

		$blob = $image->writeToBuffer($hasAlpha ? '.png' : '.jpg');
		$imagick = new \Imagick();
		$imagick->readImageBlob($blob);

Prior to this the image is simply loaded like this:

Vips\Image::newFromBuffer($this->getRawContent($url));

expected (brightness is 1)
image
result
image

doing this however results in a different image than expected

Yes, it'll look a bit different.

sRGB images have a gamma, meaning that numeric values in the file have a power law relationship with brightness (usually 2.2). Converting to scrgb (note the c) makes an image with pixels in 0-1 and a linear relationship with light. Lightness adjustments made in the space should look more natural (ie. better).

If you prefer the gamma look, you can just do:

$image = $image->divide(255);

instead.

You don't need to bandsplit and bandjoin. You can just do:

$image = $image->linear([1, 2, 3], [4, 5, 6]);

To scale each band by a different amount (if that's what you want to do).

So just write:

		$brightness = $this->brightness / 100;

		// equivalent javascript

		//for (let i = 0; i < length; i += 4) {
		//  const avg = (data[i] + data[i + 1] + data[i + 2]) / 765;
		//
		//  const factor = 1 + (valueToUse * (1 - avg));
		//
		//  data[i] *= factor;
		//  data[i + 1] *= factor;
		//  data[i + 2] *= factor;
		//
		// scrgb is linear 0-1 pixels, bandmean makes a one-band image by averaging bandwise

		$factor = $image->colourspace("scrgb")->bandmean()->linear(-1 * $brightness, 1 + $brightness);
		$image = $image->multiply($factor);

Thank you for the suggestions!
Indeed, divide(255) works as expected for both png and jpg
$image = $image->multiply($factor); also works, I might have confused the two problems at first.