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

Apply image filters like sepia, black-white #104

Closed
vnkapoor opened this issue Sep 2, 2020 · 20 comments
Closed

Apply image filters like sepia, black-white #104

vnkapoor opened this issue Sep 2, 2020 · 20 comments
Labels

Comments

@vnkapoor
Copy link

vnkapoor commented Sep 2, 2020

Hello @jcupitt

I want to add sepia, black & white, vintage etc.. effect on my image. Is there any method for apply this filters on my image.
I am trying it by using conv method is that method is for apply filters?

@jcupitt
Copy link
Member

jcupitt commented Sep 2, 2020

Hi @vnkapoor,

You can use eg. $image->colourspace('b-w') to convert an image to black and white.

For more complex colour gradients, convert to black and white, then map through a LUT containing your gradient. For example:

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

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

// make a lut which is a smooth gradient from start colour to stop colour, 
// with start and stop in CIELAB
function gradient($start, $stop) 
{   
    $lut = Vips\Image::identity()->divide(255);  
    // lut * stop + (1 - lut) * start
    $lut = $lut->multiply($stop)
        ->add($lut->multiply(-1)->add(1)->multiply($start));
    $lut = $lut->colourspace('srgb', ['source_space' => 'lab']);

    return $lut;
}

// various colours as CIELAB triples
$black = [0, 0, 0];
$red = [53, 80, 67];
$green = [88, -86, 83];
$blue = [32, 79, -108];
$white = [100, 0, 0];
$sepia = [32, 16, 35];

$image = Vips\Image::newFromFile($argv[1]);
$image = $image->colourspace("b-w")->maplut(gradient($sepia, $white));
$image->writeToFile($argv[2]);

Running:

$ ./tint.php ~/pics/Gugg_coloured.jpg x.jpg

Turns this:

Gugg_coloured

Into this:

x

@jcupitt
Copy link
Member

jcupitt commented Sep 2, 2020

conv runs a convolution on an image. You can use it to implement effects like sharpen, blur, emboss, etc.

@vnkapoor
Copy link
Author

vnkapoor commented Sep 3, 2020

@jcupitt Thank you for quick reply. I tried your above code for sepia effect.

VIPS Sepia Filter
vipssepia

Imagick Sepia Filter
imagicsepia

But i want to same sepia effect on my image like it's in Imagick image. I need to change in CIELAB triples for that?

@jcupitt
Copy link
Member

jcupitt commented Sep 3, 2020

Maybe $sepia = [0, 5, 50];? You'd need to experiment.

@vnkapoor
Copy link
Author

vnkapoor commented Sep 3, 2020

Okay, got it. One more thing just need to confirm $sepia = [0, 5, 50], What are 0, 5, 50 values are for is it a rgb? So can i get better idea for other effects.

@jcupitt
Copy link
Member

jcupitt commented Sep 3, 2020

They are CIELAB coordinates. L is black to white 0 to 100, A is red to green -100 to +100, and B is blue to yellow -100 to +100.

@vnkapoor
Copy link
Author

vnkapoor commented Sep 3, 2020

Okay, and can i manage brightness, contrast, saturation, hueRotation etc by using the above code which you provided or any other method for that?

@kleisauke
Copy link
Member

Brightness, saturation and hue could be implemented as a modulate operation. It involves a combination of converting an image to the LCH colorspace via vips_colourspace and through a linear transform via vips_linear.

For example:

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

require __DIR__ . '/vendor/autoload.php';

use Jcupitt\Vips\Image;
use Jcupitt\Vips\Interpretation;

function greyscale(Image $image): Image
{
    return $image->colourspace(Interpretation::B_W);
}

