Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
gmcmicken opened this issue Jun 14, 2024 · 18 comments
Open

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

gmcmicken opened this issue Jun 14, 2024 · 18 comments

Comments

@gmcmicken
Copy link

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
Copy link
Member

jcupitt commented Jun 15, 2024

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
Copy link
Author

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
Copy link
Member

jcupitt commented Jun 15, 2024

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
Copy link
Author

gmcmicken commented Jun 15, 2024

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
Copy link
Author

Oops forgot the result.
Result

@jcupitt
Copy link
Member

jcupitt commented Jun 16, 2024

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
Copy link
Author

// 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
Copy link
Member

jcupitt commented Jun 16, 2024

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
Copy link
Author

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
Copy link
Member

jcupitt commented Jul 18, 2024

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
Copy link
Author

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
Copy link
Member

jcupitt commented Jul 19, 2024

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

@gmcmicken
Copy link
Author

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
Copy link
Member

jcupitt commented Jul 19, 2024

Oh duh, sorry. Of course it should be:

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

(also untested)

@gmcmicken
Copy link
Author

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
Copy link
Member

jcupitt commented Aug 16, 2024

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
Copy link
Author

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
Copy link
Member

jcupitt commented Aug 16, 2024

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));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants