libvips / php-vips

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

Trying to do in PHP what I've done in nip2 #241

Open gmcmicken opened 5 months ago

gmcmicken commented 5 months ago

I'm finding the learning curve to the methods in php steep and since I'm on a deadline though maybe I would ask for a bit of help. In nip2 there is Toolkit -> Filter -> Blend -> Blend Alpha, which works well for my purpose as it allows overlay mode, opacity, and offset. But I can't seem to replicate this in PHP.

image

Also, I know it's probably super straightforward but I also can't seem to do a photographic negative filter in PHP, I thought maybe maplut() could do it?

jcupitt commented 5 months ago

Hi @gmcmicken,

The "blend alpha" menu item is implemented in nip2's macro language -- if you click Toolkits / Edit you can read (and edit) the source code. It's in the repo here:

https://github.com/libvips/nip2/blob/master/share/nip2/start/Filter.def#L1016-L1274

It dates from way back, before libvips had composite. It'd probably be simpler to base a php solution on that.

What's your exact requirement? Do you just need to blend two images together with an offset? If you can provide some test data and desired output, I could help.

I also can't seem to do a photographic negative filter in PHP

This one at least is easy -- just $image = $image->invert();. You can look up the operators by description here:

https://www.libvips.org/API/current/func-list.html

Though now I check I see the description for invert is not very helpful. I'll try to improve it.

gmcmicken commented 5 months ago

Hi @jcupitt ,

Thanks for the help so far, when I tried invert() before and thought it didn't work it must have been before I read that each method returns a new image, and maybe I wrote the file from the original.

My requirements for blending are pretty much exactly what is in nip2, settings a blend mode, and offset, and opacity. I'm layering line art drawings, one with lines, one with shading, and I'm also modifying the line one to make a third which adds weight and depth (using blur & contrast and/or other faux anti-aliasing techniques). There's no alpha in the originals. Does that help?

jcupitt commented 5 months ago

Ah OK, then you can just cook up your own overlay pretty easily, it should just be a few lines of code. If you can make some sample images and an expected result it'll save me quite a bit of time writing an example.

gmcmicken commented 5 months ago

The shaded one would be the base, the outline one is the top layer with darken mode. The outline-filter would be some type of modified layer with an offset to shadow the lines. I would like to learn how to do the opacity even though I could work a solution without it, it would be good to have available.

Outline Outline

Outline, Filter Applied Outline-Filter Applied

Shading Shading

gmcmicken commented 5 months ago

Oops forgot the result. Result

jcupitt commented 5 months ago

Hi again, this seems to work:

#!/usr/bin/env php
<?php

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

if(count($argv) != 4) {
    echo("usage: ./overlay.php outline-image shading-image output-image\n");
    exit(1);
}

$outline = Vips\Image::newFromFile($argv[1], ['access' => 'sequential']);
$shading = Vips\Image::newFromFile($argv[2], ['access' => 'sequential']);
$output_filename = $argv[3];

// multiply blending
$image = $outline->divide(255)->multiply($shading);

$image->writeToFile($output_filename);

So for every pixel you compute (outline / 255) * shading, ie. multiply blending. It's a shame PHP doesn't have operator overloading -- it's usually a curse, but this is one of the rare examples of OO actually being useful.

Translation is easiest with gravity. Use it to add (for example) 4 pixels along the right and bottom, then crop four pixels off the left and top. In effect, you move the image up and left by 4. It sounds slow, but libvips will implement it all with pointer copies internally, so it's a fast and low memory way to do it.

Maybe (untested):

$shading = $shading
    ->gravity("north-west", $shading->width + 4, $shading->height + 4, ['extend' => 'copy'])
    ->crop(4, 4, $shading->width, $shading->height);
$image = $outline->divide(255)->multiply($shading);

I noticed your PNGs are all three band -- you could use one band PNGs for a useful drop in image size.

$ vips colourspace shading.png x.png b-w
$ ls -l shading.png x.png
-rw-rw-r-- 1 john john 178501 Jun 16 16:58 shading.png
-rw-r--r-- 1 john john 104232 Jun 16 17:16 x.png

