[FFmpeg-devel] [PATCH] life: add slow_death, life_color and death_color options.

Clément Bœsch ubitux at gmail.com
Thu Dec 8 01:32:50 CET 2011


On Thu, Dec 08, 2011 at 12:29:52AM +0100, Stefano Sabatini wrote:
> On date Wednesday 2011-12-07 07:51:55 +0100, Clément Bœsch encoded:
> > On Tue, Dec 06, 2011 at 03:18:37PM +0100, Stefano Sabatini wrote:
> > > On date Monday 2011-12-05 00:48:47 +0100, Clément Bœsch encoded:
> > > > ---
> > > > Additionally to the example, here is a more organic one:
> > > >   ffplay -f lavfi life=f=CREDITS:s=400x400:slow_death=1:r=120:death_color=#113366:life_color=#ff0000,boxblur=2:2
> > > > ---
> > > >  doc/filters.texi        |   17 ++++++++++
> > > >  libavfilter/vsrc_life.c |   82 +++++++++++++++++++++++++++++++---------------
> > > >  2 files changed, 72 insertions(+), 27 deletions(-)
> > > > 
> > > > diff --git a/doc/filters.texi b/doc/filters.texi
> > > > index eb1a4df..5e38b08 100644
> > > > --- a/doc/filters.texi
> > > > +++ b/doc/filters.texi
> > > > @@ -2863,6 +2863,17 @@ If a filename is not specified, the size value defaults to "320x240"
> > > >  @item stitch
> > > >  If set to 1, stitch the left and right grid edges together, and the
> > > >  top and bottom edges also. Defaults to 1.
> > > > +
> > > > + at item slow_death
> > > > +If set to 1, the progressive death effect will be effective: the dead cells
> > > > +will slightly go from @option{death_color} to black.
> > > > +
> > > > + at item life_color
> > > > +Color of a living cell.
> > > > +
> > > > + at item death_color
> > > > +Color of a dead cell. If @option{slow_death} is set, this is the first color
> > > > +used to represent a dead cell.
> > > >  @end table
> > > 
> > > nit: sort items
> > > 
> > 
> 
> > I'm not sure about that; life_color and death_color won't be together. I
> > could rename them to something like color0 and color1, but then they will
> > be the first option user will see (while they are less important than the
> > options we can see in the current list).
> 
> I'm still slightly in favor of sorted items so that I know where to
> put options if I add them, but that's bikeshed so as you prefer.
> 

How would you avoid the separation of life & death color options?

[...]
> > From c0e0355682330844f78d560f287721a1cb9ffa57 Mon Sep 17 00:00:00 2001
> > From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= <ubitux at gmail.com>
> > Date: Mon, 5 Dec 2011 00:38:39 +0100
> > Subject: [PATCH] life: add slow_death, life_color and death_color options.
> > 
> > ---
> >  doc/filters.texi        |   16 ++++++++
> >  libavfilter/vsrc_life.c |   98 +++++++++++++++++++++++++++++++++++++---------
> >  2 files changed, 95 insertions(+), 19 deletions(-)
> > 
> > diff --git a/doc/filters.texi b/doc/filters.texi
> > index eb1a4df..0e5bda7 100644
> > --- a/doc/filters.texi
> > +++ b/doc/filters.texi
> > @@ -2863,6 +2863,17 @@ If a filename is not specified, the size value defaults to "320x240"
> >  @item stitch
> >  If set to 1, stitch the left and right grid edges together, and the
> >  top and bottom edges also. Defaults to 1.
> > +
> > + at item slow_death
> 
> > +If set to 1, the progressive death effect will be effective: the dead cells
>                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> not very clear, I suggest "enable slow death effect:"
> 

Changed.

[...]
> > @@ -135,7 +144,7 @@ static void show_life_grid(AVFilterContext *ctx)
> >          return;
> >      for (i = 0; i < life->h; i++) {
> >          for (j = 0; j < life->w; j++)
> > -            line[j] = life->buf[life->buf_idx][i*life->w + j] ? '@' : ' ';
> > +            line[j] = life->buf[life->buf_idx][i*life->w + j] ? ' ' : '@';
> >          line[j] = 0;
> >          av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line);
> >      }
> > @@ -186,13 +195,14 @@ static int init_pattern_from_file(AVFilterContext *ctx)
> >  
> >      /* fill buf[0] */
> >      p = life->file_buf;
> > +    memset(life->buf[0], 0xff, life->w * life->h);
> 
> why the inverted logic? My more natural interpretation is that alive
> cells should have non-zero values, decreasing to zero as they die.
> 

