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

Add gradient map filter #1046

Merged
merged 1 commit into from
Nov 26, 2024
Merged

Conversation

YakoYakoYokuYoku
Copy link
Contributor

@YakoYakoYokuYoku YakoYakoYokuYoku commented Nov 2, 2024

Add a new filter for gradient mapping.

filter_gradientmap takes a frame and maps the intensity of its color to a linear gradient denoted by a list of color and position pairs separated by semicolons. Inspired by the Color Ramp node from Blender and the Gradient Map operation from image editors such as GIMP's and Krita's.

The following is a demo of the filter.

Original.

Agent327.webm

A sepia photograph effect.

stop.1='#170400 0.0' stop.2='#6b4e3b 0.25' stop.3='#c5a989 0.65' stop.4='#dfcebf 0.85' stop.5='#f6e5d5 1.0'
Agent327-sepia.webm

A thermal view effect.

stop.1='#15222a 0.0' stop.2='#121121 0.15' stop.3='#0a46f2 0.35' stop.4='#f41397 0.53' stop.5='#ffa351 1.0'
Agent327-thermal.webm

@bmatherly
Copy link
Member

Some other filters already have a method for specifying color stops for a gradient. Could that method work for you? If so, it would be nice to be consistent.
Here is an example:

- identifier: color.*

@YakoYakoYokuYoku
Copy link
Contributor Author

I've tried looking into other filters but I couldn't notice how filter_lightshow does it, I thought it was in no way possible. Thanks for the tip.

@YakoYakoYokuYoku YakoYakoYokuYoku force-pushed the gradientmap branch 2 times, most recently from 291f6fb to 995a0c3 Compare November 3, 2024 17:16
@YakoYakoYokuYoku
Copy link
Contributor Author

Formatted the code with clang-format. I've preferred to use std::map instead of mlt_cache because the latter doesn't have a way to get objects by their key value. I'll add a demo to the PR comment to show what the filter can do.

@bmatherly
Copy link
Member

I had to create this patch to get this code to compile.
gradientmap.diff.txt

I've tried looking into other filters but I couldn't notice how filter_lightshow does it

Your implementation does not match the other filters because each color has two parts - the color and the position. Combining the values makes it more difficult for applications to use this API. Can you get by without the position? The other filters that use this method make the colors equally spaced - so a stop position is not required.

src/modules/plus/filter_gradientmap.cpp Outdated Show resolved Hide resolved
src/modules/plus/filter_gradientmap.cpp Outdated Show resolved Hide resolved
src/modules/plus/filter_gradientmap.cpp Show resolved Hide resolved
@bmatherly
Copy link
Member

Did you check the list of filters we get from Frei0r and avfilter to see if one of them already provide this effect?

Here is a screenshot of the UI in Shotcut for the existing gradient method. If you can switch this filter to use the same method, then the code in Shotcut would be easily reused to add a UI for it.

image

@YakoYakoYokuYoku
Copy link
Contributor Author

Your implementation does not match the other filters because each color has two parts - the color and the position. Combining the values makes it more difficult for applications to use this API.

From my point of view I don't think that combining a color and a position into a pair would be too difficult, YMMV.

Can you get by without the position? The other filters that use this method make the colors equally spaced - so a stop position is not required.

I'm afraid to tell that positioning is required for the gradient stops, if not some gradient configurations would be very difficult to replicate with equidistant stops. For instance in a gradient widget that controls its stops with slides if two slidables were too close it'd require lots of their equidistant counterparts to represent the same gradient, not even mentioning of what happens with even more sliders.

Did you check the list of filters we get from Frei0r and avfilter to see if one of them already provide this effect?

They do provide effects that involve gradients, but those are for overlays not mapping. The filter that resembles what gradient mapping does is rgblut, but that only works per RGB channel not by source intensity.

Here is a screenshot of the UI in Shotcut for the existing gradient method. If you can switch this filter to use the same method, then the code in Shotcut would be easily reused to add a UI for it.

It can reuse the same widget if it supports slidable stops, else it could be modified or subclassed to suit this need.

@bmatherly
Copy link
Member

I'm afraid to tell that positioning is required for the gradient stops, if not some gradient configurations would be very difficult to replicate with equidistant stops. For instance in a gradient widget that controls its stops with slides if two slidables were too close it'd require lots of their equidistant counterparts to represent the same gradient, not even mentioning of what happens with even more sliders.

OK. I will not require that you implement the fixed stop positions. But then lets rename this parameter so that all the color.* parameters in MLT will have the exact same interface. I suggest to rename "color." to "stop.". Someone could add the "color." parameter as an alternate to the stop parameter. Bonus points if you also implement the fixed stop method as "color." as an alternate method to specify the gradient :)

It can reuse the same widget if it supports slidable stops, else it could be modified or subclassed to suit this need.

I understand that is possible, but I think it is unlikely that a volunteer will come along to do that.

src/modules/plus/filter_gradientmap.cpp Outdated Show resolved Hide resolved
src/modules/plus/filter_gradientmap.yml Outdated Show resolved Hide resolved
@ddennedy
Copy link
Member

Just fix these minor compile errors (debug build uses -Werror with some exceptions), and I will merge it. Looks good, nice addition!

/home/runner/work/mlt/mlt/src/modules/plus/filter_gradientmap.cpp: In function ‘int filter_get_image(mlt_frame, uint8_t**, mlt_image_format*, int*, int*, int)’:
/home/runner/work/mlt/mlt/src/modules/plus/filter_gradientmap.cpp:236:18: error: unused variable ‘p’ [-Werror=unused-variable]
  236 |         uint8_t *p = *image;
      |                  ^
/home/runner/work/mlt/mlt/src/modules/plus/filter_gradientmap.cpp: In function ‘mlt_filter_s* filter_gradientmap_init(mlt_profile, mlt_service_type, const char*, char*)’:
/home/runner/work/mlt/mlt/src/modules/plus/filter_gradientmap.cpp:275:24: error: unused variable ‘properties’ [-Werror=unused-variable]
  275 |         mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
      |                        ^~~~~~~~~~

@YakoYakoYokuYoku
Copy link
Contributor Author

Just fix these minor compile errors (debug build uses -Werror with some exceptions), and I will merge it. Looks good, nice addition!

...

I've ran the file with clang-tidy in my test setup but forgot to remove those unused variables here ... oops! Though, thanks for pointing it up.

@ddennedy ddennedy added this to the v7.28.0 milestone Nov 26, 2024
@ddennedy ddennedy merged commit c8961b4 into mltframework:master Nov 26, 2024
6 checks passed
@YakoYakoYokuYoku
Copy link
Contributor Author

Great! Thanks for the merge! I'll try to see if there's a chance to use mlt_cache instead of std::map for memoizing the gradient colors and if other color spaces can be used directly instead of converting them. I'll also try to implement support for it in Shotcut, though I'm not familiar with it, so I'll see.

@ddennedy
Copy link
Member

I do not think mlt_cache will be useful here. It is not for per-instance storage. mlt_cache is when a service has a lot to store (e.g. image) or something that is limited by the kernel (e.g. file handles). When you have many instances of the same filter in a project, it reduces the number of their internal part that is kept in memory. Consider, for example, the avformat producer has often 2 handles per object, and there can be hundreds of them in a project. Same goes for the image producers that cache the image.

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

Successfully merging this pull request may close these issues.

3 participants