libvips / php-vips

php binding for libvips
MIT License
618 stars 25 forks source link

Apply math operation to each pixel #181

Closed JuanLuisGarciaBorrego closed 1 year ago

JuanLuisGarciaBorrego commented 1 year ago

Hello and thanks you in advance!

I am trying to apply an operation to each pixel of the file .tif

$image = Vips\Image::tiffload($pathFileTif);
$image->colourspace(Vips\Interpretation::CMYK); // by default my file has CMYK mode

$pixels = [];
for ($x = 0; $x < $width; $x++) {
   for ($y = 0; $y < $height; $y++) {
      $pixel = $image->getpoint($x, $y);

      $c= $pixel[0] ; // apply operation
      $m = $pixel[1]; // apply operation
      $y = $pixel[2]; // apply operation
      $k = $pixel[3]; // apply operation

      $pixels[$x][$y] = [$cyan, $magenta, $yellow, $key];
       //How do I generate an image of the same format-dimensions but with the new pixels?
   }
}

Thanks

jcupitt commented 1 year ago

Hi @JuanLuisGarciaBorrego,

Have you seen?

https://libvips.github.io/php-vips/classes/Jcupitt-Vips-Image.html

You can write stuff like:

$image = Vips\Image::tiffload($pathFileTif, ["access" => "sequential"]);
// add 128 to the K band
$image = $image->add([0, 0, 0, 128]);
$image->writeToFile("x.tif");

You should be able to do anything you need, and no looping. It'll stream the image through your system and won't load the whole thing.

JuanLuisGarciaBorrego commented 1 year ago

Thanks @jcupitt for the help!

Is there a way to make a callback that receives the original pixels and returns the changed pixels? Something like that:

$image = Vips\Image::tiffload($pathFileTif, ["access" => "sequential"]);

$myCustomFilter = function($originalPixel) { 
     //apply pixel operations
     return $updatePixel;
}

$image = $image->update(myCustomFilter);
$image->writeToFile("x.tif");

I have observed that Image::add() calls a callable method but I can't get the original pixel....

Any ideas? Thanks John!!

jcupitt commented 1 year ago

You can't really do pixel-by-pixel processing in PHP -- it's much too slow.

php-vips has a pretty complete set of operations, so you should be able to achieve the effect you want by just stringing them together. What value do you want to compute?

You can also use C or C++ to add operations to libvips then call them from PHP, if that's any help.

JuanLuisGarciaBorrego commented 1 year ago

thanks @jcupitt

I just imagined it wasn't possible from PHP. I need to apply certain formulas that alter the cmyk/rgb. It would be something like the Image::add method

I'm going to go over C and try to create a method like the one mentioned above and then call it from PHP. It

Could you briefly tell me the steps to follow?

  1. Fork Libvips
  2. Install project
  3. Create a alterPixel.c file, into libvips/arithmetic folder , Would it be correct in this folder?
  4. PR
  5. Merge
  6. Fork PHP Libvips
  7. Add method Image::alterPixel similar to Image:add()
  8. PR
  9. Merge

Would this be feasible John? Thanks!

jcupitt commented 1 year ago

libvips supports plugins, there's an example here:

https://github.com/jcupitt/vips-gmic

You don't need to change the php binding, it'll appear automatically.

But you almost certainly don't need to do this. libvips has a complete set of arithmetic operations, so any pixel manipulation code you could write in php, you can implement using ->add() etc. If you tell me roughly what you want to implement I could show you how to do it in php-vips.

Pixel manipulation done with a chained sequence of ->add(), ->shift() etc. is around half the speed of hand coded C, but libvips can parallelise it automatically, so as long as you have more than one core available, it should usually be as fast or faster. And you'll save yourself weeks of annoying work and then years of even more annoying maintenance.

JuanLuisGarciaBorrego commented 1 year ago

The code I need to do for each pixel is similar to (conditions according to the pixel value, sum between value ):

$pixel = $image->getpoint($x, $y);

$c= $pixel[0] ; 
$m = $pixel[1];
$y = $pixel[2];
$k = $pixel[3]; 

if($m === 0) {
   $m = 50;
}

if($y + $k > 100) {
   $y = 0;
}

return [$c,$m, $y, $k];

Do you think it will be possible to do it with the functions mentioned in the previous comment ->add() ->chift()?

jcupitt commented 1 year ago

Sure, try:

#!/usr/bin/php
<?php

require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;

$image = Vips\Image::newFromFile($argv[1], ["access" => "sequential"]);

[$c, $m, $y, $k] = $image->bandsplit();

$m = $m->equal(0)->ifthenelse(50, $m);

$y = $y->add($k)->more(100)->ifthenelse(0, $y);

$image = $c->bandjoin([$m, $y, $k]);

$image->writeToFile($argv[2]);

With this test image:

x

I can run:

$ ./cmyk.php x.jpg y.jpg

To make:

y

JuanLuisGarciaBorrego commented 1 year ago

WoOOuuhh!!

Thanks @jcupitt ! I'm going to try!!

Thanks!!!

jcupitt commented 1 year ago

I should have added some explanation:

