From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 4E40949498 for ; Wed, 16 Jul 2025 16:26:14 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 81DC968E9F7; Wed, 16 Jul 2025 19:25:50 +0300 (EEST) Received: from haasn.dev (haasn.dev [78.46.187.166]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 6736C68E401 for ; Wed, 16 Jul 2025 19:25:40 +0300 (EEST) Received: from haasn.dev (unknown [10.30.1.1]) by haasn.dev (Postfix) with UTF8SMTP id 1B8A24105D; Wed, 16 Jul 2025 18:25:40 +0200 (CEST) From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Wed, 16 Jul 2025 18:24:55 +0200 Message-ID: <20250716162536.394049-2-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250716162536.394049-1-ffmpeg@haasn.xyz> References: <20250716162536.394049-1-ffmpeg@haasn.xyz> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2 1/3] avfilter/vf_colordetect: add new color range detection filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Niklas Haas Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: From: Niklas Haas This filter can detect various properties about the image, including whether or not there are out-of-range values, or whether the input appears to use straight or premultiplied alpha. Of course, these can only be heuristics, with "undetermined" as the base case. While we can definitely prove the existence of full range or straight alpha colors, we can never infer the opposite. --- doc/filters.texi | 27 ++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_colordetect.c | 252 +++++++++++++++++++++++++++++++++++ libavfilter/vf_colordetect.h | 149 +++++++++++++++++++++ 5 files changed, 430 insertions(+) create mode 100644 libavfilter/vf_colordetect.c create mode 100644 libavfilter/vf_colordetect.h diff --git a/doc/filters.texi b/doc/filters.texi index ed2956fe75..74e9e71559 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -9753,6 +9753,33 @@ colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131 This filter supports the all above options as @ref{commands}. +@section colordetect +Analyze the video frames to determine the effective value range and alpha +mode. + +The filter accepts the following options: + +@table @option +@item mode +Set of properties to detect. Unavailable properties, such as alpha mode for +an input image without an alpha channel, will be ignored automatically. + +Accepts a combination of the following flags: + +@table @samp +@item color_range +Detect if the source countains luma pixels outside the limited (MPEG) range, +which indicates that this is a full range YUV source. +@item alpha_mode +Detect if the source contains color values above the alpha channel, which +indicates that the alpha channel is independent (straight), rather than +premultiplied. +@item all +Enable detection of all of the above properties. This is the default. +@end table + +@end table + @section colorize Overlay a solid color on the video stream. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 9e9153f5b0..e19f67a3a7 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -237,6 +237,7 @@ OBJS-$(CONFIG_COLORBALANCE_FILTER) += vf_colorbalance.o OBJS-$(CONFIG_COLORCHANNELMIXER_FILTER) += vf_colorchannelmixer.o OBJS-$(CONFIG_COLORCONTRAST_FILTER) += vf_colorcontrast.o OBJS-$(CONFIG_COLORCORRECT_FILTER) += vf_colorcorrect.o +OBJS-$(CONFIG_COLORDETECT_FILTER) += vf_colordetect.o OBJS-$(CONFIG_COLORIZE_FILTER) += vf_colorize.o OBJS-$(CONFIG_COLORKEY_FILTER) += vf_colorkey.o OBJS-$(CONFIG_COLORKEY_OPENCL_FILTER) += vf_colorkey_opencl.o opencl.o \ diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 409099bf1f..f3c2092b15 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -218,6 +218,7 @@ extern const FFFilter ff_vf_colorbalance; extern const FFFilter ff_vf_colorchannelmixer; extern const FFFilter ff_vf_colorcontrast; extern const FFFilter ff_vf_colorcorrect; +extern const FFFilter ff_vf_colordetect; extern const FFFilter ff_vf_colorize; extern const FFFilter ff_vf_colorkey; extern const FFFilter ff_vf_colorkey_opencl; diff --git a/libavfilter/vf_colordetect.c b/libavfilter/vf_colordetect.c new file mode 100644 index 0000000000..0fb892634f --- /dev/null +++ b/libavfilter/vf_colordetect.c @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2025 Niklas Haas + * + * 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 + * Video color space detector, tries to auto-detect YUV range and alpha mode. + */ + +#include +#include + +#include "config.h" + +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" + +#include "avfilter.h" +#include "filters.h" +#include "formats.h" +#include "video.h" + +#include "vf_colordetect.h" + +enum AlphaMode { + ALPHA_NONE = -1, + ALPHA_UNDETERMINED = 0, + ALPHA_STRAIGHT, + /* No way to positively identify premultiplied alpha */ +}; + +enum ColorDetectMode { + COLOR_DETECT_COLOR_RANGE = 1 << 0, + COLOR_DETECT_ALPHA_MODE = 1 << 1, +}; + +typedef struct ColorDetectContext { + const AVClass *class; + FFColorDetectDSPContext dsp; + unsigned mode; + + const AVPixFmtDescriptor *desc; + int nb_threads; + int depth; + int idx_a; + int mpeg_min; + int mpeg_max; + + atomic_int detected_range; // enum AVColorRange + atomic_int detected_alpha; // enum AlphaMode +} ColorDetectContext; + +#define OFFSET(x) offsetof(ColorDetectContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption colordetect_options[] = { + { "mode", "Image properties to detect", OFFSET(mode), AV_OPT_TYPE_FLAGS, {.i64 = -1}, 0, UINT_MAX, FLAGS, .unit = "mode" }, + { "color_range", "Detect (YUV) color range", 0, AV_OPT_TYPE_CONST, {.i64 = COLOR_DETECT_COLOR_RANGE}, 0, 0, FLAGS, .unit = "mode" }, + { "alpha_mode", "Detect alpha mode", 0, AV_OPT_TYPE_CONST, {.i64 = COLOR_DETECT_ALPHA_MODE }, 0, 0, FLAGS, .unit = "mode" }, + { "all", "Detect all supported properties", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, FLAGS, .unit = "mode" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(colordetect); + +static int query_format(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + int want_flags = AV_PIX_FMT_FLAG_PLANAR; + int reject_flags = AV_PIX_FMT_FLAG_PAL | AV_PIX_FMT_FLAG_HWACCEL | + AV_PIX_FMT_FLAG_BITSTREAM | AV_PIX_FMT_FLAG_FLOAT | + AV_PIX_FMT_FLAG_BAYER | AV_PIX_FMT_FLAG_XYZ; + + if (HAVE_BIGENDIAN) { + want_flags |= AV_PIX_FMT_FLAG_BE; + } else { + reject_flags |= AV_PIX_FMT_FLAG_BE; + } + + AVFilterFormats *formats = ff_formats_pixdesc_filter(want_flags, reject_flags); + return ff_set_common_formats2(ctx, cfg_in, cfg_out, formats); +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + ColorDetectContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + const int depth = desc->comp[0].depth; + const int mpeg_min = 16 << (depth - 8); + const int mpeg_max = 235 << (depth - 8); + if (depth > 16) /* not currently possible; prevent future bugs */ + return AVERROR(ENOTSUP); + + s->desc = desc; + s->depth = depth; + s->mpeg_min = mpeg_min; + s->mpeg_max = mpeg_max; + s->nb_threads = ff_filter_get_nb_threads(ctx); + + if (desc->flags & AV_PIX_FMT_FLAG_RGB) { + atomic_init(&s->detected_range, AVCOL_RANGE_JPEG); + } else { + atomic_init(&s->detected_range, AVCOL_RANGE_UNSPECIFIED); + } + + if (desc->flags & AV_PIX_FMT_FLAG_ALPHA) { + s->idx_a = desc->comp[desc->nb_components - 1].plane; + atomic_init(&s->detected_alpha, ALPHA_UNDETERMINED); + } else { + atomic_init(&s->detected_alpha, ALPHA_NONE); + } + + ff_color_detect_dsp_init(&s->dsp, depth, inlink->color_range); + return 0; +} + +static int detect_range(AVFilterContext *ctx, void *arg, + int jobnr, int nb_jobs) +{ + ColorDetectContext *s = ctx->priv; + const AVFrame *in = arg; + const ptrdiff_t stride = in->linesize[0]; + const int y_start = (in->height * jobnr) / nb_jobs; + const int y_end = (in->height * (jobnr + 1)) / nb_jobs; + const int h_slice = y_end - y_start; + + if (s->dsp.detect_range(in->data[0] + y_start * stride, stride, + in->width, h_slice, s->mpeg_min, s->mpeg_max)) + atomic_store(&s->detected_range, AVCOL_RANGE_JPEG); + + return 0; +} + +static int detect_alpha(AVFilterContext *ctx, void *arg, + int jobnr, int nb_jobs) +{ + ColorDetectContext *s = ctx->priv; + const AVFrame *in = arg; + const int w = in->width; + const int h = in->height; + const int y_start = (h * jobnr) / nb_jobs; + const int y_end = (h * (jobnr + 1)) / nb_jobs; + const int h_slice = y_end - y_start; + + const int nb_planes = (s->desc->flags & AV_PIX_FMT_FLAG_RGB) ? 3 : 1; + const ptrdiff_t alpha_stride = in->linesize[s->idx_a]; + const uint8_t *alpha = in->data[s->idx_a] + y_start * alpha_stride; + + const int p = (1 << s->depth) - 1; + const int q = s->mpeg_max - s->mpeg_min; + const int k = s->mpeg_min * p + 128; + + for (int i = 0; i < nb_planes; i++) { + const ptrdiff_t stride = in->linesize[i]; + if (s->dsp.detect_alpha(in->data[i] + y_start * stride, stride, + alpha, alpha_stride, w, h_slice, p, q, k)) { + atomic_store(&s->detected_alpha, ALPHA_STRAIGHT); + return 0; + } + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + ColorDetectContext *s = ctx->priv; + const int nb_threads = FFMIN(inlink->h, s->nb_threads); + + if (s->mode & COLOR_DETECT_COLOR_RANGE && s->detected_range == AVCOL_RANGE_UNSPECIFIED) + ff_filter_execute(ctx, detect_range, in, NULL, nb_threads); + if (s->mode & COLOR_DETECT_ALPHA_MODE && s->detected_alpha == ALPHA_UNDETERMINED) + ff_filter_execute(ctx, detect_alpha, in, NULL, nb_threads); + + return ff_filter_frame(inlink->dst->outputs[0], in); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + ColorDetectContext *s = ctx->priv; + if (!s->mode) + return; + + av_log(ctx, AV_LOG_INFO, "Detected color properties:\n"); + if (s->mode & COLOR_DETECT_COLOR_RANGE) { + av_log(ctx, AV_LOG_INFO, " Color range: %s\n", + s->detected_range == AVCOL_RANGE_JPEG ? "JPEG / full range" + : "undetermined"); + } + + if (s->mode & COLOR_DETECT_ALPHA_MODE) { + av_log(ctx, AV_LOG_INFO, " Alpha mode: %s\n", + s->detected_alpha == ALPHA_NONE ? "none" : + s->detected_alpha == ALPHA_STRAIGHT ? "straight / independent" + : "undetermined"); + } +} + +av_cold void ff_color_detect_dsp_init(FFColorDetectDSPContext *dsp, int depth, + enum AVColorRange color_range) +{ + if (!dsp->detect_range) + dsp->detect_range = depth > 8 ? ff_detect_range16_c : ff_detect_range_c; + if (!dsp->detect_alpha) { + if (color_range == AVCOL_RANGE_JPEG) { + dsp->detect_alpha = depth > 8 ? ff_detect_alpha16_full_c : ff_detect_alpha_full_c; + } else { + dsp->detect_alpha = depth > 8 ? ff_detect_alpha16_limited_c : ff_detect_alpha_limited_c; + } + } +} + +static const AVFilterPad colordetect_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + .filter_frame = filter_frame, + }, +}; + +const FFFilter ff_vf_colordetect = { + .p.name = "colordetect", + .p.description = NULL_IF_CONFIG_SMALL("Detect video color properties."), + .p.priv_class = &colordetect_class, + .p.flags = AVFILTER_FLAG_SLICE_THREADS | AVFILTER_FLAG_METADATA_ONLY, + .priv_size = sizeof(ColorDetectContext), + FILTER_INPUTS(colordetect_inputs), + FILTER_OUTPUTS(ff_video_default_filterpad), + FILTER_QUERY_FUNC2(query_format), + .uninit = uninit, +}; diff --git a/libavfilter/vf_colordetect.h b/libavfilter/vf_colordetect.h new file mode 100644 index 0000000000..8998ed83d4 --- /dev/null +++ b/libavfilter/vf_colordetect.h @@ -0,0 +1,149 @@ +/* + * 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 + */ + +#ifndef AVFILTER_VF_COLORDETECT_H +#define AVFILTER_VF_COLORDETECT_H + +#include +#include + +#include +#include + +typedef struct FFColorDetectDSPContext { + /* Returns 1 if an out-of-range value was detected, 0 otherwise */ + int (*detect_range)(const uint8_t *data, ptrdiff_t stride, + ptrdiff_t width, ptrdiff_t height, + int mpeg_min, int mpeg_max); + + /* Returns 1 if the color value exceeds the alpha value, 0 otherwise */ + int (*detect_alpha)(const uint8_t *color, ptrdiff_t color_stride, + const uint8_t *alpha, ptrdiff_t alpha_stride, + ptrdiff_t width, ptrdiff_t height, + int p, int q, int k); +} FFColorDetectDSPContext; + +void ff_color_detect_dsp_init(FFColorDetectDSPContext *dsp, int depth, + enum AVColorRange color_range); + +static inline int ff_detect_range_c(const uint8_t *data, ptrdiff_t stride, + ptrdiff_t width, ptrdiff_t height, + int mpeg_min, int mpeg_max) +{ + while (height--) { + for (int x = 0; x < width; x++) { + const uint8_t val = data[x]; + if (val < mpeg_min || val > mpeg_max) + return 1; + } + data += stride; + } + + return 0; +} + +static inline int ff_detect_range16_c(const uint8_t *data, ptrdiff_t stride, + ptrdiff_t width, ptrdiff_t height, + int mpeg_min, int mpeg_max) +{ + while (height--) { + const uint16_t *data16 = (const uint16_t *) data; + for (int x = 0; x < width; x++) { + const uint16_t val = data16[x]; + if (val < mpeg_min || val > mpeg_max) + return 1; + } + data += stride; + } + + return 0; +} + +static inline int +ff_detect_alpha_full_c(const uint8_t *color, ptrdiff_t color_stride, + const uint8_t *alpha, ptrdiff_t alpha_stride, + ptrdiff_t width, ptrdiff_t height, + int p, int q, int k) +{ + while (height--) { + for (int x = 0; x < width; x++) { + if (color[x] > alpha[x]) + return 1; + } + color += color_stride; + alpha += alpha_stride; + } + return 0; +} + +static inline int +ff_detect_alpha_limited_c(const uint8_t *color, ptrdiff_t color_stride, + const uint8_t *alpha, ptrdiff_t alpha_stride, + ptrdiff_t width, ptrdiff_t height, + int p, int q, int k) +{ + while (height--) { + for (int x = 0; x < width; x++) { + if (p * color[x] - k > q * alpha[x]) + return 1; + } + color += color_stride; + alpha += alpha_stride; + } + return 0; +} + +static inline int +ff_detect_alpha16_full_c(const uint8_t *color, ptrdiff_t color_stride, + const uint8_t *alpha, ptrdiff_t alpha_stride, + ptrdiff_t width, ptrdiff_t height, + int p, int q, int k) +{ + while (height--) { + const uint16_t *color16 = (const uint16_t *) color; + const uint16_t *alpha16 = (const uint16_t *) alpha; + for (int x = 0; x < width; x++) { + if (color16[x] > alpha16[x]) + return 1; + } + color += color_stride; + alpha += alpha_stride; + } + return 0; +} + +static inline int +ff_detect_alpha16_limited_c(const uint8_t *color, ptrdiff_t color_stride, + const uint8_t *alpha, ptrdiff_t alpha_stride, + ptrdiff_t width, ptrdiff_t height, + int p, int q, int k) +{ + while (height--) { + const uint16_t *color16 = (const uint16_t *) color; + const uint16_t *alpha16 = (const uint16_t *) alpha; + for (int x = 0; x < width; x++) { + if ((int64_t) p * color16[x] - k > (int64_t) q * alpha16[x]) + return 1; + } + color += color_stride; + alpha += alpha_stride; + } + return 0; +} + +#endif /* AVFILTER_VF_COLORDETECT_H */ -- 2.50.1 _______________________________________________ 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".