diff mbox series

[FFmpeg-devel,add,video,filter,hsl(Hue,Saturation,Lightness)]

Message ID 20230111140532.26467-1-yizhuo.liu753@gmail.com
State New
Headers show
Series [FFmpeg-devel,add,video,filter,hsl(Hue,Saturation,Lightness)] | expand

Checks

Context Check Description
yinshiyou/commit_msg_loongarch64 warning The first line of the commit message must start with a context terminated by a colon and a space, for example "lavu/opt: " or "doc: ".
andriy/commit_msg_x86 warning The first line of the commit message must start with a context terminated by a colon and a space, for example "lavu/opt: " or "doc: ".
yinshiyou/make_fate_loongarch64 success Make fate finished
yinshiyou/make_loongarch64 warning New warnings during build
andriy/make_fate_x86 success Make fate finished
andriy/make_x86 warning New warnings during build

Commit Message

yizhuo liu Jan. 11, 2023, 2:05 p.m. UTC
From: "2498228118@qq.com" <2498228118@qq.com>

---
 libavfilter/Makefile        |   1 +
 libavfilter/allfilters.c    |   1 +
 libavfilter/vf_hsl.c        | 310 ++++++++++++++++++++++++++++++++++++
 tests/fate-run.sh           |   2 +
 tests/fate/filter-video.mak |   3 +
 tests/ref/fate/filter-hsl   |   1 +
 6 files changed, 318 insertions(+)
 create mode 100644 libavfilter/vf_hsl.c
 create mode 100644 tests/ref/fate/filter-hsl

Comments

