diff mbox

[FFmpeg-devel,1/2] avfilter: add thistogram video filter

Message ID 20191226202527.19829-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Dec. 26, 2019, 8:25 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi           |  46 ++++++++++++++
 libavfilter/Makefile       |   1 +
 libavfilter/allfilters.c   |   1 +
 libavfilter/vf_histogram.c | 127 ++++++++++++++++++++++++++++++-------
 4 files changed, 151 insertions(+), 24 deletions(-)

Comments

Nicolas George Dec. 27, 2019, 11:01 a.m. UTC | #1
Paul B Mahol (12019-12-26):
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi           |  46 ++++++++++++++
>  libavfilter/Makefile       |   1 +
>  libavfilter/allfilters.c   |   1 +
>  libavfilter/vf_histogram.c | 127 ++++++++++++++++++++++++++++++-------
>  4 files changed, 151 insertions(+), 24 deletions(-)

Thanks for merging them. I have no objection to this version, just a
small remark below.

I did not look at the arithmetic of the code closely, but I can do it if
you want me to.

> +    if (!strcmp(ctx->filter->name, "thistogram"))
> +        s->thistogram = 1;

Could you not test if s->width == 1 and have just one filter:

	histogram=... -> equivalent to current histogram

	histogram=width=1080:... -> equivalent to new thistogram

? It would save you the task of using a macro for common options.

Regards,
Paul B Mahol Dec. 27, 2019, 11:34 a.m. UTC | #2
On 12/27/19, Nicolas George <george@nsup.org> wrote:
> Paul B Mahol (12019-12-26):
>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>> ---
>>  doc/filters.texi           |  46 ++++++++++++++
>>  libavfilter/Makefile       |   1 +
>>  libavfilter/allfilters.c   |   1 +
>>  libavfilter/vf_histogram.c | 127 ++++++++++++++++++++++++++++++-------
>>  4 files changed, 151 insertions(+), 24 deletions(-)
>
> Thanks for merging them. I have no objection to this version, just a
> small remark below.
>
> I did not look at the arithmetic of the code closely, but I can do it if
> you want me to.
>
>> +    if (!strcmp(ctx->filter->name, "thistogram"))
>> +        s->thistogram = 1;
>
> Could you not test if s->width == 1 and have just one filter:
>
> 	histogram=... -> equivalent to current histogram
>
> 	histogram=width=1080:... -> equivalent to new thistogram
>
> ? It would save you the task of using a macro for common options.

Nope, as it would not be logical. It would become big mess.

> Regards,
>
> --
>   Nicolas George
>
Paul B Mahol Dec. 27, 2019, 3:09 p.m. UTC | #3
Will apply in 5 minutes.
Nicolas George Dec. 27, 2019, 3:10 p.m. UTC | #4
Paul B Mahol (12019-12-27):
> Will apply in 5 minutes.

Too soon. Let other people time to spot problems and voice objections.

Regards,
James Almer Dec. 27, 2019, 3:12 p.m. UTC | #5
On 12/26/2019 5:25 PM, Paul B Mahol wrote:
> +    if (s->thistogram)
> +        return ff_filter_frame(outlink, av_frame_clone(out));

av_frame_clone() can fail to allocate an AVFrame struct and return NULL,
which will make ff_filter_frame() crash as it doesn't expect frame to be
NULL.

> +    else
> +        return ff_filter_frame(outlink, out);
Paul B Mahol Dec. 27, 2019, 3:21 p.m. UTC | #6
On 12/27/19, Nicolas George <george@nsup.org> wrote:
> Paul B Mahol (12019-12-27):
>> Will apply in 5 minutes.
>
> Too soon. Let other people time to spot problems and voice objections.

I disagree.

>
> Regards,
>
> --
>   Nicolas George
>
Nicolas George Dec. 27, 2019, 3:26 p.m. UTC | #7
Paul B Mahol (12019-12-27):
> I disagree.

And yet in the meantime somebody found a bug. This is a big patch, and
we have a policy.
Paul B Mahol Dec. 27, 2019, 3:29 p.m. UTC | #8
On 12/27/19, Nicolas George <george@nsup.org> wrote:
> Paul B Mahol (12019-12-27):
>> I disagree.
>
> And yet in the meantime somebody found a bug. This is a big patch, and
> we have a policy.
>