function sepia(Image $image): Image
{
    $sepia = Image::newFromArray([
        [0.3588, 0.7044, 0.1368],
        [0.2990, 0.5870, 0.1140],
        [0.2392, 0.4696, 0.0912]
    ]);

    if ($image->hasAlpha()) {
        // Separate alpha channel
        $imageWithoutAlpha = $image->extract_band(0, ['n' => $image->bands - 1]);
        $alpha = $image->extract_band($image->bands - 1, ['n' => 1]);
        return $imageWithoutAlpha->recomb($sepia)->bandjoin($alpha);
    }

    return $image->recomb($sepia);
}

function modulate(Image $image, float $brightness = 1.0, float $saturation = 1.0, float $hue = 0.0): Image
{
    $oldInterpretation = $image->interpretation;

    // Normalize hue rotation to [0, 360]
    $hue %= 360;
    if ($hue < 0) {
        $hue = 360 + $hue;
    }

    // Modulate brightness, saturation and hue
    if ($image->hasAlpha()) {
        // Separate alpha channel
        $imageWithoutAlpha = $image->extract_band(0, ['n' => $image->bands - 1]);
        $alpha = $image->extract_band($image->bands - 1, ['n' => 1]);
        return $imageWithoutAlpha
            ->colourspace(Interpretation::LCH)
            ->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
            ->colourspace($oldInterpretation)
            ->bandjoin($alpha);
    }

    return $image
        ->colourspace(Interpretation::LCH)
        ->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
        ->colourspace($oldInterpretation);
}

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

// Sepia filter.
//$image = sepia($image);

// Convert to 8-bit greyscale; 256 shades of grey.
//$image = greyscale($image);

// Decrease brightness and saturation while also hue-rotating by 90 degrees.
$image = modulate($image, 0.5, 0.5, 90);

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

(I also added a hard-coded matrix that seems to work for a sepia-like effect)

@vnkapoor
Copy link
Author

vnkapoor commented Sep 3, 2020

@kleisauke
I tried your sepia effect function, it works fine for me but it's not perfect as i want. Here below i add my sepia array which i am using for my imagick -color-matrix command it's using 5*4 matrix, how can i manage below array in above sepia function?