[$c, $m, $y, $k] = $image->bandsplit();

All the $variables here are images. bandsplit is splitting the four channel CMYK image up into four one band images, then we use php array destructuring to get those into four variables.

$y = $y->add($k)->more(100)->ifthenelse(0, $y);

It's a shame php doesn't have operator overloading, it would make this much easier to read. You can imagine this as:

$y = (($y + $k) > 100)->ifthenelse(0, $y);

The relational operators (eg. > here) make a bool image (an 8-bit image with 0 for false and 255 for true). The if-then-else is $condition_image->ifthenelse($true_image, $false_image). For each pixel, it'll test the condition for != 0 and pick the corresponding pixel from the then side or the else side.

The docs I linked explain this, and some other stuff too:

https://libvips.github.io/php-vips/classes/Jcupitt-Vips-Image.html

JuanLuisGarciaBorrego commented 1 year ago

Thanks you very much John

I am looking at the information carefully =)

One question more..

Can I see the original value of the pixel from pointer?? In the next image I'm showing from $c from

[$c, $m, $y, $k] = $image->bandsplit(); 
Captura de pantalla 2023-01-09 a las 17 34 55

Thanks @jcupitt !

jcupitt commented 1 year ago

You can use writeToArray to save an image to a PHP array and then print it, that might be simplest. Maybe:

#!/usr/bin/php
<?php

require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;

$image = Vips\Image::newFromFile($argv[1]);

// just 2x2 pixels for testing 
$image = $image->crop(0, 0, 2, 2);

[$c, $m, $y, $k] = $image->bandsplit();

$m = $m->equal(0)->ifthenelse(50, $m);

$y = $y->add($k)->more(100)->ifthenelse(0, $y);

$after = $c->bandjoin([$m, $y, $k]);

echo("before:\n");
print_r($image->writeToArray());

echo("after:\n");
print_r($after->writeToArray());

These things can be annoying to debug. I usually develop complex stuff in nip2, then code up in python/ruby/php/whatever right at the end.

https://github.com/libvips/nip2

JuanLuisGarciaBorrego commented 1 year ago

Hi @jcupitt !!

I have a .tiff image with CMYK color mode

$image = Vips\Image::tiffload($path, ["access" => "sequential"])
$image->colourspace(Vips\Interpretation::CMYK); 

$image = $image->crop(0, 0, 2, 2);

$image->writeToArray()

The array's values are RGB, not CMYK

165,0,58,0 | 146,0,71,0 173,0,53,0 | 146,0,65,0

If I try to force the image with php $image->colourspace(Vips\Interpretation::RGB); return the error ibvips error: vips_colourspace: no known route from 'cmyk' to RGB (I understand that error is correct)

With mode Vips\Interpretation::CMYK should return the data in CMYK, right?


I understand that with concatenation of filter methods like ->add() or->ifthenelse(), etc, but I don't know how to pass code like:

[$c, $m, $y, $k] = $pixel;

if($m === 50 || $y > 50) {
     return [$c, $m, $y, $k] 
}

if($c>0) {
 //...
 return [$c, $m, $y, $k] 
}

$minValueOfPixel = getMinValueFrom($c, $m, $y, $k)
$mediumValueOfPixel = getMinValueFrom($c, $m, $y, $k)
$maxValueOfPixel = getMinValueFrom($c, $m, $y, $k)

....multiples steps...

return [$c, $m, $y, $k] 

I think I will try to make the plugin, could you guide me the steps to follow?

Thanks John!

jcupitt commented 1 year ago

php-vips doesn't have any (or almost no) operations which modify images. Everything will make a new image, and it has some tricks behind the scenes to make this quick.

Try:

$image = Vips\Image::tiffload($path, ["access" => "sequential"])
$image = $image->colourspace("cmyk"); 
jcupitt commented 1 year ago

Wait, now I read more carefully, just do:

$image = Vips\Image::tiffload($path, ["access" => "sequential"])
$image = $image->crop(0, 0, 2, 2);
$image->writeToArray();

And you'll get CMYK.

The array's values are RGB, not CMYK

No, I think those are CMYK. What image are you testing with?

With that test image I used above ^^^^ I see:

$ vips getpoint cmyk6.jpg 0 0 
23 13 11 0 

ie. low ink density, so a light grey (as you'd expect).

jcupitt commented 1 year ago

For this:

$minValueOfPixel = getMinValueFrom($c, $m, $y, $k)

You need bandrank:

https://www.libvips.org/API/current/libvips-conversion.html#vips-bandrank

eg.:

$min = Vips\Image::bandrank([$c, $m, $y, $k], 0);

Now $min will be a one-band image containing the mimimum of c, m, y, k at each point

jcupitt commented 1 year ago

I think I will try to make the plugin, could you guide me the steps to follow?

Just have a look at that gmic example. This file is the plugin source:

https://github.com/jcupitt/vips-gmic/blob/master/vips_gmic.cpp

And this is how you build it:

https://github.com/jcupitt/vips-gmic/blob/master/Makefile

But you'll need to be a pretty good C/C++ dev, and you'll need to do quite a bit of reading.