I will not follow such policy.

> --
>   Nicolas George
>
James Almer Dec. 27, 2019, 3:35 p.m. UTC | #9
On 12/27/2019 12:29 PM, Paul B Mahol wrote:
> On 12/27/19, Nicolas George <george@nsup.org> wrote:
>> Paul B Mahol (12019-12-27):
>>> I disagree.
>>
>> And yet in the meantime somebody found a bug. This is a big patch, and
>> we have a policy.
>>
> 
> I will not follow such policy.

Paul, you got a positive review. All you have to do is wait a day or so
before pushing. Why are you blowing this so hard?

> 
>> --
>>   Nicolas George
>>
> _______________________________________________
> 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 Dec. 27, 2019, 3:48 p.m. UTC | #10
On 12/27/19, James Almer <jamrial@gmail.com> wrote:
> On 12/27/2019 12:29 PM, Paul B Mahol wrote:
>> On 12/27/19, Nicolas George <george@nsup.org> wrote:
>>> Paul B Mahol (12019-12-27):
>>>> I disagree.
>>>
>>> And yet in the meantime somebody found a bug. This is a big patch, and
>>> we have a policy.
>>>
>>
>> I will not follow such policy.
>
> Paul, you got a positive review. All you have to do is wait a day or so
> before pushing. Why are you blowing this so hard?

I'm not going to wait a day or so, I gonna push this immediately.

>
>>
>>> --
>>>   Nicolas George
>>>
>> _______________________________________________
>> 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".
Reino Wijnsma Dec. 27, 2019, 4:47 p.m. UTC | #11
On 2019-12-27T16:48:22+0100, Paul B Mahol <onemda@gmail.com> wrote:
> On 12/27/19, James Almer <jamrial@gmail.com> wrote:
>> On 12/27/2019 12:29 PM, Paul B Mahol wrote:
>>> On 12/27/19, Nicolas George <george@nsup.org> wrote:
>>>> Paul B Mahol (12019-12-27):
>>>>> I disagree.
>>>>>
>>>>> And yet in the meantime somebody found a bug. This is a big patch, and
>>>>> we have a policy.
>>>>>
>>> I will not follow such policy.
>> Paul, you got a positive review. All you have to do is wait a day or so
>> before pushing. Why are you blowing this so hard?
> I'm not going to wait a day or so, I gonna push this immediately.
Why are you always such a deliberate troubleseeker?!
Every single developer here treats the other with respect AND has respect for
the mailinglist rules here. It's as if you think they don't apply to you at all.
If I'm correct the official rule is to wait for at least 1 week before pushing
a patch, yet you want to push it after hardly 1 day!
Top-posting is not allowed on this list, yet of all the core developers here
you're the only one I see doing this very thing quite often. And somehow you're
getting away with it.
I really don't understand how the other developers keep up with you and your
childish behaviour.

-- Reino
Paul B Mahol Dec. 27, 2019, 5:49 p.m. UTC | #12
On 12/27/19, Reino Wijnsma <rwijnsma@xs4all.nl> wrote:
> On 2019-12-27T16:48:22+0100, Paul B Mahol <onemda@gmail.com> wrote:
>> On 12/27/19, James Almer <jamrial@gmail.com> wrote:
>>> On 12/27/2019 12:29 PM, Paul B Mahol wrote:
>>>> On 12/27/19, Nicolas George <george@nsup.org> wrote:
>>>>> Paul B Mahol (12019-12-27):
>>>>>> I disagree.
>>>>>>
>>>>>> And yet in the meantime somebody found a bug. This is a big patch, and
>>>>>> we have a policy.
>>>>>>
>>>> I will not follow such policy.
>>> Paul, you got a positive review. All you have to do is wait a day or so
>>> before pushing. Why are you blowing this so hard?
>> I'm not going to wait a day or so, I gonna push this immediately.
> Why are you always such a deliberate troubleseeker?!
> Every single developer here treats the other with respect AND has respect
> for
> the mailinglist rules here. It's as if you think they don't apply to you at
> all.
> If I'm correct the official rule is to wait for at least 1 week before
> pushing
> a patch, yet you want to push it after hardly 1 day!
> Top-posting is not allowed on this list, yet of all the core developers here
> you're the only one I see doing this very thing quite often. And somehow
> you're
> getting away with it.
> I really don't understand how the other developers keep up with you and your
> childish behaviour.