`array( "matrix" => array(0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0)),

@kleisauke
Copy link
Member

kleisauke commented Sep 3, 2020

That seems to be the equivalent of CSS's filter: sepia(1) function. It can be achieved with this matrix:

$sepia = Image::newFromArray([
    [0.393, 0.769, 0.189],
    [0.349, 0.686, 0.168],
    [0.272, 0.534, 0.131]
]);

There is also an online playground available for JavaScript (which has a similar API as the PHP binding) if you want to experiment with this:
http://kleisauke.github.io/wasm-vips/playground/#thumbnail-smartcrop-sepia

@vnkapoor
Copy link
Author

vnkapoor commented Sep 3, 2020

Okay, Got it.
@jcupitt @kleisauke Thank you for the help! and support.

@jcupitt
Copy link
Member

jcupitt commented Sep 7, 2020

Here's another way of doing contrast adjustment:

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

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

// make a tone map LUT ... pull shadows down, push highlights up
// there are lots of other params, see the docs
$tone = Vips\Image::tonelut(['S' => -10, 'H' => +10]);

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

// just map L of CIELAB so we change lightness but don't boost saturation
// use LABS: LAB encoded as signed short
$lab = $image->colourspace('labs');
$lab[0] = $lab[0]->maplut($tone);
$image = $lab->cast('short')->colourspace('srgb');

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

This make an S-shaped curve and maps just L (lightness) through it. This lets you boost contrast without flattening highlights or squashing shadows.

@vnkapoor
Copy link
Author

vnkapoor commented Sep 9, 2020

@jcupitt @kleisauke
Thank you for your quick support. The sepia effect is working perfectly in my code. But i have some another effects like vintage, kodachrome, Technicolor, polaroid, brownie etc.

But o/p of my image is different with compare to imagick. Here i add my code for Technicolor effects.

function technicolor($image) {
    $technicolor = Vips\Image::newFromArray([
        [1.91252, -0.85453, -0.09155],
        [0.00018133333333333334, -0.30878, 1.76589],
        [-0.10601, -0.0010819215686274511, -0.2311]
    ]);

    if ($image->hasAlpha()) {
        // Separate alpha channel
        $imageWithoutAlpha = $image->extract_band(0, ['n' => $image->bands - 1]);
        $alpha = $image->extract_band($image->bands - 1, ['n' => 1]);
        return $imageWithoutAlpha->recomb($technicolor)->bandjoin($alpha);
    }
    return $image->recomb($technicolor);
}

//Below is the actual imgick matrix for technicolor
array(1.91252, -0.85453, -0.09155, 0, 0.00018133333333333334, -0.30878, 1.76589, -0.10601, 0, -0.0010819215686274511, -0.2311, -0.75018, 1.84759, 0, 0.0004759607843137255, 0, 0, 0, 1, 0)),
How can i achieve same Imagick matrix in vips?

When i am trying to below example it gives me error

$technicolor = Vips\Image::newFromArray([
        [1.91252, -0.85453, -0.09155, 0, 0.00018133333333333334],
        [0.00018133333333333334, -0.30878, 1.76589],
        [-0.10601, -0.0010819215686274511, -0.2311]
    ]);

@jcupitt
Copy link
Member

jcupitt commented Sep 9, 2020

You have five numbers in the first row of your matrix, not three.

@vnkapoor
Copy link
Author

vnkapoor commented Sep 9, 2020

@jcupitt
I tried below matrix but it gives me this error recomb: bands in must equal matrix width

$technicolor = Vips\Image::newFromArray([
        [1.91252, -0.85453, -0.09155, 0, 0.00018133333333333334],
        [-0.30878, 1.76589, -0.10601],
        [0, -0.0010819215686274511, 0],
        [-0.2311, -0.75018, 1.84759],
        [0, 0.0004759607843137255, 0],
        [0, 1, 0]
    ]);

@jcupitt
Copy link
Member

jcupitt commented Sep 9, 2020

Yes, it needs to be a 3x3 matrix.

@vnkapoor
Copy link
Author

vnkapoor commented Sep 9, 2020

@jcupitt
Okay, is there any way to achieve same technicolor effect like imagick vips. Because by using 3 * 3 matrix the output image is differ from imagick

@jcupitt
Copy link
Member

jcupitt commented Sep 9, 2020

You'll need to read the imagick code and reimplement it in libvips.

@vnkapoor
Copy link
Author

@jcupitt
I achieved this (vintage, kodachrome, Technicolor, polaroid, brownie) effects using vips. Below i add my matrix for anyone who need it.

$brownie = Vips\Image::newFromArray([
        [0.59970, 0.34553, -0.27082],
        [-0.03770, 0.86095, 0.15059],
        [0.24113, -0.07441, 0.44972]
    ]);

$vintage = Vips\Image::newFromArray([
        [0.62793, 0.32021, -0.03965],
        [0.02578, 0.64411, 0.03259],
        [0.0466, -0.08512, 0.52416]
    ]);

 $kodachrome = Vips\Image::newFromArray([
        [1.12855, -0.39673, -0.03992],
        [-0.16404, 1.08352, -0.05498],
        [-0.16786, -0.56034, 1.60148]
    ]);
$technicolor = Vips\Image::newFromArray([
        [1.91252, -0.85453, -0.09155],
        [-0.30878, 1.76589, -0.10601],
        [-0.2311, -0.75018, 1.84759]
    ]);

$polaroid = Vips\Image::newFromArray([
        [1.438, -0.062, -0.062],
        [-0.122, 1.378, -0.122],
        [-0.016, -0.016, 1.483]
    ]);

@jcupitt
Copy link
Member

jcupitt commented Sep 10, 2020

Oh hey, that's great! Thank you for sharing, @vnkapoor.

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

No branches or pull requests

3 participants