Skip to content

Commit

Permalink
Add HSL Primaries and HSL Range filters
Browse files Browse the repository at this point in the history
  • Loading branch information
bmatherly committed Nov 25, 2024
1 parent 68319ff commit 8d48e87
Show file tree
Hide file tree
Showing 7 changed files with 902 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/modules/plus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ add_library(mltplus MODULE
filter_chroma.c
filter_dynamictext.c
filter_dynamic_loudness.c
filter_hslprimaries.c
filter_hslrange.c
filter_invert.c
filter_lift_gamma_gain.c
filter_loudness.c
Expand Down Expand Up @@ -67,6 +69,8 @@ install(FILES
filter_chroma.yml
filter_dynamic_loudness.yml
filter_dynamictext.yml
filter_hslprimaries.yml
filter_hslrange.yml
filter_invert.yml
filter_lift_gamma_gain.yml
filter_loudness_meter.yml
Expand Down
18 changes: 18 additions & 0 deletions src/modules/plus/factory.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ extern mlt_filter filter_dynamic_loudness_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_hslprimaries_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_hslrange_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_invert_init(mlt_profile profile,
mlt_service_type type,
const char *id,
Expand Down Expand Up @@ -157,6 +165,8 @@ MLT_REPOSITORY
MLT_REGISTER(mlt_service_filter_type, "chroma_hold", filter_chroma_hold_init);
MLT_REGISTER(mlt_service_filter_type, "dynamictext", filter_dynamictext_init);
MLT_REGISTER(mlt_service_filter_type, "dynamic_loudness", filter_dynamic_loudness_init);
MLT_REGISTER(mlt_service_filter_type, "hslprimaries", filter_hslprimaries_init);
MLT_REGISTER(mlt_service_filter_type, "hslrange", filter_hslrange_init);
MLT_REGISTER(mlt_service_filter_type, "invert", filter_invert_init);
MLT_REGISTER(mlt_service_filter_type, "lift_gamma_gain", filter_lift_gamma_gain_init);
MLT_REGISTER(mlt_service_filter_type, "loudness", filter_loudness_init);
Expand Down Expand Up @@ -201,6 +211,14 @@ MLT_REPOSITORY
"dynamic_loudness",
metadata,
"filter_dynamic_loudness.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type,
"hslprimaries",
metadata,
"filter_hslprimaries.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type,
"hslrange",
metadata,
"filter_hslrange.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "invert", metadata, "filter_invert.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type,
"lift_gamma_gain",
Expand Down
304 changes: 304 additions & 0 deletions src/modules/plus/filter_hslprimaries.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
/*
* filter_hslprimaries.cpp
* Copyright (C) 2024 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "hsl.h"

#include <framework/mlt.h>

#include <math.h>
#include <stdlib.h>
#include <string.h>

static const float MAX_OVERLAP_RANGE = 29.9;

enum {
HSL_REGION_RED,
HSL_REGION_YELLOW,
HSL_REGION_GREEN,
HSL_REGION_CYAN,
HSL_REGION_BLUE,
HSL_REGION_MAGENTA,
HSL_REGION_COUNT,
};

struct
{
float beg;
float mid;
float end;
} hueRegions[HSL_REGION_COUNT] = {
[HSL_REGION_RED] = {330.0 / 360.0, 0.0 / 360.0, 30.0 / 360.0},
[HSL_REGION_YELLOW] = {30.0 / 360.0, 60.0 / 360.0, 90.0 / 360.0},
[HSL_REGION_GREEN] = {90.0 / 360.0, 120.0 / 360.0, 150.0 / 360.0},
[HSL_REGION_CYAN] = {150.0 / 360.0, 180.0 / 360.0, 210.0 / 360.0},
[HSL_REGION_BLUE] = {210.0 / 360.0, 240.0 / 360.0, 270.0 / 360.0},
[HSL_REGION_MAGENTA] = {270.0 / 360.0, 300.0 / 360.0, 330.0 / 360.0},
};

typedef struct
{
mlt_filter filter;
uint8_t *image;
mlt_image_format format;
int width;
int height;
float h_shift[HSL_REGION_COUNT];
float s_scale[HSL_REGION_COUNT];
float l_scale[HSL_REGION_COUNT];
float overlap;
float overlap_range;
} sliced_desc;

static int hueToRegion(float h)
{
for (int i = 0; i < HSL_REGION_COUNT; i++) {
if (h < hueRegions[i].end) {
return i;
}
}
return HSL_REGION_RED;
}

static void adjust_pixel(uint8_t *sample, sliced_desc *desc)
{
float h, s, l;
rgbToHsl(sample[0] / 255.0, sample[1] / 255.0, sample[2] / 255.0, &h, &s, &l);
if (s == 0) {
// No color. Do not adjust.
return;
}

int region = hueToRegion(h);
float h_shift = desc->h_shift[region];
float s_scale = desc->s_scale[region];
float l_scale = desc->l_scale[region];

if (desc->overlap != 0.0) {
int o_region = region;
float o_strength = 0.0;
if (region == HSL_REGION_RED) {
// Special case to handle red wrap around
if (h > hueRegions[HSL_REGION_RED].beg
&& h < (hueRegions[HSL_REGION_RED].beg + desc->overlap_range)) {
o_region = HSL_REGION_MAGENTA;
o_strength = (h - hueRegions[region].beg) / desc->overlap_range;
} else if (h < hueRegions[HSL_REGION_RED].end
&& h > (hueRegions[HSL_REGION_RED].end - desc->overlap_range)) {
o_region = HSL_REGION_YELLOW;
o_strength = (hueRegions[region].end - h) / desc->overlap_range;
}
} else if (h < (hueRegions[region].beg + desc->overlap_range)) {
o_region = (region - 1 + HSL_REGION_COUNT) % HSL_REGION_COUNT;
o_strength = (h - hueRegions[region].beg) / desc->overlap_range;
} else if (h > (hueRegions[region].end - desc->overlap_range)) {
o_region = (region + 1) % HSL_REGION_COUNT;
o_strength = (hueRegions[region].end - h) / desc->overlap_range;
}
if (o_region != region) {
float edge_hshift = (desc->h_shift[region] + desc->h_shift[o_region]) / 2.0;
h_shift = (desc->h_shift[region] * o_strength) + (edge_hshift * (1.0 - o_strength));
float edge_sscale = (desc->s_scale[region] + desc->s_scale[o_region]) / 2.0;
s_scale = (desc->s_scale[region] * o_strength) + (edge_sscale * (1.0 - o_strength));
float edge_lscale = (desc->l_scale[region] + desc->l_scale[o_region]) / 2.0;
l_scale = (desc->l_scale[region] * o_strength) + (edge_lscale * (1.0 - o_strength));
}
}

if (h_shift == 0.0 && s_scale == 1.0 && l_scale == 1.0) {
// No adjustment for this pixel
return;
}

// Apply the adjustment
h = h + h_shift;
h = fmod(h, 1.0);
s = s * s_scale;
s = s < 0.0 ? 0.0 : s > 1.0 ? 1.0 : s;
l = l * l_scale;
l = l < 0.0 ? 0.0 : l > 1.0 ? 1.0 : l;
float r, g, b;
hslToRgb(h, s, l, &r, &g, &b);
sample[0] = lrint(r * 255.0);
sample[1] = lrint(g * 255.0);
sample[2] = lrint(b * 255.0);
}

static int sliced_proc(int id, int index, int jobs, void *data)
{
(void) id; // unused
sliced_desc *desc = ((sliced_desc *) data);
int slice_line_start,
slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start);
int total = desc->width * slice_height + 1;
uint8_t *sample = desc->image
+ slice_line_start
* mlt_image_format_size(desc->format, desc->width, 1, NULL);

switch (desc->format) {
case mlt_image_rgb:
while (--total) {
adjust_pixel(sample, desc);
sample += 3;
}
break;
case mlt_image_rgba:
while (--total) {
adjust_pixel(sample, desc);
sample += 4;
}
break;
default:
mlt_log_error(MLT_FILTER_SERVICE(desc->filter),
"Invalid image format: %s\n",
mlt_image_format_name(desc->format));
break;
}

return 0;
}

static int filter_get_image(mlt_frame frame,
uint8_t **image,
mlt_image_format *format,
int *width,
int *height,
int writable)
{
mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame);
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
mlt_position position = mlt_filter_get_position(filter, frame);
mlt_position length = mlt_filter_get_length2(filter, frame);
sliced_desc desc;
desc.h_shift[HSL_REGION_RED]
= mlt_properties_anim_get_double(properties, "h_shift_red", position, length);
desc.s_scale[HSL_REGION_RED]
= mlt_properties_anim_get_double(properties, "s_scale_red", position, length);
desc.l_scale[HSL_REGION_RED]
= mlt_properties_anim_get_double(properties, "l_scale_red", position, length);
desc.h_shift[HSL_REGION_YELLOW]
= mlt_properties_anim_get_double(properties, "h_shift_yellow", position, length);
desc.s_scale[HSL_REGION_YELLOW]
= mlt_properties_anim_get_double(properties, "s_scale_yellow", position, length);
desc.l_scale[HSL_REGION_YELLOW]
= mlt_properties_anim_get_double(properties, "l_scale_yellow", position, length);
desc.h_shift[HSL_REGION_GREEN]
= mlt_properties_anim_get_double(properties, "h_shift_green", position, length);
desc.s_scale[HSL_REGION_GREEN]
= mlt_properties_anim_get_double(properties, "s_scale_green", position, length);
desc.l_scale[HSL_REGION_GREEN]
= mlt_properties_anim_get_double(properties, "l_scale_green", position, length);
desc.h_shift[HSL_REGION_CYAN]
= mlt_properties_anim_get_double(properties, "h_shift_cyan", position, length);
desc.s_scale[HSL_REGION_CYAN]
= mlt_properties_anim_get_double(properties, "s_scale_cyan", position, length);
desc.l_scale[HSL_REGION_CYAN]
= mlt_properties_anim_get_double(properties, "l_scale_cyan", position, length);
desc.h_shift[HSL_REGION_BLUE]
= mlt_properties_anim_get_double(properties, "h_shift_blue", position, length);
desc.s_scale[HSL_REGION_BLUE]
= mlt_properties_anim_get_double(properties, "s_scale_blue", position, length);
desc.l_scale[HSL_REGION_BLUE]
= mlt_properties_anim_get_double(properties, "l_scale_blue", position, length);
desc.h_shift[HSL_REGION_MAGENTA]
= mlt_properties_anim_get_double(properties, "h_shift_magenta", position, length);
desc.s_scale[HSL_REGION_MAGENTA]
= mlt_properties_anim_get_double(properties, "s_scale_magenta", position, length);
desc.l_scale[HSL_REGION_MAGENTA]
= mlt_properties_anim_get_double(properties, "l_scale_magenta", position, length);
desc.overlap = mlt_properties_anim_get_double(properties, "overlap", position, length);

// Check if there is any processing to do
int no_change = 1;
for (int i = 0; i < HSL_REGION_COUNT; i++) {
if (desc.h_shift[i] != 0.0 || desc.s_scale[i] != 100.0 || desc.l_scale[i] != 100.0) {
no_change = 0;
break;
}
}
if (no_change) {
return mlt_frame_get_image(frame, image, format, width, height, writable);
}
int error = 0;

// Make sure the format is acceptable
if (*format != mlt_image_rgb && *format != mlt_image_rgba) {
*format = mlt_image_rgb;
}

// Get the image
writable = 1;
error = mlt_frame_get_image(frame, image, format, width, height, writable);
if (!error) {
// Scale the parameters down to [0-1]
for (int i = 0; i < HSL_REGION_COUNT; i++) {
desc.h_shift[i] /= 360.0;
desc.s_scale[i] /= 100.0;
desc.l_scale[i] /= 100.0;
}
desc.overlap /= 100.0;
desc.overlap_range = desc.overlap * MAX_OVERLAP_RANGE / 360.0;
// Perform the processing
desc.format = *format;
desc.height = *height;
desc.width = *width;
desc.image = *image;
mlt_slices_run_normal(0, sliced_proc, &desc);
}

return error;
}

static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
{
mlt_frame_push_service(frame, filter);
mlt_frame_push_get_image(frame, filter_get_image);
return frame;
}

mlt_filter filter_hslprimaries_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg)
{
mlt_filter filter = mlt_filter_new();
if (filter != NULL) {
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
mlt_properties_set_double(properties, "h_shift_red", 0);
mlt_properties_set_double(properties, "s_scale_red", 100);
mlt_properties_set_double(properties, "l_scale_red", 100);
mlt_properties_set_double(properties, "h_shift_yellow", 0);
mlt_properties_set_double(properties, "s_scale_yellow", 100);
mlt_properties_set_double(properties, "l_scale_yellow", 100);
mlt_properties_set_double(properties, "h_shift_green", 0);
mlt_properties_set_double(properties, "s_scale_green", 100);
mlt_properties_set_double(properties, "l_scale_green", 100);
mlt_properties_set_double(properties, "h_shift_cyan", 0);
mlt_properties_set_double(properties, "s_scale_cyan", 100);
mlt_properties_set_double(properties, "l_scale_cyan", 100);
mlt_properties_set_double(properties, "h_shift_blue", 0);
mlt_properties_set_double(properties, "s_scale_blue", 100);
mlt_properties_set_double(properties, "l_scale_blue", 100);
mlt_properties_set_double(properties, "h_shift_magenta", 0);
mlt_properties_set_double(properties, "s_scale_magenta", 100);
mlt_properties_set_double(properties, "l_scale_magenta", 100);
mlt_properties_set_double(properties, "overlap", 0);
filter->process = filter_process;
}
return filter;
}
Loading

0 comments on commit 8d48e87

Please sign in to comment.