libvips / php-vips

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

Apply image filters like sepia, black-white #104

Closed vnkapoor closed 3 years ago

vnkapoor commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

vnkapoor commented 4 years ago

@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 commented 4 years ago

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

vnkapoor commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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

jcupitt commented 4 years ago

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 commented 3 years ago

@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 commented 3 years ago

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

vnkapoor commented 3 years ago

@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 commented 3 years ago

Yes, it needs to be a 3x3 matrix.

vnkapoor commented 3 years ago

@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 commented 3 years ago

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

vnkapoor commented 3 years ago

@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 commented 3 years ago

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