Paul B Mahol Jan. 11, 2023, 9:10 p.m. UTC | #1
On 1/11/23, liuyizhuo <yizhuo.liu753@gmail.com> wrote:
> From: "2498228118@qq.com" <2498228118@qq.com>
>
> ---
>  libavfilter/Makefile        |   1 +
>  libavfilter/allfilters.c    |   1 +
>  libavfilter/vf_hsl.c        | 310 ++++++++++++++++++++++++++++++++++++
>  tests/fate-run.sh           |   2 +
>  tests/fate/filter-video.mak |   3 +
>  tests/ref/fate/filter-hsl   |   1 +
>  6 files changed, 318 insertions(+)
>  create mode 100644 libavfilter/vf_hsl.c
>  create mode 100644 tests/ref/fate/filter-hsl
>
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 5783be281d..b9ed9e065a 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER)                   +=
> asrc_sine.o
>  OBJS-$(CONFIG_ANULLSINK_FILTER)              += asink_anullsink.o
>
>  # video filters
> +OBJS-$(CONFIG_HSL_FILTER)                    += vf_hsl.o
>  OBJS-$(CONFIG_ADDROI_FILTER)                 += vf_addroi.o
>  OBJS-$(CONFIG_ALPHAEXTRACT_FILTER)           += vf_extractplanes.o
>  OBJS-$(CONFIG_ALPHAMERGE_FILTER)             += vf_alphamerge.o
> framesync.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 52741b60e4..6910189ce4 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine;
>
>  extern const AVFilter ff_asink_anullsink;
>
> +extern const AVFilter ff_vf_hsl;
>  extern const AVFilter ff_vf_addroi;
>  extern const AVFilter ff_vf_alphaextract;
>  extern const AVFilter ff_vf_alphamerge;
> diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c
> new file mode 100644
> index 0000000000..47c9af9120
> --- /dev/null
> +++ b/libavfilter/vf_hsl.c
> @@ -0,0 +1,310 @@
> +/*
> + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com>
> + * Copyright (c) 2022 Liu yizhuo  <yizhuo.liu753@gmail.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg 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.
> + *
> + * FFmpeg 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 FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
> USA
> + */
> +
> +/**
> + * @file
> + * Calculate the HSL color space information.
> + * HSL is a color space that is more perceptually uniform than RGB :
> + *     https://en.wikipedia.org/wiki/HSL_and_HSV

better integrate this into signalstats filter, as it does not do
compare of 2 inputs.

> + */
> +
> +#include <math.h>
> +
> +#include "libavutil/imgutils.h"
> +#include "libavutil/internal.h"
> +#include "libavutil/opt.h"
> +#include "libswscale/swscale.h"
> +
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +typedef float num;
> +
> +typedef struct HSL_COLOR {
> +    num H;
> +    num S;
> +    num L;
> +} HSL_COLOR;
> +
> +typedef struct HSLContext {
> +    const AVClass *class;
> +    int width, height;
> +    uint64_t nb_frames;
> +    float max_hue, max_sat, max_light;
> +    float min_hue, min_sat, min_light;
> +    float sum_hue, sum_sat, sum_light;
> +    int print_summary;
> +    AVFrame *rgbFrame;
> +    enum AVPixelFormat rgb_format;
> +} HSLContext;
> +
> +static const enum AVPixelFormat pix_fmts[] = {
> +    AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV422P,
> +    AV_PIX_FMT_YUVJ420P,  AV_PIX_FMT_YUVJ422P,
> +    AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10,
> +    AV_PIX_FMT_NONE
> +};
> +
> +static av_cold int init(AVFilterContext *ctx)
> +{
> +    // User options but no input data
> +    HSLContext *s = ctx->priv;
> +    s->max_hue    = 0;
> +    s->max_sat    = 0;
> +    s->max_light  = 0;
> +
> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    HSLContext *s = ctx->priv;
> +
> +    if (s->print_summary) {
> +        float avg_hue   = s->sum_hue   / s->nb_frames;
> +        float avg_sat   = s->sum_sat   / s->nb_frames;
> +        float avg_light = s->sum_light / s->nb_frames;
> +        av_log(ctx, AV_LOG_INFO,
> +               "HSL Summary:\nTotal frames: %"PRId64"\n\n"
> +               "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n"
> +               "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin:
> %.1f\n\n"
> +               "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin:
> %.1f\n\n",
> +               s->nb_frames,
> +               avg_hue,   s->max_hue,   s->min_hue,
> +               avg_sat   * 100, s->max_sat   * 100, s->min_sat   * 100,
> +               avg_light * 100, s->max_light * 100, s->min_light * 100
> +        );
> +    }
> +
> +    av_frame_free(&s->rgbFrame);
> +}
> +
> +static int config_input(AVFilterLink *inlink)
> +{
> +    // Video input data avilable
> +    AVFilterContext *ctx = inlink->dst;
> +    HSLContext *s = ctx->priv;
> +
> +    // free previous buffers in case they are allocated already
> +    av_frame_free(&s->rgbFrame);
> +    s->width  = inlink->w;
> +    s->height = inlink->h;
> +    s->rgb_format = AV_PIX_FMT_RGB24;
> +    s->rgbFrame = av_frame_alloc();
> +    if (!s->rgbFrame) {
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    s->rgbFrame->format = s->rgb_format;
> +    s->rgbFrame->width  = s->width;
> +    s->rgbFrame->height = s->height;
> +    if (av_frame_get_buffer(s->rgbFrame, 0)) {
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    return 0;
> +}
> +
> +static void set_meta(AVDictionary **metadata, const char *key, float d)
> +{
> +    char value[128];
> +    snprintf(value, sizeof(value), "%0.1f", d);
> +    av_dict_set(metadata, key, value, 0);
> +}
> +
> +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat,
> AVFrame* dst)
> +{
> +    int width  = src->width;
> +    int height = src->height;
> +
> +    struct SwsContext* conversion = NULL;
> +    conversion = sws_getContext(width,
> +                                height,
> +                                (enum AVPixelFormat)src->format,
> +                                width,
> +                                height,
> +                                dstFormat,
> +                                SWS_FAST_BILINEAR,
> +                                NULL,
> +                                NULL,
> +                                NULL);
> +    sws_scale(conversion, src->data, src->linesize, 0, height, dst->data,
> dst->linesize);
> +    sws_freeContext(conversion);
> +}
> +
> +static const float EPSILON = 1e-9;
> +
> +/** @brief Min of A and B */
> +#define MIN(A,B)    (((A) <= (B)) ? (A) : (B))
> +
> +/** @brief Max of A and B */
> +#define MAX(A,B)    (((A) >= (B)) ? (A) : (B))
> +
> +/** @brief Min of A, B, and C */
> +#define MIN3(A,B,C)    (((A) <= (B)) ? MIN(A,C) : MIN(B,C))
> +
> +/** @brief Max of A, B, and C */
> +#define MAX3(A,B,C)    (((A) >= (B)) ? MAX(A,C) : MAX(B,C))
> +
> +/** @brief Equal of A and B */
> +#define EQ(A,B)    ((fabs((A) - (B)) < EPSILON) ? 1 : 0)
> +
> +
> +/**
> + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL)
> + *
> + * @param H, S, L pointers to hold the result
> + * @param R, G, B the input sRGB values scaled in [0,1]
> + *
> + * This routine transforms from sRGB to the double hexcone HSL color space
> + * The sRGB values are assumed to be between 0 and 1.  The outputs are
> + *   H = hexagonal hue angle                (0 <= H < 360),
> + *   S = { C/(2L)     if L <= 1/2           (0 <= S <= 1),
> + *       { C/(2 - 2L) if L >  1/2
> + *   L = (max(R',G',B') + min(R',G',B'))/2  (0 <= L <= 1),
> + * where C = max(R',G',B') - min(R',G',B').
> + *
> + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
> + */
> +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B)
> +{
> +    num Max = MAX3(R, G, B);
> +    num Min = MIN3(R, G, B);
> +    num C = Max - Min;
> +
> +    *L = (Max + Min) / 2;
> +
> +    if (C > 0) {
> +        if (EQ(Max, R)) {
> +            *H = (G - B) / C;
> +
> +            if (G < B) {
> +                *H += 6;
> +            }
> +        } else if (EQ(Max, G)) {
> +            *H = 2 + (B - R) / C;
> +        } else {
> +            *H = 4 + (R - G) / C;
> +        }
> +
> +        *H *= 60;
> +        *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L)));
> +    } else {
> +        *H = *S = 0;
> +    }
> +}
> +
> +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl)
> +{
> +    // Convert to RGB
> +    YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame);
> +
> +    int cnt = frame->width * frame->height;
> +    num R = 0, G = 0, B = 0;
> +    num H = 0, S = 0, L = 0;
> +    num H_sum = 0, S_sum = 0, L_sum = 0;
> +
> +    for (int i = 0; i < cnt; i++) {
> +        R = ctx->rgbFrame->data[0][3 * i];
> +        G = ctx->rgbFrame->data[0][3 * i + 1];
> +        B = ctx->rgbFrame->data[0][3 * i + 2];
> +        RGB2HSL(&H, &S, &L, R/255, G/255, B/255);
> +        H_sum += H;
> +        S_sum += S;
> +        L_sum += L;
> +    }
> +
> +    hsl->H = H_sum / cnt;
> +    hsl->S = S_sum / cnt;
> +    hsl->L = L_sum / cnt;
> +
> +    // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n",
> +    //        hsl->H, hsl->S * 100, hsl->L * 100);
> +}
> +
> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    HSLContext *s = ctx->priv;
> +    s->nb_frames++;
> +
> +    HSL_COLOR hsl;
> +    calcHSL(s, frame, &hsl);
> +
> +    // Calculate statistics
> +    s->max_hue    = fmaxf(hsl.H, s->max_hue);
> +    s->max_sat    = fmaxf(hsl.S, s->max_sat);
> +    s->max_light  = fmaxf(hsl.L, s->max_light);
> +    s->sum_hue   += hsl.H;
> +    s->sum_sat   += hsl.S;
> +    s->sum_light += hsl.L;
> +    s->min_hue    = s->nb_frames == 1 ? hsl.H : fminf(hsl.H, s->min_hue);
> +    s->min_sat    = s->nb_frames == 1 ? hsl.S : fminf(hsl.S, s->min_sat);
> +    s->min_light  = s->nb_frames == 1 ? hsl.L : fminf(hsl.L,
> s->min_light);
> +
> +    // Set HSL information in frame metadata
> +    set_meta(&frame->metadata, "lavfi.hsl.hue",   hsl.H);
> +    set_meta(&frame->metadata, "lavfi.hsl.sat",   hsl.S * 100);
> +    set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100);
> +
> +    return ff_filter_frame(inlink->dst->outputs[0], frame);
> +}
> +
> +#define OFFSET(x) offsetof(HSLContext, x)
> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> +
> +static const AVOption hsl_options[] = {
> +    { "print_summary", "Print summary showing average values",
> OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(hsl);
> +
> +static const AVFilterPad avfilter_vf_hsl_inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .config_props = config_input,
> +        .filter_frame = filter_frame,
> +    },
> +};
> +
> +static const AVFilterPad avfilter_vf_hsl_outputs[] = {
> +    {
> +        .name = "default",
> +        .type = AVMEDIA_TYPE_VIDEO
> +    },
> +};
> +
> +const AVFilter ff_vf_hsl = {
> +    .name          = "hsl",
> +    .description   = NULL_IF_CONFIG_SMALL("Calculate the HSL color space
> information."),
> +    .priv_size     = sizeof(HSLContext),
> +    .priv_class    = &hsl_class,
> +    .init          = init,
> +    .uninit        = uninit,
> +    .flags         = AVFILTER_FLAG_METADATA_ONLY,
> +    FILTER_PIXFMTS_ARRAY(pix_fmts),
> +    FILTER_INPUTS(avfilter_vf_hsl_inputs),
> +    FILTER_OUTPUTS(avfilter_vf_hsl_outputs),
> +};
> diff --git a/tests/fate-run.sh b/tests/fate-run.sh
> index 61cc59acc0..c350570f5b 100755
> --- a/tests/fate-run.sh
> +++ b/tests/fate-run.sh
> @@ -1,5 +1,7 @@
>  #! /bin/sh
>
> +set -x
> +
>  export LC_ALL=C
>
>  base=$(dirname $0)
> diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
> index 63873a7a07..aa9812aa18 100644
> --- a/tests/fate/filter-video.mak
> +++ b/tests/fate/filter-video.mak
> @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter
> "scale=w=200:h=200"
>  FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) +=
> fate-filter-scale500
>  fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"
>
> +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += fate-filter-hsl
> +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1"
> +
>  FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER
> NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) +=
> fate-filter-scale2ref_keep_aspect
>  fate-filter-scale2ref_keep_aspect:
> tests/data/filtergraphs/scale2ref_keep_aspect
>  fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5
> -filter_complex_script
> $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map "[main]"
> diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl
> new file mode 100644
> index 0000000000..ae0a064f46
> --- /dev/null
> +++ b/tests/ref/fate/filter-hsl
> @@ -0,0 +1 @@
> +hsl            fcb007249fba9371fe84a61c974fcb00
> --
> 2.24.3 (Apple Git-128)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Paul B Mahol Jan. 11, 2023, 9:11 p.m. UTC | #2
On 1/11/23, Paul B Mahol <onemda@gmail.com> wrote:
> On 1/11/23, liuyizhuo <yizhuo.liu753@gmail.com> wrote:
>> From: "2498228118@qq.com" <2498228118@qq.com>
>>
>> ---
>>  libavfilter/Makefile        |   1 +
>>  libavfilter/allfilters.c    |   1 +
>>  libavfilter/vf_hsl.c        | 310 ++++++++++++++++++++++++++++++++++++
>>  tests/fate-run.sh           |   2 +
>>  tests/fate/filter-video.mak |   3 +
>>  tests/ref/fate/filter-hsl   |   1 +
>>  6 files changed, 318 insertions(+)
>>  create mode 100644 libavfilter/vf_hsl.c
>>  create mode 100644 tests/ref/fate/filter-hsl
>>
>> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
>> index 5783be281d..b9ed9e065a 100644
>> --- a/libavfilter/Makefile
>> +++ b/libavfilter/Makefile
>> @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER)                   +=
>> asrc_sine.o
>>  OBJS-$(CONFIG_ANULLSINK_FILTER)              += asink_anullsink.o
>>
>>  # video filters
>> +OBJS-$(CONFIG_HSL_FILTER)                    += vf_hsl.o
>>  OBJS-$(CONFIG_ADDROI_FILTER)                 += vf_addroi.o
>>  OBJS-$(CONFIG_ALPHAEXTRACT_FILTER)           += vf_extractplanes.o
>>  OBJS-$(CONFIG_ALPHAMERGE_FILTER)             += vf_alphamerge.o
>> framesync.o
>> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
>> index 52741b60e4..6910189ce4 100644
>> --- a/libavfilter/allfilters.c
>> +++ b/libavfilter/allfilters.c
>> @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine;
>>
>>  extern const AVFilter ff_asink_anullsink;
>>
>> +extern const AVFilter ff_vf_hsl;
>>  extern const AVFilter ff_vf_addroi;
>>  extern const AVFilter ff_vf_alphaextract;
>>  extern const AVFilter ff_vf_alphamerge;
>> diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c
>> new file mode 100644
>> index 0000000000..47c9af9120
>> --- /dev/null
>> +++ b/libavfilter/vf_hsl.c
>> @@ -0,0 +1,310 @@
>> +/*
>> + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com>
>> + * Copyright (c) 2022 Liu yizhuo  <yizhuo.liu753@gmail.com>
>> + *
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg 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.
>> + *
>> + * FFmpeg 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 FFmpeg; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
>> 02110-1301
>> USA
>> + */
>> +
>> +/**
>> + * @file
>> + * Calculate the HSL color space information.
>> + * HSL is a color space that is more perceptually uniform than RGB :
>> + *     https://en.wikipedia.org/wiki/HSL_and_HSV
>
> better integrate this into signalstats filter, as it does not do
> compare of 2 inputs.
>

Also use already available macros, FFMAX(3) FFMIN(3) ...

>> + */
>> +
>> +#include <math.h>
>> +
>> +#include "libavutil/imgutils.h"
>> +#include "libavutil/internal.h"
>> +#include "libavutil/opt.h"
>> +#include "libswscale/swscale.h"
>> +
>> +#include "avfilter.h"
>> +#include "formats.h"
>> +#include "internal.h"
>> +#include "video.h"
>> +
>> +typedef float num;
>> +
>> +typedef struct HSL_COLOR {
>> +    num H;
>> +    num S;
>> +    num L;
>> +} HSL_COLOR;
>> +
>> +typedef struct HSLContext {
>> +    const AVClass *class;
>> +    int width, height;
>> +    uint64_t nb_frames;
>> +    float max_hue, max_sat, max_light;
>> +    float min_hue, min_sat, min_light;
>> +    float sum_hue, sum_sat, sum_light;
>> +    int print_summary;
>> +    AVFrame *rgbFrame;
>> +    enum AVPixelFormat rgb_format;
>> +} HSLContext;
>> +
>> +static const enum AVPixelFormat pix_fmts[] = {
>> +    AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV422P,
>> +    AV_PIX_FMT_YUVJ420P,  AV_PIX_FMT_YUVJ422P,
>> +    AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10,
>> +    AV_PIX_FMT_NONE
>> +};
>> +
>> +static av_cold int init(AVFilterContext *ctx)
>> +{
>> +    // User options but no input data
>> +    HSLContext *s = ctx->priv;
>> +    s->max_hue    = 0;
>> +    s->max_sat    = 0;
>> +    s->max_light  = 0;
>> +
>> +    return 0;
>> +}
>> +
>> +static av_cold void uninit(AVFilterContext *ctx)
>> +{
>> +    HSLContext *s = ctx->priv;
>> +
>> +    if (s->print_summary) {
>> +        float avg_hue   = s->sum_hue   / s->nb_frames;
>> +        float avg_sat   = s->sum_sat   / s->nb_frames;
>> +        float avg_light = s->sum_light / s->nb_frames;
>> +        av_log(ctx, AV_LOG_INFO,
>> +               "HSL Summary:\nTotal frames: %"PRId64"\n\n"
>> +               "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin:
>> %.1f\n\n"
>> +               "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin:
>> %.1f\n\n"
>> +               "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin:
>> %.1f\n\n",
>> +               s->nb_frames,
>> +               avg_hue,   s->max_hue,   s->min_hue,
>> +               avg_sat   * 100, s->max_sat   * 100, s->min_sat   * 100,
>> +               avg_light * 100, s->max_light * 100, s->min_light * 100
>> +        );
>> +    }
>> +
>> +    av_frame_free(&s->rgbFrame);
>> +}
>> +
>> +static int config_input(AVFilterLink *inlink)
>> +{
>> +    // Video input data avilable
>> +    AVFilterContext *ctx = inlink->dst;
>> +    HSLContext *s = ctx->priv;
>> +
>> +    // free previous buffers in case they are allocated already
>> +    av_frame_free(&s->rgbFrame);
>> +    s->width  = inlink->w;
>> +    s->height = inlink->h;
>> +    s->rgb_format = AV_PIX_FMT_RGB24;
>> +    s->rgbFrame = av_frame_alloc();
>> +    if (!s->rgbFrame) {
>> +        return AVERROR(ENOMEM);
>> +    }
>> +
>> +    s->rgbFrame->format = s->rgb_format;
>> +    s->rgbFrame->width  = s->width;
>> +    s->rgbFrame->height = s->height;
>> +    if (av_frame_get_buffer(s->rgbFrame, 0)) {
>> +        return AVERROR(ENOMEM);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static void set_meta(AVDictionary **metadata, const char *key, float d)
>> +{
>> +    char value[128];
>> +    snprintf(value, sizeof(value), "%0.1f", d);
>> +    av_dict_set(metadata, key, value, 0);
>> +}
>> +
>> +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat,
>> AVFrame* dst)
>> +{
>> +    int width  = src->width;
>> +    int height = src->height;
>> +
>> +    struct SwsContext* conversion = NULL;
>> +    conversion = sws_getContext(width,
>> +                                height,
>> +                                (enum AVPixelFormat)src->format,
>> +                                width,
>> +                                height,
>> +                                dstFormat,
>> +                                SWS_FAST_BILINEAR,
>> +                                NULL,
>> +                                NULL,
>> +                                NULL);
>> +    sws_scale(conversion, src->data, src->linesize, 0, height,
>> dst->data,
>> dst->linesize);
>> +    sws_freeContext(conversion);
>> +}
>> +
>> +static const float EPSILON = 1e-9;
>> +
>> +/** @brief Min of A and B */
>> +#define MIN(A,B)    (((A) <= (B)) ? (A) : (B))
>> +
>> +/** @brief Max of A and B */
>> +#define MAX(A,B)    (((A) >= (B)) ? (A) : (B))
>> +
>> +/** @brief Min of A, B, and C */
>> +#define MIN3(A,B,C)    (((A) <= (B)) ? MIN(A,C) : MIN(B,C))
>> +
>> +/** @brief Max of A, B, and C */
>> +#define MAX3(A,B,C)    (((A) >= (B)) ? MAX(A,C) : MAX(B,C))
>> +
>> +/** @brief Equal of A and B */
>> +#define EQ(A,B)    ((fabs((A) - (B)) < EPSILON) ? 1 : 0)
>> +
>> +
>> +/**
>> + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL)
>> + *
>> + * @param H, S, L pointers to hold the result
>> + * @param R, G, B the input sRGB values scaled in [0,1]
>> + *
>> + * This routine transforms from sRGB to the double hexcone HSL color
>> space
>> + * The sRGB values are assumed to be between 0 and 1.  The outputs are
>> + *   H = hexagonal hue angle                (0 <= H < 360),
>> + *   S = { C/(2L)     if L <= 1/2           (0 <= S <= 1),
>> + *       { C/(2 - 2L) if L >  1/2
>> + *   L = (max(R',G',B') + min(R',G',B'))/2  (0 <= L <= 1),
>> + * where C = max(R',G',B') - min(R',G',B').
>> + *
>> + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
>> + */
>> +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B)
>> +{
>> +    num Max = MAX3(R, G, B);
>> +    num Min = MIN3(R, G, B);
>> +    num C = Max - Min;
>> +
>> +    *L = (Max + Min) / 2;
>> +
>> +    if (C > 0) {
>> +        if (EQ(Max, R)) {
>> +            *H = (G - B) / C;
>> +
>> +            if (G < B) {
>> +                *H += 6;
>> +            }
>> +        } else if (EQ(Max, G)) {
>> +            *H = 2 + (B - R) / C;
>> +        } else {
>> +            *H = 4 + (R - G) / C;
>> +        }
>> +
>> +        *H *= 60;
>> +        *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L)));
>> +    } else {
>> +        *H = *S = 0;
>> +    }
>> +}
>> +
>> +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl)
>> +{
>> +    // Convert to RGB
>> +    YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame);
>> +
>> +    int cnt = frame->width * frame->height;
>> +    num R = 0, G = 0, B = 0;
>> +    num H = 0, S = 0, L = 0;
>> +    num H_sum = 0, S_sum = 0, L_sum = 0;
>> +
>> +    for (int i = 0; i < cnt; i++) {
>> +        R = ctx->rgbFrame->data[0][3 * i];
>> +        G = ctx->rgbFrame->data[0][3 * i + 1];
>> +        B = ctx->rgbFrame->data[0][3 * i + 2];
>> +        RGB2HSL(&H, &S, &L, R/255, G/255, B/255);
>> +        H_sum += H;
>> +        S_sum += S;
>> +        L_sum += L;
>> +    }
>> +
>> +    hsl->H = H_sum / cnt;
>> +    hsl->S = S_sum / cnt;
>> +    hsl->L = L_sum / cnt;
>> +
>> +    // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n",
>> +    //        hsl->H, hsl->S * 100, hsl->L * 100);
>> +}
>> +
>> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
>> +{
>> +    AVFilterContext *ctx = inlink->dst;
>> +    HSLContext *s = ctx->priv;
>> +    s->nb_frames++;
>> +
>> +    HSL_COLOR hsl;
>> +    calcHSL(s, frame, &hsl);
>> +
>> +    // Calculate statistics
>> +    s->max_hue    = fmaxf(hsl.H, s->max_hue);
>> +    s->max_sat    = fmaxf(hsl.S, s->max_sat);
>> +    s->max_light  = fmaxf(hsl.L, s->max_light);
>> +    s->sum_hue   += hsl.H;
>> +    s->sum_sat   += hsl.S;
>> +    s->sum_light += hsl.L;
>> +    s->min_hue    = s->nb_frames == 1 ? hsl.H : fminf(hsl.H,
>> s->min_hue);
>> +    s->min_sat    = s->nb_frames == 1 ? hsl.S : fminf(hsl.S,
>> s->min_sat);
>> +    s->min_light  = s->nb_frames == 1 ? hsl.L : fminf(hsl.L,
>> s->min_light);
>> +
>> +    // Set HSL information in frame metadata
>> +    set_meta(&frame->metadata, "lavfi.hsl.hue",   hsl.H);
>> +    set_meta(&frame->metadata, "lavfi.hsl.sat",   hsl.S * 100);
>> +    set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100);
>> +
>> +    return ff_filter_frame(inlink->dst->outputs[0], frame);
>> +}
>> +
>> +#define OFFSET(x) offsetof(HSLContext, x)
>> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
>> +
>> +static const AVOption hsl_options[] = {
>> +    { "print_summary", "Print summary showing average values",
>> OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
>> +    { NULL }
>> +};
>> +
>> +AVFILTER_DEFINE_CLASS(hsl);
>> +
>> +static const AVFilterPad avfilter_vf_hsl_inputs[] = {
>> +    {
>> +        .name         = "default",
>> +        .type         = AVMEDIA_TYPE_VIDEO,
>> +        .config_props = config_input,
>> +        .filter_frame = filter_frame,
>> +    },
>> +};
>> +
>> +static const AVFilterPad avfilter_vf_hsl_outputs[] = {
>> +    {
>> +        .name = "default",
>> +        .type = AVMEDIA_TYPE_VIDEO
>> +    },
>> +};
>> +
>> +const AVFilter ff_vf_hsl = {
>> +    .name          = "hsl",
>> +    .description   = NULL_IF_CONFIG_SMALL("Calculate the HSL color space
>> information."),
>> +    .priv_size     = sizeof(HSLContext),
>> +    .priv_class    = &hsl_class,
>> +    .init          = init,
>> +    .uninit        = uninit,
>> +    .flags         = AVFILTER_FLAG_METADATA_ONLY,
>> +    FILTER_PIXFMTS_ARRAY(pix_fmts),
>> +    FILTER_INPUTS(avfilter_vf_hsl_inputs),
>> +    FILTER_OUTPUTS(avfilter_vf_hsl_outputs),
>> +};
>> diff --git a/tests/fate-run.sh b/tests/fate-run.sh
>> index 61cc59acc0..c350570f5b 100755
>> --- a/tests/fate-run.sh
>> +++ b/tests/fate-run.sh
>> @@ -1,5 +1,7 @@
>>  #! /bin/sh
>>
>> +set -x
>> +
>>  export LC_ALL=C
>>
>>  base=$(dirname $0)
>> diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
>> index 63873a7a07..aa9812aa18 100644
>> --- a/tests/fate/filter-video.mak
>> +++ b/tests/fate/filter-video.mak
>> @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter
>> "scale=w=200:h=200"
>>  FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) +=
>> fate-filter-scale500
>>  fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"
>>
>> +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) +=
>> fate-filter-hsl
>> +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1"
>> +
>>  FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER
>> NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) +=
>> fate-filter-scale2ref_keep_aspect
>>  fate-filter-scale2ref_keep_aspect:
>> tests/data/filtergraphs/scale2ref_keep_aspect
>>  fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5
>> -filter_complex_script
>> $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map
>> "[main]"
>> diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl
>> new file mode 100644
>> index 0000000000..ae0a064f46
>> --- /dev/null
>> +++ b/tests/ref/fate/filter-hsl
>> @@ -0,0 +1 @@
>> +hsl            fcb007249fba9371fe84a61c974fcb00
>> --
>> 2.24.3 (Apple Git-128)
>>
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>> To unsubscribe, visit link above, or email
>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>>
>
yizhuo liu March 10, 2023, 10:49 a.m. UTC | #3
dear Paul, hsl integrate into signalstats has been done, thank u for advice

looking forward to your reply

besh wishes

attachment ( recent commit )
https://patchwork.ffmpeg.org/project/ffmpeg/patch/20230309133517.26683-1-yizhuo.liu753@gmail.com/



Paul B Mahol <onemda@gmail.com> 于2023年1月12日周四 05:11写道:

> On 1/11/23, Paul B Mahol <onemda@gmail.com> wrote:
> > On 1/11/23, liuyizhuo <yizhuo.liu753@gmail.com> wrote:
> >> From: "2498228118@qq.com" <2498228118@qq.com>
> >>
> >> ---
> >>  libavfilter/Makefile        |   1 +
> >>  libavfilter/allfilters.c    |   1 +
> >>  libavfilter/vf_hsl.c        | 310 ++++++++++++++++++++++++++++++++++++
> >>  tests/fate-run.sh           |   2 +
> >>  tests/fate/filter-video.mak |   3 +
> >>  tests/ref/fate/filter-hsl   |   1 +
> >>  6 files changed, 318 insertions(+)
> >>  create mode 100644 libavfilter/vf_hsl.c
> >>  create mode 100644 tests/ref/fate/filter-hsl
> >>
> >> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> >> index 5783be281d..b9ed9e065a 100644
> >> --- a/libavfilter/Makefile
> >> +++ b/libavfilter/Makefile
> >> @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER)                   +=
> >> asrc_sine.o
> >>  OBJS-$(CONFIG_ANULLSINK_FILTER)              += asink_anullsink.o
> >>
> >>  # video filters
> >> +OBJS-$(CONFIG_HSL_FILTER)                    += vf_hsl.o
> >>  OBJS-$(CONFIG_ADDROI_FILTER)                 += vf_addroi.o
> >>  OBJS-$(CONFIG_ALPHAEXTRACT_FILTER)           += vf_extractplanes.o
> >>  OBJS-$(CONFIG_ALPHAMERGE_FILTER)             += vf_alphamerge.o
> >> framesync.o
> >> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> >> index 52741b60e4..6910189ce4 100644
> >> --- a/libavfilter/allfilters.c
> >> +++ b/libavfilter/allfilters.c
> >> @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine;
> >>
> >>  extern const AVFilter ff_asink_anullsink;
> >>
> >> +extern const AVFilter ff_vf_hsl;
> >>  extern const AVFilter ff_vf_addroi;
> >>  extern const AVFilter ff_vf_alphaextract;
> >>  extern const AVFilter ff_vf_alphamerge;
> >> diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c
> >> new file mode 100644
> >> index 0000000000..47c9af9120
> >> --- /dev/null
> >> +++ b/libavfilter/vf_hsl.c
> >> @@ -0,0 +1,310 @@
> >> +/*
> >> + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com>
> >> + * Copyright (c) 2022 Liu yizhuo  <yizhuo.liu753@gmail.com>
> >> + *
> >> + * This file is part of FFmpeg.
> >> + *
> >> + * FFmpeg 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.
> >> + *
> >> + * FFmpeg 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 FFmpeg; if not, write to the Free Software
> >> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> >> 02110-1301
> >> USA
> >> + */
> >> +
> >> +/**
> >> + * @file
> >> + * Calculate the HSL color space information.
> >> + * HSL is a color space that is more perceptually uniform than RGB :
> >> + *     https://en.wikipedia.org/wiki/HSL_and_HSV
> >
> > better integrate this into signalstats filter, as it does not do
> > compare of 2 inputs.
> >
>
> Also use already available macros, FFMAX(3) FFMIN(3) ...
>
> >> + */
> >> +
> >> +#include <math.h>
> >> +
> >> +#include "libavutil/imgutils.h"
> >> +#include "libavutil/internal.h"
> >> +#include "libavutil/opt.h"
> >> +#include "libswscale/swscale.h"
> >> +
> >> +#include "avfilter.h"
> >> +#include "formats.h"
> >> +#include "internal.h"
> >> +#include "video.h"
> >> +
> >> +typedef float num;
> >> +
> >> +typedef struct HSL_COLOR {
> >> +    num H;
> >> +    num S;
> >> +    num L;
> >> +} HSL_COLOR;
> >> +
> >> +typedef struct HSLContext {
> >> +    const AVClass *class;
> >> +    int width, height;
> >> +    uint64_t nb_frames;
> >> +    float max_hue, max_sat, max_light;
> >> +    float min_hue, min_sat, min_light;
> >> +    float sum_hue, sum_sat, sum_light;
> >> +    int print_summary;
> >> +    AVFrame *rgbFrame;
> >> +    enum AVPixelFormat rgb_format;
> >> +} HSLContext;
> >> +
> >> +static const enum AVPixelFormat pix_fmts[] = {
> >> +    AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV422P,
> >> +    AV_PIX_FMT_YUVJ420P,  AV_PIX_FMT_YUVJ422P,
> >> +    AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10,
> >> +    AV_PIX_FMT_NONE
> >> +};
> >> +
> >> +static av_cold int init(AVFilterContext *ctx)
> >> +{
> >> +    // User options but no input data
> >> +    HSLContext *s = ctx->priv;
> >> +    s->max_hue    = 0;
> >> +    s->max_sat    = 0;
> >> +    s->max_light  = 0;
> >> +
> >> +    return 0;
> >> +}
> >> +
> >> +static av_cold void uninit(AVFilterContext *ctx)
> >> +{
> >> +    HSLContext *s = ctx->priv;
> >> +
> >> +    if (s->print_summary) {
> >> +        float avg_hue   = s->sum_hue   / s->nb_frames;
> >> +        float avg_sat   = s->sum_sat   / s->nb_frames;
> >> +        float avg_light = s->sum_light / s->nb_frames;
> >> +        av_log(ctx, AV_LOG_INFO,
> >> +               "HSL Summary:\nTotal frames: %"PRId64"\n\n"
> >> +               "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin:
> >> %.1f\n\n"
> >> +               "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin:
> >> %.1f\n\n"
> >> +               "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin:
> >> %.1f\n\n",
> >> +               s->nb_frames,
> >> +               avg_hue,   s->max_hue,   s->min_hue,
> >> +               avg_sat   * 100, s->max_sat   * 100, s->min_sat   * 100,
> >> +               avg_light * 100, s->max_light * 100, s->min_light * 100
> >> +        );
> >> +    }
> >> +
> >> +    av_frame_free(&s->rgbFrame);
> >> +}
> >> +
> >> +static int config_input(AVFilterLink *inlink)
> >> +{
> >> +    // Video input data avilable
> >> +    AVFilterContext *ctx = inlink->dst;
> >> +    HSLContext *s = ctx->priv;
> >> +
> >> +    // free previous buffers in case they are allocated already
> >> +    av_frame_free(&s->rgbFrame);
> >> +    s->width  = inlink->w;
> >> +    s->height = inlink->h;
> >> +    s->rgb_format = AV_PIX_FMT_RGB24;
> >> +    s->rgbFrame = av_frame_alloc();
> >> +    if (!s->rgbFrame) {
> >> +        return AVERROR(ENOMEM);
> >> +    }
> >> +
> >> +    s->rgbFrame->format = s->rgb_format;
> >> +    s->rgbFrame->width  = s->width;
> >> +    s->rgbFrame->height = s->height;
> >> +    if (av_frame_get_buffer(s->rgbFrame, 0)) {
> >> +        return AVERROR(ENOMEM);
> >> +    }
> >> +
> >> +    return 0;
> >> +}
> >> +
> >> +static void set_meta(AVDictionary **metadata, const char *key, float d)
> >> +{
> >> +    char value[128];
> >> +    snprintf(value, sizeof(value), "%0.1f", d);
> >> +    av_dict_set(metadata, key, value, 0);
> >> +}
> >> +
> >> +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat,
> >> AVFrame* dst)
> >> +{
> >> +    int width  = src->width;
> >> +    int height = src->height;
> >> +
> >> +    struct SwsContext* conversion = NULL;
> >> +    conversion = sws_getContext(width,
> >> +                                height,
> >> +                                (enum AVPixelFormat)src->format,
> >> +                                width,
> >> +                                height,
> >> +                                dstFormat,
> >> +                                SWS_FAST_BILINEAR,
> >> +                                NULL,
> >> +                                NULL,
> >> +                                NULL);
> >> +    sws_scale(conversion, src->data, src->linesize, 0, height,
> >> dst->data,
> >> dst->linesize);
> >> +    sws_freeContext(conversion);
> >> +}
> >> +
> >> +static const float EPSILON = 1e-9;
> >> +
> >> +/** @brief Min of A and B */
> >> +#define MIN(A,B)    (((A) <= (B)) ? (A) : (B))
> >> +
> >> +/** @brief Max of A and B */
> >> +#define MAX(A,B)    (((A) >= (B)) ? (A) : (B))
> >> +
> >> +/** @brief Min of A, B, and C */
> >> +#define MIN3(A,B,C)    (((A) <= (B)) ? MIN(A,C) : MIN(B,C))
> >> +
> >> +/** @brief Max of A, B, and C */
> >> +#define MAX3(A,B,C)    (((A) >= (B)) ? MAX(A,C) : MAX(B,C))
> >> +
> >> +/** @brief Equal of A and B */
> >> +#define EQ(A,B)    ((fabs((A) - (B)) < EPSILON) ? 1 : 0)
> >> +
> >> +
> >> +/**
> >> + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL)
> >> + *
> >> + * @param H, S, L pointers to hold the result
> >> + * @param R, G, B the input sRGB values scaled in [0,1]
> >> + *
> >> + * This routine transforms from sRGB to the double hexcone HSL color
> >> space
> >> + * The sRGB values are assumed to be between 0 and 1.  The outputs are
> >> + *   H = hexagonal hue angle                (0 <= H < 360),
> >> + *   S = { C/(2L)     if L <= 1/2           (0 <= S <= 1),
> >> + *       { C/(2 - 2L) if L >  1/2
> >> + *   L = (max(R',G',B') + min(R',G',B'))/2  (0 <= L <= 1),
> >> + * where C = max(R',G',B') - min(R',G',B').
> >> + *
> >> + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
> >> + */
> >> +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B)
> >> +{
> >> +    num Max = MAX3(R, G, B);
> >> +    num Min = MIN3(R, G, B);
> >> +    num C = Max - Min;
> >> +
> >> +    *L = (Max + Min) / 2;
> >> +
> >> +    if (C > 0) {
> >> +        if (EQ(Max, R)) {
> >> +            *H = (G - B) / C;
> >> +
> >> +            if (G < B) {
> >> +                *H += 6;
> >> +            }
> >> +        } else if (EQ(Max, G)) {
> >> +            *H = 2 + (B - R) / C;
> >> +        } else {
> >> +            *H = 4 + (R - G) / C;
> >> +        }
> >> +
> >> +        *H *= 60;
> >> +        *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L)));
> >> +    } else {
> >> +        *H = *S = 0;
> >> +    }
> >> +}
> >> +
> >> +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl)
> >> +{
> >> +    // Convert to RGB
> >> +    YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame);
> >> +
> >> +    int cnt = frame->width * frame->height;
> >> +    num R = 0, G = 0, B = 0;
> >> +    num H = 0, S = 0, L = 0;
> >> +    num H_sum = 0, S_sum = 0, L_sum = 0;
> >> +
> >> +    for (int i = 0; i < cnt; i++) {
> >> +        R = ctx->rgbFrame->data[0][3 * i];
> >> +        G = ctx->rgbFrame->data[0][3 * i + 1];
> >> +        B = ctx->rgbFrame->data[0][3 * i + 2];
> >> +        RGB2HSL(&H, &S, &L, R/255, G/255, B/255);
> >> +        H_sum += H;
> >> +        S_sum += S;
> >> +        L_sum += L;
> >> +    }
> >> +
> >> +    hsl->H = H_sum / cnt;
> >> +    hsl->S = S_sum / cnt;
> >> +    hsl->L = L_sum / cnt;
> >> +
> >> +    // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n",
> >> +    //        hsl->H, hsl->S * 100, hsl->L * 100);
> >> +}
> >> +
> >> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
> >> +{
> >> +    AVFilterContext *ctx = inlink->dst;
> >> +    HSLContext *s = ctx->priv;
> >> +    s->nb_frames++;
> >> +
> >> +    HSL_COLOR hsl;
> >> +    calcHSL(s, frame, &hsl);
> >> +
> >> +    // Calculate statistics
> >> +    s->max_hue    = fmaxf(hsl.H, s->max_hue);
> >> +    s->max_sat    = fmaxf(hsl.S, s->max_sat);
> >> +    s->max_light  = fmaxf(hsl.L, s->max_light);
> >> +    s->sum_hue   += hsl.H;
> >> +    s->sum_sat   += hsl.S;
> >> +    s->sum_light += hsl.L;
> >> +    s->min_hue    = s->nb_frames == 1 ? hsl.H : fminf(hsl.H,
> >> s->min_hue);
> >> +    s->min_sat    = s->nb_frames == 1 ? hsl.S : fminf(hsl.S,
> >> s->min_sat);
> >> +    s->min_light  = s->nb_frames == 1 ? hsl.L : fminf(hsl.L,
> >> s->min_light);
> >> +
> >> +    // Set HSL information in frame metadata
> >> +    set_meta(&frame->metadata, "lavfi.hsl.hue",   hsl.H);
> >> +    set_meta(&frame->metadata, "lavfi.hsl.sat",   hsl.S * 100);
> >> +    set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100);
> >> +
> >> +    return ff_filter_frame(inlink->dst->outputs[0], frame);
> >> +}
> >> +
> >> +#define OFFSET(x) offsetof(HSLContext, x)
> >> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> >> +
> >> +static const AVOption hsl_options[] = {
> >> +    { "print_summary", "Print summary showing average values",
> >> OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
> >> +    { NULL }
> >> +};
> >> +
> >> +AVFILTER_DEFINE_CLASS(hsl);
> >> +
> >> +static const AVFilterPad avfilter_vf_hsl_inputs[] = {
> >> +    {
> >> +        .name         = "default",
> >> +        .type         = AVMEDIA_TYPE_VIDEO,
> >> +        .config_props = config_input,
> >> +        .filter_frame = filter_frame,
> >> +    },
> >> +};
> >> +
> >> +static const AVFilterPad avfilter_vf_hsl_outputs[] = {
> >> +    {
> >> +        .name = "default",
> >> +        .type = AVMEDIA_TYPE_VIDEO
> >> +    },
> >> +};
> >> +
> >> +const AVFilter ff_vf_hsl = {
> >> +    .name          = "hsl",
> >> +    .description   = NULL_IF_CONFIG_SMALL("Calculate the HSL color
> space
> >> information."),
> >> +    .priv_size     = sizeof(HSLContext),
> >> +    .priv_class    = &hsl_class,
> >> +    .init          = init,
> >> +    .uninit        = uninit,
> >> +    .flags         = AVFILTER_FLAG_METADATA_ONLY,
> >> +    FILTER_PIXFMTS_ARRAY(pix_fmts),
> >> +    FILTER_INPUTS(avfilter_vf_hsl_inputs),
> >> +    FILTER_OUTPUTS(avfilter_vf_hsl_outputs),
> >> +};
> >> diff --git a/tests/fate-run.sh b/tests/fate-run.sh
> >> index 61cc59acc0..c350570f5b 100755
> >> --- a/tests/fate-run.sh
> >> +++ b/tests/fate-run.sh
> >> @@ -1,5 +1,7 @@
> >>  #! /bin/sh
> >>
> >> +set -x
> >> +
> >>  export LC_ALL=C
> >>
> >>  base=$(dirname $0)
> >> diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
> >> index 63873a7a07..aa9812aa18 100644
> >> --- a/tests/fate/filter-video.mak
> >> +++ b/tests/fate/filter-video.mak
> >> @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter
> >> "scale=w=200:h=200"
> >>  FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) +=
> >> fate-filter-scale500
> >>  fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"
> >>
> >> +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) +=
> >> fate-filter-hsl
> >> +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1"
> >> +
> >>  FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER
> >> NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) +=
> >> fate-filter-scale2ref_keep_aspect
> >>  fate-filter-scale2ref_keep_aspect:
> >> tests/data/filtergraphs/scale2ref_keep_aspect
> >>  fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5
> >> -filter_complex_script
> >> $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map
> >> "[main]"
> >> diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl
> >> new file mode 100644
> >> index 0000000000..ae0a064f46
> >> --- /dev/null
> >> +++ b/tests/ref/fate/filter-hsl
> >> @@ -0,0 +1 @@
> >> +hsl            fcb007249fba9371fe84a61c974fcb00
> >> --
> >> 2.24.3 (Apple Git-128)
> >>
> >> _______________________________________________
> >> ffmpeg-devel mailing list
> >> ffmpeg-devel@ffmpeg.org
> >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >>
> >> To unsubscribe, visit link above, or email
> >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> >>
> >
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
diff mbox series