To allow a wider range? I added a doxycomment to buf to explain this:

    A zero value means the cell is alive (or new born), while the values
    from 1 to 0xFF means the cell is dead; the range of values is used for
    the slow death effect (1 means dead, 2 means very dead, 3 means very
    very dead... and 0xFF means definitely dead).

We could use 0 → 0xFE for dead cell, and 0xFF for alive cell, but it's a bit
weird to deal with the value sometimes (you can't just "not" the value for
instance). I also don't like the solution of 0 and 2 → 0xFF for dead. And using
signed value make use of half of the buffer (BTW, I changed the char to uint8_t
in the struct).

[...]
> >  
> > +static void fill_picture_rgb(AVFilterContext *ctx, AVFilterBufferRef *picref)
> > +{
> > +    LifeContext *life = ctx->priv;
> > +    uint8_t *buf = life->buf[life->buf_idx];
> > +    int i, j;
> > +
> > +    /* fill the output picture with the old grid buffer */
> > +    for (i = 0; i < life->h; i++) {
> > +        uint8_t *p = picref->data[0] + i * picref->linesize[0];
> > +        for (j = 0; j < life->w; j++) {
> > +            uint8_t death_age = buf[i*life->w + j];
> > +            if (death_age == 0) {
> > +                *p++ = life->life_color[0];
> > +                *p++ = life->life_color[1];
> > +                *p++ = life->life_color[2];
> > +            } else {
> 
> > +                if (life->slow_death) {
> > +                    *p++ = FFMAX((int)life->death_color[0] - death_age, 0);
> > +                    *p++ = FFMAX((int)life->death_color[1] - death_age, 0);
> > +                    *p++ = FFMAX((int)life->death_color[2] - death_age, 0);
> 
> wht about interpolating between life and death color?

I simplified the whole block, it should be better.

> In this case > you may also want to define how fast a cell dies.
> 

Yes good idea, but maybe in an additional patch, this is one is big enough IMO.

> > +                } else {
> > +                    *p++ = life->death_color[0];
> > +                    *p++ = life->death_color[1];
> > +                    *p++ = life->death_color[2];
> > +                }
> > +            }
> > +        }
> > +    }
> > +}
> > +
> >  static int request_frame(AVFilterLink *outlink)
> >  {
> >      LifeContext *life = outlink->src->priv;
> > @@ -368,7 +420,7 @@ static int request_frame(AVFilterLink *outlink)
> >      picref->pts = life->pts++;
> >      picref->pos = -1;
> >  
> > -    fill_picture(outlink->src, picref);
> > +    life->draw(outlink->src, picref);
> >      evolve(outlink->src);
> >  #ifdef DEBUG
> >      show_life_grid(outlink->src);
> > @@ -384,7 +436,15 @@ static int request_frame(AVFilterLink *outlink)
> >  
> >  static int query_formats(AVFilterContext *ctx)
> >  {
> > -    static const enum PixelFormat pix_fmts[] = { PIX_FMT_MONOBLACK, PIX_FMT_NONE };
> > +    LifeContext *life = ctx->priv;
> > +    enum PixelFormat pix_fmts[] = { PIX_FMT_MONOBLACK, PIX_FMT_NONE };
> > +    if (   life->slow_death
> > +        || memcmp(life-> life_color, "\xff\xff\xff", 3)
> > +        || memcmp(life->death_color, "\x00\x00\x00", 3)) {
> > +        pix_fmts[0] = PIX_FMT_RGB24;
> > +        life->draw = fill_picture_rgb;
> > +    } else
> > +        life->draw = fill_picture_monoblack;
> 
> Nit: set pix_fmts[] = { PIX_FMT_NONE, PIX_FMT_NONE } and set
> pix_fmts[0] = MONOB explicitely.
> 

Sure OK.

-- 
Clément B.
-------------- next part --------------
From 8099b89dc2aef8e85c6a00ef0c61003ff42d9150 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= <ubitux at gmail.com>
Date: Mon, 5 Dec 2011 00:38:39 +0100
Subject: [PATCH] life: add slow_death, life_color and death_color options.

---
 doc/filters.texi        |   16 +++++++
 libavfilter/vsrc_life.c |  103 +++++++++++++++++++++++++++++++++++++---------
 2 files changed, 99 insertions(+), 20 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index eb1a4df..a697df5 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -2863,6 +2863,17 @@ If a filename is not specified, the size value defaults to "320x240"
 @item stitch
 If set to 1, stitch the left and right grid edges together, and the
 top and bottom edges also. Defaults to 1.
+
+ at item slow_death
+Enable slow death effect: the dead cells will slightly go from
+ at option{death_color} to black.
+
+ at item life_color
+Color of a living (or new born) cell.
+
+ at item death_color
+Color of a dead cell. If @option{slow_death} is set, this is the first color
+used to represent a dead cell.
 @end table
 
 @subsection Examples
@@ -2887,6 +2898,11 @@ Specify a custom rule for evolving a randomly generated grid:
 life=rule=S14/B34
 @end example
 
+ at item
+Full example with slow death effect using ffplay:
+ at example
+ffplay -f lavfi life=s=300x200:slow_death=1:r=60:ratio=0.1:death_color=#C83232:life_color=#00ff00,scale=1200:800:flags=16
+ at end example
 @end itemize
 
 @section nullsrc, rgbtestsrc, testsrc
diff --git a/libavfilter/vsrc_life.c b/libavfilter/vsrc_life.c
index ce0eecc..3f23d96 100644
--- a/libavfilter/vsrc_life.c
+++ b/libavfilter/vsrc_life.c
@@ -39,7 +39,17 @@ typedef struct {
     char *rule_str;
     uint8_t *file_buf;
     size_t file_bufsize;
-    char *buf[2];
+
+    /**
+     * The two grid state buffers.
+     *
+     * A zero value means the cell is alive (or new born), while the values
+     * from 1 to 0xFF means the cell is dead; the range of values is used for
+     * the slow death effect (1 means dead, 2 means very dead, 3 means very
+     * very dead... and 0xFF means definitely dead).
+     */
+    uint8_t *buf[2];
+
     uint8_t  buf_idx;
     uint16_t stay_rule;         ///< encode the behavior for filled cells
     uint16_t born_rule;         ///< encode the behavior for empty cells
@@ -50,7 +60,13 @@ typedef struct {
     double   random_fill_ratio;
     uint32_t random_seed;
     int stitch;
+    int slow_death;
+    char  *life_color_str;
+    char *death_color_str;
+    uint8_t  life_color[4];
+    uint8_t death_color[4];
     AVLFG lfg;
+    void (*draw)(AVFilterContext*, AVFilterBufferRef*);
 } LifeContext;
 
 #define OFFSET(x) offsetof(LifeContext, x)
@@ -68,6 +84,9 @@ static const AVOption life_options[] = {
     { "random_seed", "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_INT, {.dbl=-1}, -1, UINT32_MAX },
     { "seed",        "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_INT, {.dbl=-1}, -1, UINT32_MAX },
     { "stitch",      "stitch boundaries", OFFSET(stitch), AV_OPT_TYPE_INT, {.dbl=1}, 0, 1 },
+    { "slow_death",  "set slow death effect", OFFSET(slow_death), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 },
+    { "life_color",  "set life color",  OFFSET( life_color_str), AV_OPT_TYPE_STRING, {.str="white"}, CHAR_MIN, CHAR_MAX },
+    { "death_color", "set death color", OFFSET(death_color_str), AV_OPT_TYPE_STRING, {.str="black"}, CHAR_MIN, CHAR_MAX },
     { NULL },
 };
 
@@ -135,7 +154,7 @@ static void show_life_grid(AVFilterContext *ctx)
         return;
     for (i = 0; i < life->h; i++) {
         for (j = 0; j < life->w; j++)
-            line[j] = life->buf[life->buf_idx][i*life->w + j] ? '@' : ' ';
+            line[j] = life->buf[life->buf_idx][i*life->w + j] ? ' ' : '@';
         line[j] = 0;
         av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line);
     }
@@ -186,13 +205,14 @@ static int init_pattern_from_file(AVFilterContext *ctx)
 
     /* fill buf[0] */
     p = life->file_buf;
+    memset(life->buf[0], 0xff, life->w * life->h);
     for (i0 = 0, i = (life->h - h)/2; i0 < h; i0++, i++) {
         for (j = (life->w - max_w)/2;; j++) {
             av_log(ctx, AV_LOG_DEBUG, "%d:%d %c\n", i, j, *p == '\n' ? 'N' : *p);
             if (*p == '\n') {
                 p++; break;
             } else
-                life->buf[0][i*life->w + j] = !!isgraph(*(p++));
+                life->buf[0][i*life->w + j] = !isgraph(*(p++)) * 0xff;
         }
     }
     life->buf_idx = 0;
@@ -231,6 +251,16 @@ static int init(AVFilterContext *ctx, const char *args, void *opaque)
     if ((ret = parse_rule(&life->born_rule, &life->stay_rule, life->rule_str, ctx)) < 0)
         return ret;
 
+    if ((ret = av_parse_color(life->life_color, life->life_color_str, -1, ctx))) {
+        av_log(ctx, AV_LOG_ERROR, "Invalid life color '%s'\n", life->life_color_str);
+        return ret;
+    }
+
+    if ((ret = av_parse_color(life->death_color, life->death_color_str, -1, ctx))) {
+        av_log(ctx, AV_LOG_ERROR, "Invalid death color '%s'\n", life->death_color_str);
+        return ret;
+    }
+
     life->time_base.num = frame_rate.den;
     life->time_base.den = frame_rate.num;
 
@@ -251,14 +281,15 @@ static int init(AVFilterContext *ctx, const char *args, void *opaque)
 
         for (i = 0; i < life->w * life->h; i++) {
             double r = (double)av_lfg_get(&life->lfg) / UINT32_MAX;
-            if (r <= life->random_fill_ratio)
-                life->buf[0][i] = 1;
+            if (r > life->random_fill_ratio)
+                life->buf[0][i] = 0xff; // dead
         }
         life->buf_idx = 0;
     } else {
         if ((ret = init_pattern_from_file(ctx)) < 0)
             return ret;
     }
+    memcpy(life->buf[1], life->buf[0], life->w * life->h);
 
     av_log(ctx, AV_LOG_INFO,
            "s:%dx%d r:%d/%d rule:%s stay_rule:%d born_rule:%d stitch:%d\n",
@@ -322,24 +353,25 @@ static void evolve(AVFilterContext *ctx)
             }
 
             /* compute the number of live neighbor cells */
-            n = (pos[NW][0] == -1 || pos[NW][1] == -1 ? 0 : oldbuf[pos[NW][0]*life->w + pos[NW][1]]) +
-                (pos[N ][0] == -1 || pos[N ][1] == -1 ? 0 : oldbuf[pos[N ][0]*life->w + pos[N ][1]]) +
-                (pos[NE][0] == -1 || pos[NE][1] == -1 ? 0 : oldbuf[pos[NE][0]*life->w + pos[NE][1]]) +
-                (pos[W ][0] == -1 || pos[W ][1] == -1 ? 0 : oldbuf[pos[W ][0]*life->w + pos[W ][1]]) +
-                (pos[E ][0] == -1 || pos[E ][1] == -1 ? 0 : oldbuf[pos[E ][0]*life->w + pos[E ][1]]) +
-                (pos[SW][0] == -1 || pos[SW][1] == -1 ? 0 : oldbuf[pos[SW][0]*life->w + pos[SW][1]]) +
-                (pos[S ][0] == -1 || pos[S ][1] == -1 ? 0 : oldbuf[pos[S ][0]*life->w + pos[S ][1]]) +
-                (pos[SE][0] == -1 || pos[SE][1] == -1 ? 0 : oldbuf[pos[SE][0]*life->w + pos[SE][1]]);
-            v = !!(1<<n & (oldbuf[i*life->w + j] ? life->stay_rule : life->born_rule));
-            av_dlog(ctx, "i:%d j:%d live_neighbors:%d cell:%d -> cell:%d\n", i, j, n, oldbuf[i*life->w + j], v);
-            newbuf[i*life->w+j] = v;
+            n = (pos[NW][0] == -1 || pos[NW][1] == -1 ? 0 : !oldbuf[pos[NW][0]*life->w + pos[NW][1]]) +
+                (pos[N ][0] == -1 || pos[N ][1] == -1 ? 0 : !oldbuf[pos[N ][0]*life->w + pos[N ][1]]) +
+                (pos[NE][0] == -1 || pos[NE][1] == -1 ? 0 : !oldbuf[pos[NE][0]*life->w + pos[NE][1]]) +
+                (pos[W ][0] == -1 || pos[W ][1] == -1 ? 0 : !oldbuf[pos[W ][0]*life->w + pos[W ][1]]) +
+                (pos[E ][0] == -1 || pos[E ][1] == -1 ? 0 : !oldbuf[pos[E ][0]*life->w + pos[E ][1]]) +
+                (pos[SW][0] == -1 || pos[SW][1] == -1 ? 0 : !oldbuf[pos[SW][0]*life->w + pos[SW][1]]) +
+                (pos[S ][0] == -1 || pos[S ][1] == -1 ? 0 : !oldbuf[pos[S ][0]*life->w + pos[S ][1]]) +
+                (pos[SE][0] == -1 || pos[SE][1] == -1 ? 0 : !oldbuf[pos[SE][0]*life->w + pos[SE][1]]);
+            v = 1<<n & (!oldbuf[i*life->w + j] ? life->stay_rule : life->born_rule);
+            *newbuf = v ? 0 : *newbuf + (*newbuf < 0xff);
+            av_dlog(ctx, "i:%d j:%d live_neighbors:%d cell:%d -> cell:%d\n", i, j, n, oldbuf[i*life->w + j], *newbuf);
+            newbuf++;
         }
     }
 
     life->buf_idx = !life->buf_idx;
 }
 