Both rules being mentioned here are not useful to be strictly followed.

Top-posting is allowed on this list. Discriminating by top-posting is not OK.

The rule you mentioned is to wait for patches that no-one wants to apply/review.

Your interpretation of rule to wait for patch committing at least one
week is dangerous.

As I'm patch creator and code maintainer I'm free to apply any patches.

So to conclude: there is no rule to minimal wait for patch to be
applied after it is reviewed.

It is just that some developers want and like to block my commits, and
this is indeed childish behavior of them as you like to write.
Paul B Mahol Dec. 29, 2019, 11:20 a.m. UTC | #13
Gonna apply soon with local minor changes.
diff mbox

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index 8c5d3a5760..4468351fc4 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -11674,6 +11674,7 @@  the histogram. Possible values are @code{none}, @code{weak} or
 @code{strong}. It defaults to @code{none}.
 @end table
 
+@anchor{histogram}
 @section histogram
 
 Compute and draw a color distribution histogram for the input video.
@@ -17717,6 +17718,51 @@  PAL output (25i):
 16p: 33333334
 @end example
 
+@section thistogram
+
+Compute and draw a color distribution histogram for the input video across time.
+
+Unlike @ref{histogram} video filter which only shows histogram of single input frame
+at certain time, this filter shows also past histograms of number of frames defined
+by @code{width} option.
+
+The computed histogram is a representation of the color component
+distribution in an image.
+
+The filter accepts the following options:
+
+@table @option
+@item width
+Set width of single color component output. Default value is @code{1080}.
+This also set number of passed histograms to keep.
+Allowed range is [50, 8192].
+
+@item display_mode, d
+Set display mode.
+It accepts the following values:
+@table @samp
+@item stack
+Per color component graphs are placed below each other.
+
+@item parade
+Per color component graphs are placed side by side.
+
+@item overlay
+Presents information identical to that in the @code{parade}, except
+that the graphs representing color components are superimposed directly
+over one another.
+@end table
+Default is @code{stack}.
+
+@item levels_mode, m
+Set mode. Can be either @code{linear}, or @code{logarithmic}.
+Default is @code{linear}.
+
+@item components, c
+Set what color components to display.
+Default is @code{7}.
+@end table
+
 @section threshold
 
 Apply threshold effect to video stream.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 37d4eee858..8b8a5bd535 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -401,6 +401,7 @@  OBJS-$(CONFIG_SWAPRECT_FILTER)               += vf_swaprect.o
 OBJS-$(CONFIG_SWAPUV_FILTER)                 += vf_swapuv.o
 OBJS-$(CONFIG_TBLEND_FILTER)                 += vf_blend.o framesync.o
 OBJS-$(CONFIG_TELECINE_FILTER)               += vf_telecine.o
+OBJS-$(CONFIG_THISTOGRAM_FILTER)             += vf_histogram.o
 OBJS-$(CONFIG_THRESHOLD_FILTER)              += vf_threshold.o framesync.o
 OBJS-$(CONFIG_THUMBNAIL_FILTER)              += vf_thumbnail.o
 OBJS-$(CONFIG_THUMBNAIL_CUDA_FILTER)         += vf_thumbnail_cuda.o vf_thumbnail_cuda.ptx.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index c295f8e403..9f2080f857 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -382,6 +382,7 @@  extern AVFilter ff_vf_swaprect;
 extern AVFilter ff_vf_swapuv;
 extern AVFilter ff_vf_tblend;
 extern AVFilter ff_vf_telecine;
+extern AVFilter ff_vf_thistogram;
 extern AVFilter ff_vf_threshold;
 extern AVFilter ff_vf_thumbnail;
 extern AVFilter ff_vf_thumbnail_cuda;