Did you want to make the outline a little wider? It's easiest to do this with a small blur and a big contrast boost.

gmcmicken commented 5 months ago

// multiply blending $image = $outline->divide(255)->multiply($shading);

Thanks, can you give me an example how to make it semi transparent? Add band 4 and adjust to 128 or something like that? (it's not for this layer, but potentially for other layers)

Did you want to make the outline a little wider? It's easiest to do this with a small blur and a big contrast boost.

Yes I haven't settled on the exact technique yet, I have done a couple variations in photoshop I wasn't paying too much attention to the example I gave, I think it was gaussian r1.0 and legacy photoshop contrast 50.

I'm blending the text layer in too but that won't have any thickness added.

I noticed your PNGs are all three band -- you could use one band PNGs for a useful drop in image size.

They will be colored in practice, or perhaps themed using a color swap for the greys I haven't decided yet at which point to apply the color. Would it speed up processing or just the output size?

jcupitt commented 5 months ago

It's really easy -- if you do:

$outline->divide(255)

Your outline image is now 0 - 1 for black to white. To make the black lines in the outlines midgrey instead, you need to add eg. 0.6. But you don't want the white parts to go over 1, so you need to scale the outline down to 0 - 0.4 instead.

$opacity = 0.8;
$outline = $outline->multiply($opacity / 255)->add(1 - $opacity);
$image = $outline->multiply($shading);
$image->writeToFile($output_filename);

I haven't decided yet at which point to apply the color. Would it speed up processing or just the output size?

Ah I see. Yes, you can apply the colour near the end, it'd be a bit quicker.

gmcmicken commented 4 months ago

Hey sorry I went missing, thanks for your help again. I did decide to go with a colour output but if I want to target the greys now how would I use a conditional. In pseudocode it would be "if red == green == blue AND greater than 200"?

jcupitt commented 4 months ago

Maybe (untested):

// pixels where r == g == b
$grey_mask = $image[0]->equal($image[1])->equal(image[2]);
// pixels where r > 200
$bright_mask = $image[0]->more(200);
// AND them together
$mask = $grey_mask->andimage($bright_mask);
gmcmicken commented 4 months ago

Hmm I've tried your method on finding grey and I also tried to simplify $image = $image->equal([ $image[0], $image[0], $image[0] ])->ifthenelse([255,255,255], $image);

but it never hits on grey for some reason...

jcupitt commented 4 months ago

I don't think that will work, the code I posted is probably the best solution.

gmcmicken commented 4 months ago

Here's what happens when I try just the grey mask as you've demonstrated. It just grabs everything that isn't white including the coloured areas.

php > $image = Jcupitt\Vips\Image::newFromFile('input.png');
php > $grey_mask = $image[0]->equal($image[1])->equal($image[2]);
php > $grey_mask->writeToFile('output.png');  

output input

jcupitt commented 4 months ago

Oh duh, sorry. Of course it should be:

$grey_mask = $image[0]->equal($image[1])->andimage($image[1]->equal($image[2]));

(also untested)

gmcmicken commented 3 months ago

Thanks again. It's looking pretty good, I'm trying to automate the addition of a watercolour background and I'm wondering how I would go about comparing two masks, like the overlap of mask1 and mask2 as a number of pixels or a percentage?

image

jcupitt commented 3 months ago

Sorry, could you explain what you mean in more detail? Do you want to compute the number of pixels two masks have in common?

gmcmicken commented 3 months ago

Sorry, could you explain what you mean in more detail? Do you want to compute the number of pixels two masks have in common?

Exactly, ya, you got it.

jcupitt commented 3 months ago

You can write something to count the non-zero pixels in an image as:

function count_set($image)
{
   $avg = $image->notequal(0)->avg();

   return $image->width * $image->height * $avg / 255.0;
}

Then it'd just be:

$n_pixels_in_common = count_set($image1->andimage($image2));