-static void fill_picture(AVFilterContext *ctx, AVFilterBufferRef *picref)
+static void fill_picture_monoblack(AVFilterContext *ctx, AVFilterBufferRef *picref)
 {
     LifeContext *life = ctx->priv;
     uint8_t *buf = life->buf[life->buf_idx];
@@ -350,7 +382,7 @@ static void fill_picture(AVFilterContext *ctx, AVFilterBufferRef *picref)
         uint8_t byte = 0;
         uint8_t *p = picref->data[0] + i * picref->linesize[0];
         for (k = 0, j = 0; j < life->w; j++) {
-            byte |= buf[i*life->w+j]<<(7-k++);
+            byte |= (!buf[i*life->w+j])<<(7-k++);
             if (k==8 || j == life->w-1) {
                 k = 0;
                 *p++ = byte;
@@ -360,6 +392,27 @@ static void fill_picture(AVFilterContext *ctx, AVFilterBufferRef *picref)
     }
 }
 
+static void fill_picture_rgb(AVFilterContext *ctx, AVFilterBufferRef *picref)
+{
+    LifeContext *life = ctx->priv;
+    uint8_t *buf = life->buf[life->buf_idx];
+    int i, j;
+
+    /* fill the output picture with the old grid buffer */
+    for (i = 0; i < life->h; i++) {
+        uint8_t *p = picref->data[0] + i * picref->linesize[0];
+        for (j = 0; j < life->w; j++) {
+            uint8_t death_age = buf[i*life->w + j];
+            uint8_t *c = death_age ? life->death_color : life->life_color;
+            if (!life->slow_death)
+                death_age = 0;
+            *p++ = FFMAX((int)c[0] - death_age, 0);
+            *p++ = FFMAX((int)c[1] - death_age, 0);
+            *p++ = FFMAX((int)c[2] - death_age, 0);
+        }
+    }
+}
+
 static int request_frame(AVFilterLink *outlink)
 {
     LifeContext *life = outlink->src->priv;
@@ -368,7 +421,7 @@ static int request_frame(AVFilterLink *outlink)
     picref->pts = life->pts++;
     picref->pos = -1;
 
-    fill_picture(outlink->src, picref);
+    life->draw(outlink->src, picref);
     evolve(outlink->src);
 #ifdef DEBUG
     show_life_grid(outlink->src);
@@ -384,7 +437,17 @@ static int request_frame(AVFilterLink *outlink)
 
 static int query_formats(AVFilterContext *ctx)
 {
-    static const enum PixelFormat pix_fmts[] = { PIX_FMT_MONOBLACK, PIX_FMT_NONE };
+    LifeContext *life = ctx->priv;
+    enum PixelFormat pix_fmts[] = { PIX_FMT_NONE, PIX_FMT_NONE };
+    if (   life->slow_death
+        || memcmp(life-> life_color, "\xff\xff\xff", 3)
+        || memcmp(life->death_color, "\x00\x00\x00", 3)) {
+        pix_fmts[0] = PIX_FMT_RGB24;
+        life->draw = fill_picture_rgb;
+    } else {
+        pix_fmts[0] = PIX_FMT_MONOBLACK;
+        life->draw = fill_picture_monoblack;
+    }
     avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
     return 0;
 }
-- 
1.7.8

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20111208/b8b1e2fa/attachment.asc>


More information about the ffmpeg-devel mailing list