diff --git a/libavfilter/vf_histogram.c b/libavfilter/vf_histogram.c
index 64ee993a21..83e8925ea7 100644
--- a/libavfilter/vf_histogram.c
+++ b/libavfilter/vf_histogram.c
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2012-2013 Paul B Mahol
+ * Copyright (c) 2012-2019 Paul B Mahol
  *
  * This file is part of FFmpeg.
  *
@@ -31,8 +31,11 @@ 
 
 typedef struct HistogramContext {
     const AVClass *class;               ///< AVClass context for log and options purpose
+    int            thistogram;
     unsigned       histogram[256*256];
     int            histogram_size;
+    int            width;
+    int            x_pos;
     int            mult;
     int            ncomp;
     int            dncomp;
@@ -48,25 +51,30 @@  typedef struct HistogramContext {
     float          bgopacity;
     int            planewidth[4];
     int            planeheight[4];
+    int            start[4];
+    AVFrame       *out;
 } HistogramContext;
 
 #define OFFSET(x) offsetof(HistogramContext, x)
 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
 
+#define COMMON_OPTIONS \
+    { "display_mode", "set display mode", OFFSET(display_mode), AV_OPT_TYPE_INT, {.i64=2}, 0, 2, FLAGS, "display_mode"}, \
+    { "d",            "set display mode", OFFSET(display_mode), AV_OPT_TYPE_INT, {.i64=2}, 0, 2, FLAGS, "display_mode"}, \
+        { "overlay", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "display_mode" }, \
+        { "parade",  NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "display_mode" }, \
+        { "stack",   NULL, 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "display_mode" }, \
+    { "levels_mode", "set levels mode", OFFSET(levels_mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS, "levels_mode"}, \
+    { "m",           "set levels mode", OFFSET(levels_mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS, "levels_mode"}, \
+        { "linear",      NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "levels_mode" }, \
+        { "logarithmic", NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "levels_mode" }, \
+    { "components", "set color components to display", OFFSET(components), AV_OPT_TYPE_INT, {.i64=7}, 1, 15, FLAGS}, \
+    { "c",          "set color components to display", OFFSET(components), AV_OPT_TYPE_INT, {.i64=7}, 1, 15, FLAGS},
+
 static const AVOption histogram_options[] = {
     { "level_height", "set level height", OFFSET(level_height), AV_OPT_TYPE_INT, {.i64=200}, 50, 2048, FLAGS},
     { "scale_height", "set scale height", OFFSET(scale_height), AV_OPT_TYPE_INT, {.i64=12}, 0, 40, FLAGS},
-    { "display_mode", "set display mode", OFFSET(display_mode), AV_OPT_TYPE_INT, {.i64=2}, 0, 2, FLAGS, "display_mode"},
-    { "d",            "set display mode", OFFSET(display_mode), AV_OPT_TYPE_INT, {.i64=2}, 0, 2, FLAGS, "display_mode"},
-        { "overlay", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "display_mode" },
-        { "parade",  NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "display_mode" },
-        { "stack",   NULL, 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "display_mode" },
-    { "levels_mode", "set levels mode", OFFSET(levels_mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS, "levels_mode"},
-    { "m",           "set levels mode", OFFSET(levels_mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS, "levels_mode"},
-        { "linear",      NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "levels_mode" },
-        { "logarithmic", NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "levels_mode" },
-    { "components", "set color components to display", OFFSET(components), AV_OPT_TYPE_INT, {.i64=7}, 1, 15, FLAGS},
-    { "c",          "set color components to display", OFFSET(components), AV_OPT_TYPE_INT, {.i64=7}, 1, 15, FLAGS},
+    COMMON_OPTIONS
     { "fgopacity", "set foreground opacity", OFFSET(fgopacity), AV_OPT_TYPE_FLOAT, {.dbl=0.7}, 0, 1, FLAGS},
     { "f",         "set foreground opacity", OFFSET(fgopacity), AV_OPT_TYPE_FLOAT, {.dbl=0.7}, 0, 1, FLAGS},
     { "bgopacity", "set background opacity", OFFSET(bgopacity), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS},
@@ -209,10 +217,13 @@  static int config_input(AVFilterLink *inlink)
     case AV_PIX_FMT_GBRP:
         memcpy(s->bg_color, black_gbrp_color, 4);
         memcpy(s->fg_color, white_gbrp_color, 4);
+        s->start[0] = s->start[1] = s->start[2] = s->start[3] = 0;
         break;
     default:
         memcpy(s->bg_color, black_yuva_color, 4);
         memcpy(s->fg_color, white_yuva_color, 4);
+        s->start[0] = s->start[3] = 0;
+        s->start[1] = s->start[2] = s->histogram_size / 2;
     }
 
     s->fg_color[3] = s->fgopacity * 255;
@@ -232,12 +243,21 @@  static int config_output(AVFilterLink *outlink)
     HistogramContext *s = ctx->priv;
     int ncomp = 0, i;
 
+    if (!strcmp(ctx->filter->name, "thistogram"))
+        s->thistogram = 1;
+
     for (i = 0; i < s->ncomp; i++) {
         if ((1 << i) & s->components)
             ncomp++;
     }
+
+    if (s->thistogram) {
+        outlink->w = s->width * FFMAX(ncomp * (s->display_mode == 1), 1);
+        outlink->h = s->histogram_size * FFMAX(ncomp * (s->display_mode == 2), 1);
+    } else {
     outlink->w = s->histogram_size * FFMAX(ncomp * (s->display_mode == 1), 1);
     outlink->h = (s->level_height + s->scale_height) * FFMAX(ncomp * (s->display_mode == 2), 1);
+    }
 
     s->odesc = av_pix_fmt_desc_get(outlink->format);
     s->dncomp = s->odesc->nb_components;
@@ -251,16 +271,16 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     HistogramContext *s   = inlink->dst->priv;
     AVFilterContext *ctx  = inlink->dst;
     AVFilterLink *outlink = ctx->outputs[0];
-    AVFrame *out;
+    AVFrame *out = s->out;
     int i, j, k, l, m;
 
+    if (!s->thistogram || !out) {
     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
     if (!out) {
         av_frame_free(&in);
         return AVERROR(ENOMEM);
     }
-
-    out->pts = in->pts;
+    s->out = out;
 
     for (k = 0; k < 4 && out->data[k]; k++) {
         const int is_chroma = (k == 1 || k == 2);
@@ -282,19 +302,26 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *in)
                         s->bg_color[k] * mult);
         }
     }
+    }
 
     for (m = 0, k = 0; k < s->ncomp; k++) {
         const int p = s->desc->comp[k].plane;
+        const int max_value = s->histogram_size - 1 - s->start[p];
         const int height = s->planeheight[p];
         const int width = s->planewidth[p];
         double max_hval_log;
         unsigned max_hval = 0;
-        int start, startx;
+        int starty, startx;
 
         if (!((1 << k) & s->components))
             continue;
+        if (s->thistogram) {
+            starty = m * s->histogram_size * (s->display_mode == 2);
+            startx = m++ * s->width * (s->display_mode == 1);
+        } else {
         startx = m * s->histogram_size * (s->display_mode == 1);
-        start = m++ * (s->level_height + s->scale_height) * (s->display_mode == 2);
+        starty = m++ * (s->level_height + s->scale_height) * (s->display_mode == 2);
+        }
 
         if (s->histogram_size <= 256) {
             for (i = 0; i < height; i++) {
@@ -314,6 +341,23 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *in)
             max_hval = FFMAX(max_hval, s->histogram[i]);
         max_hval_log = log2(max_hval + 1);
 
+        if (s->thistogram) {
+            for (int i = 0; i < s->histogram_size; i++) {
+                int idx = s->histogram_size - i - 1;
+                int value = s->start[p];
+
+                if (s->levels_mode)
+                    value += lrint(max_value * (log2(s->histogram[idx] + 1) / max_hval_log));
+                else
+                    value += lrint(max_value * s->histogram[idx] / (float)max_hval);
+
+                if (s->histogram_size <= 256) {
+                    s->out->data[p][(i + starty) * s->out->linesize[p] + startx + s->x_pos] = value;
+                } else {
+                    AV_WN16(s->out->data[p] + (i + starty) * s->out->linesize[p] + startx * 2 + s->x_pos * 2, value);
+                }
+            }
+        } else {
         for (i = 0; i < s->histogram_size; i++) {
             int col_height;
 
@@ -326,34 +370,43 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *in)
                 for (j = s->level_height - 1; j >= col_height; j--) {
                     if (s->display_mode) {
                         for (l = 0; l < s->dncomp; l++)
-                            out->data[l][(j + start) * out->linesize[l] + startx + i] = s->fg_color[l];
+                            out->data[l][(j + starty) * out->linesize[l] + startx + i] = s->fg_color[l];
                     } else {
-                        out->data[p][(j + start) * out->linesize[p] + startx + i] = 255;
+                        out->data[p][(j + starty) * out->linesize[p] + startx + i] = 255;
                     }
                 }
                 for (j = s->level_height + s->scale_height - 1; j >= s->level_height; j--)
-                    out->data[p][(j + start) * out->linesize[p] + startx + i] = i;
+                    out->data[p][(j + starty) * out->linesize[p] + startx + i] = i;
             } else {
                 const int mult = s->mult;
 
                 for (j = s->level_height - 1; j >= col_height; j--) {
                     if (s->display_mode) {
                         for (l = 0; l < s->dncomp; l++)
-                            AV_WN16(out->data[l] + (j + start) * out->linesize[l] + startx * 2 + i * 2, s->fg_color[l] * mult);
+                            AV_WN16(out->data[l] + (j + starty) * out->linesize[l] + startx * 2 + i * 2, s->fg_color[l] * mult);
                     } else {
-                        AV_WN16(out->data[p] + (j + start) * out->linesize[p] + startx * 2 + i * 2, 255 * mult);
+                        AV_WN16(out->data[p] + (j + starty) * out->linesize[p] + startx * 2 + i * 2, 255 * mult);
                     }
                 }
                 for (j = s->level_height + s->scale_height - 1; j >= s->level_height; j--)
-                    AV_WN16(out->data[p] + (j + start) * out->linesize[p] + startx * 2 + i * 2, i);
+                    AV_WN16(out->data[p] + (j + starty) * out->linesize[p] + startx * 2 + i * 2, i);
             }
         }
+        }
 
         memset(s->histogram, 0, s->histogram_size * sizeof(unsigned));
     }
 
+    out->pts = in->pts;
     av_frame_free(&in);
-    return ff_filter_frame(outlink, out);
+    s->x_pos++;
+    if (s->x_pos >= s->width)
+        s->x_pos = 0;
+
+    if (s->thistogram)
+        return ff_filter_frame(outlink, av_frame_clone(out));
+    else
+        return ff_filter_frame(outlink, out);
 }
 
 static const AVFilterPad inputs[] = {
@@ -375,6 +428,8 @@  static const AVFilterPad outputs[] = {
     { NULL }
 };
 
+#if CONFIG_HISTOGRAM_FILTER
+
 AVFilter ff_vf_histogram = {
     .name          = "histogram",
     .description   = NULL_IF_CONFIG_SMALL("Compute and draw a histogram."),
@@ -384,3 +439,27 @@  AVFilter ff_vf_histogram = {
     .outputs       = outputs,
     .priv_class    = &histogram_class,
 };
+
+#endif /* CONFIG_HISTOGRAM_FILTER */
+
+#if CONFIG_THISTOGRAM_FILTER
+
+static const AVOption thistogram_options[] = {
+    { "width", "set width", OFFSET(width), AV_OPT_TYPE_INT, {.i64=1080}, 50, 8192, FLAGS},
+    COMMON_OPTIONS
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(thistogram);
+
+AVFilter ff_vf_thistogram = {
+    .name          = "thistogram",
+    .description   = NULL_IF_CONFIG_SMALL("Compute and draw a temporal histogram."),
+    .priv_size     = sizeof(HistogramContext),
+    .query_formats = query_formats,
+    .inputs        = inputs,
+    .outputs       = outputs,
+    .priv_class    = &thistogram_class,
+};
+
+#endif /* CONFIG_THISTOGRAM_FILTER */