libvips / php-vips

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

Colourspace conversion for gradient creation #130

Closed jarodium closed 2 years ago

jarodium commented 2 years ago

Hello,

I'm trying to create a user input gradient, in which, the user feeds html's hexcodes and then the software generates a gradient. I one of the issues here i've found the following code:

function fxGradient($start,$stop) {

    $lut = \Jcupitt\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;     
}

In which I call like this:

$corstart = parse("#000000"); //returns [0,0,0]
$corstop = parse("#FF0000"); //returns [255,0,0]

$image = $this->image->maplut(fxGradient($corstart, $corstop));

in which, my start and stop codes are rgb triples [0,0,0] and [255,0,0] which I got from @kleisauke 's useful parse function.

I figure I should make use of

  1. sRGB2scRGB
  2. scRGB2XYZ
  3. XYZ2Lab

in that particular order.

I would like to ask if there is an easier way to create a gradient of RGB colors and choose the amount of start color ( I suspect I should play with the add function a bit ) , this, while avoiding colorspace conversion.

Best regards

PS: I would like to also like to suggest opening up "Discussions" for the community to trade ideas... I'm starting to feel heavy conscience of having my questions here as issues :)

jcupitt commented 2 years ago

Hi again, I turned on "discussions" for php-vips, thanks for the suggestion.

You can make gradients by defining a distance function which varies between 0 and 1, then using start * d + end * (1 - d) to blend between two triplets. For example:

#!/usr/bin/python3

import pyvips

start = [255, 0, 0]
end = [0, 0, 255]
x = pyvips.Image.xyz(100, 200)
x = x - [x.width / 2, x.height / 2]
d = (x[0] ** 2 + x[1] ** 2) ** 0.5
d = (d * 10).cos() / 2 + 0.5
out = d * end + (1 - d) * start
out = out.copy(interpretation="srgb")

out.write_to_file("x.png")

(python, but the php version is obvious) to make the very ugly:

x

You can use any colourspace for the blend -- LAB ought to be a bit smoother, you're right. You can do a LAB blend with eg.:

#!/usr/bin/python3

import pyvips

start = [100, 50, 0]
end = [50, 0, 50]
size = 512
x = pyvips.Image.xyz(size, size)
x = x - [x.width / 2, x.height / 2]
d = (x[0] ** 2 + x[1] ** 2) ** 0.5 / (2 ** 0.5 * size / 2)
out = d * end + (1 - d) * start
out = out.copy(interpretation="lab")

out.write_to_file("x.png")

Now start and end and the blend are in CIELAB. We tag the image as LAB at the end, and write_to_file will automatically convert to srgb for us. It makes the (again) very ugly:

x

jcupitt commented 2 years ago

Oh huh now I look more carefully, that's almost what your php is doing. I'm not sure I understand your question, could you rephrase?

jarodium commented 2 years ago

Hello @jcupitt

Thank you for taking the time for this issue.

The image below has a linear gradient ( from black to white ) which it is what i'm trying to achieve:

image

The gradient function is taken from #104 but I skip converting to black and white ( because I will need colors ). The function almost does what it is intended, but in my dev server, it is only using the $black and white lab triples hardcoded so I can make a control image.

I wish to change those triples to any other color pair, so I can manage them on my user's interface.

  1. Since I can use any colorspace, I will make tests with sRGB directly, but if, as you say, lab looks nicer, then I will have to convert the HTML to RGB, RGB to XYZ and XYZ to lab. or:
  2. I will try to recreate the function you have written in PHP ( i'm kinda stuck with PHP on the script i'm working on, since it is web based :) ) , but yes, I have stumbled in that particular function in libvips discussion board but had no idea at that time how to work with her in php.

After i'm done ( if I get the conversion right ), I will prepare two images using both fxGradient and the new gradient using .XYZ and share them here.

Once again, thank you for your help Best regards

jarodium

jcupitt commented 2 years ago

Here's a demo prog:

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

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

if(count($argv) != 4) {
    echo("usage: ./gradient.php input-image output-image 'text to write'\n");
    exit(1);
}

# make a vertical gradient from the start to end
# size it to match $image
function gradient($image, $start, $end): Vips\Image {
    # a two-band image the size of $image whose pixel values are their
    # coordinates
    $xyz = Vips\Image::xyz($image->width, $image->height);

    # the distance image: 0 - 1 for the start to the end of the gradient
    $d = $xyz[1]->divide($xyz->height);

    # and use it to fade the quads ... we need to tag the result as an RGB
    # image
    return $d
        ->multiply($end)
        ->add($d
            ->multiply(-1)
            ->add(1)
            ->multiply($start)
        )
        ->copy(["interpretation" => "srgb"]);
}

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

# from transparent black at the top to solid black at the bottom
$grad = gradient($image, [0, 0, 0, 0], [0, 0, 0, 255]);

# make a text image to fit within width/height
$text = Vips\Image::text($argv[3], [
    "font" => "sans",
    "rgba" => true,
    "width" => $image->width * 0.6,
    "height" => $image->height * 0.2
]);

# layer the background image, the gradient, and the text on top of each other
# the x/y parameters position the two overlaying layers
$image = $image->composite([$grad, $text], "over", [
    "x" => [0, $image->width * 0.1],
    "y" => [0, $image->height / 2 - $text->height / 2]
]);

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

With this test image:

https://cdn.motor1.com/images/mgl/63KVE/s1/lamborghini-forsennato-concept.jpg

I can run this:

$ ./gradient.php ~/pics/lamborghini-forsennato-concept.jpg x.jpg '<span color="white">hello there!</span>'

To make:

x

jarodium commented 2 years ago

Awesome. I will tweak the function to fine tune it with more options.

Meanwhile, I have talked to a friend of mine that is a designer and he explained it to me that I could use HSL values from HTML instead of converting Hex to RGB, so I think I can go back to the original idea of using LAB in order to provide a better gradient output, like you said.

For now I will close this issue and will post the completed function with my tweaks :)

Once again, thank you for your help Best regards jarodium