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

Stefano Sabatini stefasab at gmail.com
Thu Dec 8 23:45:00 CET 2011


On date Thursday 2011-12-08 15:44:47 +0100, Clément Bœsch encoded:
> On Thu, Dec 08, 2011 at 02:50:58PM +0100, Stefano Sabatini wrote:
> [...]
> > > > > @@ -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).
> > 
> > What I mean is:
> > alive = 255
> > ...
> > dead  = 0
> > 
> > so you can interpret the values in the grid like "energy", which seems
> > the most natural interpretation, and so should lead to the most
> > readable implementation.
> 
> This makes impossible to just "not" the value as I said, but well OK. I
> introduced ALIVE_CELL for clarity. Despite what I thought it still looks
> neat (IMO) :)
> 
> -- 
> Clément B.

> From 79df9acc72b2f3198058e7ed6e43d8ec6164cb6d 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 |  100 ++++++++++++++++++++++++++++++++++++++---------
>  2 files changed, 97 insertions(+), 19 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.

Nit: specify the verb, these entries specify what an option *does*,
not what the thing the option sets *is*, so this should be "Set the color ..."

> +
> + 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.

idem

Note: an alternative implementation may consist in this: you define
death_color, and interpolate the current color according to this
equation:
color = death_color + (alive_color - death_color) * energy

energy is in [0, 1]

this would remove the arbitrary assumption for which the definitively
dead color is black.

>  @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:

nit: @file{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..c61ec57 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 0xFF (ALIVE_CELL) value means the cell is alive (or new born), while
> +     * the values from 0 to 0xFE means the cell is dead; the range of values is
> +     * used for the slow death effect (0xFE means dead, 0xFD means very dead,
> +     * 0xFC means very very dead... and 0x00 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,9 +60,16 @@ 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 ALIVE_CELL 0xFF
>  #define OFFSET(x) offsetof(LifeContext, x)
>  
>  static const AVOption life_options[] = {
> @@ -68,6 +85,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 +155,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] == ALIVE_CELL ? '@' : ' ';
>          line[j] = 0;
>          av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line);
>      }
> @@ -192,7 +212,7 @@ static int init_pattern_from_file(AVFilterContext *ctx)
>              if (*p == '\n') {
>                  p++; break;
>              } else
> -                life->buf[0][i*life->w + j] = !!isgraph(*(p++));
> +                life->buf[0][i*life->w + j] = !!isgraph(*(p++)) * ALIVE_CELL;
>          }
>      }
>      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;
>  
> @@ -252,13 +282,14 @@ 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;
> +                life->buf[0][i] = ALIVE_CELL;
>          }
>          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);

Is this required?

>      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]] == ALIVE_CELL) +
> +                (pos[N ][0] == -1 || pos[N ][1] == -1 ? 0 : oldbuf[pos[N ][0]*life->w + pos[N ][1]] == ALIVE_CELL) +
> +                (pos[NE][0] == -1 || pos[NE][1] == -1 ? 0 : oldbuf[pos[NE][0]*life->w + pos[NE][1]] == ALIVE_CELL) +
> +                (pos[W ][0] == -1 || pos[W ][1] == -1 ? 0 : oldbuf[pos[W ][0]*life->w + pos[W ][1]] == ALIVE_CELL) +
> +                (pos[E ][0] == -1 || pos[E ][1] == -1 ? 0 : oldbuf[pos[E ][0]*life->w + pos[E ][1]] == ALIVE_CELL) +
> +                (pos[SW][0] == -1 || pos[SW][1] == -1 ? 0 : oldbuf[pos[SW][0]*life->w + pos[SW][1]] == ALIVE_CELL) +
> +                (pos[S ][0] == -1 || pos[S ][1] == -1 ? 0 : oldbuf[pos[S ][0]*life->w + pos[S ][1]] == ALIVE_CELL) +
> +                (pos[SE][0] == -1 || pos[SE][1] == -1 ? 0 : oldbuf[pos[SE][0]*life->w + pos[SE][1]] == ALIVE_CELL);
> +            v = 1<<n & (oldbuf[i*life->w + j] == ALIVE_CELL ? life->stay_rule : life->born_rule);
> +            *newbuf = v ? ALIVE_CELL : *newbuf - (*newbuf != 0);
> +            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] == ALIVE_CELL)<<(7-k++);
>              if (k==8 || j == life->w-1) {
>                  k = 0;
>                  *p++ = byte;
> @@ -360,6 +392,26 @@ 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  v = buf[i*life->w + j];

> +            uint8_t *c = v == ALIVE_CELL ? life->life_color : life->death_color;
> +            int age = life->slow_death ? 0xff - v : 0;
> +            *p++ = FFMAX((int)c[0] - age, 0);
> +            *p++ = FFMAX((int)c[1] - age, 0);
> +            *p++ = FFMAX((int)c[2] - age, 0);

you may split the two alive/dead cases, and gain in speed and maybe
readability

[...]
-- 
FFmpeg = Frenzy and Frenzy Muttering Portable Egregious Gem


More information about the ffmpeg-devel mailing list