Patch

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 5783be281d..b9ed9e065a 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -182,6 +182,7 @@  OBJS-$(CONFIG_SINE_FILTER)                   += asrc_sine.o
 OBJS-$(CONFIG_ANULLSINK_FILTER)              += asink_anullsink.o
 
 # video filters
+OBJS-$(CONFIG_HSL_FILTER)                    += vf_hsl.o
 OBJS-$(CONFIG_ADDROI_FILTER)                 += vf_addroi.o
 OBJS-$(CONFIG_ALPHAEXTRACT_FILTER)           += vf_extractplanes.o
 OBJS-$(CONFIG_ALPHAMERGE_FILTER)             += vf_alphamerge.o framesync.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 52741b60e4..6910189ce4 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -169,6 +169,7 @@  extern const AVFilter ff_asrc_sine;
 
 extern const AVFilter ff_asink_anullsink;
 
+extern const AVFilter ff_vf_hsl;
 extern const AVFilter ff_vf_addroi;
 extern const AVFilter ff_vf_alphaextract;
 extern const AVFilter ff_vf_alphamerge;
diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c
new file mode 100644
index 0000000000..47c9af9120
--- /dev/null
+++ b/libavfilter/vf_hsl.c
@@ -0,0 +1,310 @@ 
+/*
+ * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com>
+ * Copyright (c) 2022 Liu yizhuo  <yizhuo.liu753@gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Calculate the HSL color space information.
+ * HSL is a color space that is more perceptually uniform than RGB :
+ *     https://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+
+#include <math.h>
+
+#include "libavutil/imgutils.h"
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "libswscale/swscale.h"
+
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "video.h"
+
+typedef float num;
+
+typedef struct HSL_COLOR {
+    num H;
+    num S;
+    num L;
+} HSL_COLOR;
+
+typedef struct HSLContext {
+    const AVClass *class;
+    int width, height;
+    uint64_t nb_frames;
+    float max_hue, max_sat, max_light;
+    float min_hue, min_sat, min_light;
+    float sum_hue, sum_sat, sum_light;
+    int print_summary;
+    AVFrame *rgbFrame;
+    enum AVPixelFormat rgb_format;
+} HSLContext;
+
+static const enum AVPixelFormat pix_fmts[] = {
+    AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV422P,
+    AV_PIX_FMT_YUVJ420P,  AV_PIX_FMT_YUVJ422P,
+    AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10,
+    AV_PIX_FMT_NONE
+};
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    // User options but no input data
+    HSLContext *s = ctx->priv;
+    s->max_hue    = 0;
+    s->max_sat    = 0;
+    s->max_light  = 0;
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    HSLContext *s = ctx->priv;
+
+    if (s->print_summary) {
+        float avg_hue   = s->sum_hue   / s->nb_frames;
+        float avg_sat   = s->sum_sat   / s->nb_frames;
+        float avg_light = s->sum_light / s->nb_frames;
+        av_log(ctx, AV_LOG_INFO,
+               "HSL Summary:\nTotal frames: %"PRId64"\n\n"
+               "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n"
+               "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n"
+               "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n",
+               s->nb_frames, 
+               avg_hue,   s->max_hue,   s->min_hue,
+               avg_sat   * 100, s->max_sat   * 100, s->min_sat   * 100,
+               avg_light * 100, s->max_light * 100, s->min_light * 100
+        );
+    }
+
+    av_frame_free(&s->rgbFrame);
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    // Video input data avilable
+    AVFilterContext *ctx = inlink->dst;
+    HSLContext *s = ctx->priv;
+    
+    // free previous buffers in case they are allocated already
+    av_frame_free(&s->rgbFrame);
+    s->width  = inlink->w;
+    s->height = inlink->h;
+    s->rgb_format = AV_PIX_FMT_RGB24;
+    s->rgbFrame = av_frame_alloc();
+    if (!s->rgbFrame) {
+        return AVERROR(ENOMEM);
+    }
+
+    s->rgbFrame->format = s->rgb_format;
+    s->rgbFrame->width  = s->width;
+    s->rgbFrame->height = s->height;
+    if (av_frame_get_buffer(s->rgbFrame, 0)) {
+        return AVERROR(ENOMEM);
+    }
+
+    return 0;
+}
+
+static void set_meta(AVDictionary **metadata, const char *key, float d)
+{
+    char value[128];
+    snprintf(value, sizeof(value), "%0.1f", d);
+    av_dict_set(metadata, key, value, 0);
+}
+
+static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat, AVFrame* dst) 
+{
+    int width  = src->width;
+    int height = src->height;
+    
+    struct SwsContext* conversion = NULL;
+    conversion = sws_getContext(width,
+                                height,
+                                (enum AVPixelFormat)src->format,
+                                width,
+                                height,
+                                dstFormat,
+                                SWS_FAST_BILINEAR,
+                                NULL,
+                                NULL,
+                                NULL);
+    sws_scale(conversion, src->data, src->linesize, 0, height, dst->data, dst->linesize);
+    sws_freeContext(conversion);
+}
+
+static const float EPSILON = 1e-9;
+
+/** @brief Min of A and B */
+#define MIN(A,B)    (((A) <= (B)) ? (A) : (B))
+
+/** @brief Max of A and B */
+#define MAX(A,B)    (((A) >= (B)) ? (A) : (B))
+
+/** @brief Min of A, B, and C */
+#define MIN3(A,B,C)    (((A) <= (B)) ? MIN(A,C) : MIN(B,C))
+
+/** @brief Max of A, B, and C */
+#define MAX3(A,B,C)    (((A) >= (B)) ? MAX(A,C) : MAX(B,C))
+
+/** @brief Equal of A and B */
+#define EQ(A,B)    ((fabs((A) - (B)) < EPSILON) ? 1 : 0)
+
+
+/** 
+ * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL)
+ * 
+ * @param H, S, L pointers to hold the result
+ * @param R, G, B the input sRGB values scaled in [0,1]
+ *
+ * This routine transforms from sRGB to the double hexcone HSL color space
+ * The sRGB values are assumed to be between 0 and 1.  The outputs are
+ *   H = hexagonal hue angle                (0 <= H < 360),
+ *   S = { C/(2L)     if L <= 1/2           (0 <= S <= 1),
+ *       { C/(2 - 2L) if L >  1/2
+ *   L = (max(R',G',B') + min(R',G',B'))/2  (0 <= L <= 1),
+ * where C = max(R',G',B') - min(R',G',B').
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B)
+{
+    num Max = MAX3(R, G, B);
+    num Min = MIN3(R, G, B);
+    num C = Max - Min;
+
+    *L = (Max + Min) / 2;
+    
+    if (C > 0) {
+        if (EQ(Max, R)) {
+            *H = (G - B) / C;
+            
+            if (G < B) {
+                *H += 6;
+            }
+        } else if (EQ(Max, G)) {
+            *H = 2 + (B - R) / C;
+        } else {
+            *H = 4 + (R - G) / C;
+        }
+
+        *H *= 60;
+        *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L)));
+    } else {
+        *H = *S = 0;
+    }
+}
+
+static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl)
+{
+    // Convert to RGB
+    YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame);
+    
+    int cnt = frame->width * frame->height;
+    num R = 0, G = 0, B = 0;
+    num H = 0, S = 0, L = 0;
+    num H_sum = 0, S_sum = 0, L_sum = 0;
+    
+    for (int i = 0; i < cnt; i++) {
+        R = ctx->rgbFrame->data[0][3 * i];
+        G = ctx->rgbFrame->data[0][3 * i + 1];
+        B = ctx->rgbFrame->data[0][3 * i + 2];
+        RGB2HSL(&H, &S, &L, R/255, G/255, B/255);
+        H_sum += H;
+        S_sum += S;
+        L_sum += L;
+    }
+
+    hsl->H = H_sum / cnt;
+    hsl->S = S_sum / cnt;
+    hsl->L = L_sum / cnt;
+    
+    // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n",
+    //        hsl->H, hsl->S * 100, hsl->L * 100);
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+    AVFilterContext *ctx = inlink->dst;
+    HSLContext *s = ctx->priv;
+    s->nb_frames++;
+
+    HSL_COLOR hsl;
+    calcHSL(s, frame, &hsl);
+
+    // Calculate statistics
+    s->max_hue    = fmaxf(hsl.H, s->max_hue);
+    s->max_sat    = fmaxf(hsl.S, s->max_sat);
+    s->max_light  = fmaxf(hsl.L, s->max_light);
+    s->sum_hue   += hsl.H;
+    s->sum_sat   += hsl.S;
+    s->sum_light += hsl.L;
+    s->min_hue    = s->nb_frames == 1 ? hsl.H : fminf(hsl.H, s->min_hue);
+    s->min_sat    = s->nb_frames == 1 ? hsl.S : fminf(hsl.S, s->min_sat);
+    s->min_light  = s->nb_frames == 1 ? hsl.L : fminf(hsl.L, s->min_light);
+
+    // Set HSL information in frame metadata
+    set_meta(&frame->metadata, "lavfi.hsl.hue",   hsl.H);
+    set_meta(&frame->metadata, "lavfi.hsl.sat",   hsl.S * 100);
+    set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100);
+
+    return ff_filter_frame(inlink->dst->outputs[0], frame);
+}
+
+#define OFFSET(x) offsetof(HSLContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption hsl_options[] = {
+    { "print_summary", "Print summary showing average values", OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(hsl);
+
+static const AVFilterPad avfilter_vf_hsl_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .config_props = config_input,
+        .filter_frame = filter_frame,
+    },
+};
+
+static const AVFilterPad avfilter_vf_hsl_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO
+    },
+};
+
+const AVFilter ff_vf_hsl = {
+    .name          = "hsl",
+    .description   = NULL_IF_CONFIG_SMALL("Calculate the HSL color space information."),
+    .priv_size     = sizeof(HSLContext),
+    .priv_class    = &hsl_class,
+    .init          = init,
+    .uninit        = uninit,
+    .flags         = AVFILTER_FLAG_METADATA_ONLY,
+    FILTER_PIXFMTS_ARRAY(pix_fmts),
+    FILTER_INPUTS(avfilter_vf_hsl_inputs),
+    FILTER_OUTPUTS(avfilter_vf_hsl_outputs),
+};
diff --git a/tests/fate-run.sh b/tests/fate-run.sh
index 61cc59acc0..c350570f5b 100755
--- a/tests/fate-run.sh
+++ b/tests/fate-run.sh
@@ -1,5 +1,7 @@ 
 #! /bin/sh
 
+set -x
+
 export LC_ALL=C
 
 base=$(dirname $0)
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 63873a7a07..aa9812aa18 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -408,6 +408,9 @@  fate-filter-scale200: CMD = video_filter "scale=w=200:h=200"
 FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += fate-filter-scale500
 fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"
 
+FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += fate-filter-hsl
+fate-filter-hsl: CMD = video_filter "hsl=print_summary=1"
+
 FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) += fate-filter-scale2ref_keep_aspect
 fate-filter-scale2ref_keep_aspect: tests/data/filtergraphs/scale2ref_keep_aspect
 fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5 -filter_complex_script $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map "[main]"
diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl
new file mode 100644
index 0000000000..ae0a064f46
--- /dev/null
+++ b/tests/ref/fate/filter-hsl
@@ -0,0 +1 @@ 
+hsl            fcb007249fba9371fe84a61c974fcb00