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

Colourspace conversion for gradient creation #130

Closed
jarodium opened this issue Feb 14, 2022 · 5 comments
Closed

Colourspace conversion for gradient creation #130

jarodium opened this issue Feb 14, 2022 · 5 comments

Comments

@jarodium
Copy link

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

jcupitt commented Feb 15, 2022

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

jcupitt commented Feb 15, 2022

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

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

jcupitt commented Feb 15, 2022

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

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

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