[MPlayer-dev-eng] [PATCH] convol (general convolution filter)

Adam M. Costello mplayer-dev-eng.amc+s0+ at nicemice.net
Mon Aug 3 00:56:02 CEST 2009


The attached patch adds a new filter, convol, which does general
convolution.  The scale filter already uses convolution for scaling
and image-format conversion, but provides very limited control over
the filter coefficients.  The convol filter does no scaling and no
image-format conversion but provides full control over the convolution
coefficients, with (optionally) separate filters for luma-x, luma-y,
chroma-x, and chroma-y.

The geq filter can do most of what convol can do (and more), but is
about 20x slower for similar operations and has a more cumbersome option
syntax.

I use convol for attenuating very noisy high-frequency "information" in
the luma channel of over-compressed MJPEG source material from a pocket
camera before recoding as H.264.

AMC
-------------- next part --------------
Index: libmpcodecs/vf_convol.c
===================================================================
--- libmpcodecs/vf_convol.c	(revision 0)
+++ libmpcodecs/vf_convol.c	(revision 0)
@@ -0,0 +1,889 @@
+/*
+
+Unlike vf_scale, which does scaling and image-format conversion using
+convolution filters but provides very limited control over the filter
+coefficients, vf_convol does no scaling and no image-format conversion
+but provides full control over the convolution coefficients.
+
+vf_geq can do most of what vf_convol does (and more), but is about 20x
+slower for similar operations.
+
+Note that convolution is a linear operation that really only makes
+sense when applied to linear samples, not gamma-encoded samples.  The
+correct way to apply convolution to luma/chroma samples would be to
+upsample the chroma, convert luma/chroma to RGB, gamma-decode the RGB
+samples, apply the convolution, gamma-encode the RGB samples, convert
+back to luma/chroma, and subsample the chroma.  But that doesn't seem
+very practical, and all the resampling would introduce errors, so the
+common practice is to apply the convolution directly to the luma and
+chroma channels and live with the resulting errors (like dark outlines
+appearing at sharp boundaries between complementary colors).
+
+*/
+
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "config.h"
+#include "img_format.h"  /* must be included before mp_image.h */
+#include "m_struct.h"
+#include "mp_image.h"
+#include "mp_msg.h"
+#include "vf.h"
+
+
+#if UINT_MAX < 0xFFFFFFFF
+#error Sorry, this code depends on int being at least 32 bits.
+#endif
+
+
+struct sequence_filter {
+  int *taps;     /* Filter coefficients. */
+  int num_taps;  /* Must be odd. */
+  int sum_taps;  /* Sum of taps, must be > 0. */
+};
+
+
+/* A channel is all samples of the same kind (R, G, B, Y, U, or V)   */
+/* in an image, regardless of whether the image is planar or packed. */
+
+struct channel_filter {
+  struct sequence_filter horizontal;  /* Filter for rows. */
+  struct sequence_filter vertical;    /* Filter for columns. */
+  int **buf;            /* Array of horizontally-filtered row buffers, */
+                        /* of which vertical.num_taps are non-NULL.    */
+  int *tmp_row;         /* Staging for an output row. */
+  int samples_per_row;  /* Length of each row buffer, >= horizontal.num_taps. */
+  int num_rows;         /* Length of buf array, >= vertical.num_taps. */
+  int max_raw_output;   /* 0xFF * horizontal.sum_taps * vertical.sum_taps. */
+                        /* Not-yet-normalized output is clamped to */
+                        /* [0..max_raw_output]. */
+  unsigned int normalize_factor;  /* Scales max_raw_output to 32-bit range. */
+
+  /* output = (unsigned_clamped_raw_output * normalize_factor) >> 24
+
+     We always round down, rather than rounding to the nearest integer,
+     because it's more important to preserve the correspondence between
+     0 and black, rather than the correspondence between 0xFF and white.
+     Black is a physical absolute, whereas white is an arbitrary maximum
+     allowed brightness.
+  */
+};
+
+struct vf_priv_s {
+  struct channel_filter non_chroma_filter;  /* For R, G, B, Y. */
+  struct channel_filter chroma_filter;      /* For U, V. */
+};
+
+
+static inline int dot_product(
+  int num_taps,
+  const int *taps,
+  const uint8_t *samples,
+  int sample_stride)
+{
+  int sum = 0, i;
+
+  for (i = 0;  i < num_taps;  ++i) {
+    sum += *samples * taps[i];
+    samples += sample_stride;
+  }
+
+  return sum;
+}
+
+
+static inline int min(int a, int b) {
+  return a >= b ? b : a;
+}
+
+static inline int max(int a, int b) {
+  return a >= b ? a : b;
+}
+
+
+/* Clamp sample to the range [0..sample_max]. */
+static inline int clamp(int sample, int sample_max) {
+  return min(sample_max, max(0, sample));
+}
+
+
+/* Apply a convolution filter to one row of one channel. */
+
+static void convolve_row(
+  const struct channel_filter *filter,
+  int sample_stride,
+  const uint8_t *in_row,
+  int *out_row)
+{
+  const uint8_t *in_sample;
+  int col, col_end;
+
+  /* We do not horizontally filter a few samples at the far left */
+  /* and right, where the filter would extend out of bounds.     */
+
+  int horizontal_margin = filter->horizontal.num_taps / 2;
+
+  /* Apply the identity filter to the first horizontal_margin samples. */
+  in_sample = in_row;
+  for (col = 0;  col < horizontal_margin;  ++col) {
+    out_row[col] = *in_sample * filter->horizontal.sum_taps;
+    in_sample += sample_stride;
+  }
+
+  /* Apply the horizontal filter. */
+  in_sample = in_row;
+  col_end = filter->samples_per_row - horizontal_margin;
+  for (;  col < col_end;  ++col) {
+    out_row[col] = dot_product(filter->horizontal.num_taps,
+                               filter->horizontal.taps,
+                               in_sample,
+                               sample_stride);
+    in_sample += sample_stride;
+  }
+
+  /* Apply the identity filter to the last horizontal_margin samples. */
+  in_sample += horizontal_margin * sample_stride;
+  for (;  col < filter->samples_per_row;  ++col) {
+    out_row[col] = *in_sample * filter->horizontal.sum_taps;
+    in_sample += sample_stride;
+  }
+}
+
+
+/* Copy a channel. */
+
+static void copy_channel(
+  int width,
+  int height,
+  int sample_stride,
+  int in_row_stride,
+  int out_row_stride,
+  const uint8_t *in_channel,
+  uint8_t *out_channel)
+{
+  const uint8_t *in_row, *in_sample;
+  uint8_t *out_row, *out_sample;
+  int row, col;
+
+  if (sample_stride == 1 &&
+      in_row_stride == out_row_stride &&
+      in_row_stride >= width &&
+      in_row_stride < width * 2 &&
+      in_row_stride - width < 64) {
+    memcpy(out_channel, in_channel, in_row_stride * height);
+    return;
+  }
+
+  for (row = 0, in_row = in_channel, out_row = out_channel;
+       row < height;
+       ++row, in_row += in_row_stride, out_row += out_row_stride) {
+    for (col = 0, in_sample = in_row, out_sample = out_row;
+         col < width;
+         ++col, in_sample += sample_stride, out_sample += sample_stride) {
+      *out_sample = *in_sample;
+    }
+  }
+}
+
+
+/* Apply a convolution filter to one channel. */
+
+static void convolve_channel(
+  const struct channel_filter *filter,
+  int sample_stride,
+  int in_row_stride,
+  int out_row_stride,
+  const uint8_t *in_channel,
+  uint8_t *out_channel)
+{
+  const uint8_t *in_row = in_channel;
+  uint8_t *out_row = out_channel;
+  int row, col, vertical_margin;
+
+  /* Optimization:  If the filter is a no-op, just copy the channel. */
+  if (filter->horizontal.num_taps == 1 && filter->vertical.num_taps == 1) {
+    copy_channel(filter->samples_per_row,
+                 filter->num_rows,
+                 sample_stride,
+                 in_row_stride,
+                 out_row_stride,
+                 in_channel,
+                 out_channel);
+    return;
+  }
+
+  /* We do not vertically filter a few rows at the top and */
+  /* bottom, where the filter would extend out of bounds.  */
+
+  vertical_margin = filter->vertical.num_taps / 2;
+
+  /* Fill up the horizontally-filtered row buffers. */
+
+  for (row = 0;  row < filter->vertical.num_taps;  ++row) {
+    convolve_row(filter, sample_stride, in_row, filter->buf[row]);
+    in_row += in_row_stride;
+  }
+
+  /* Output the top vertical_margin rows. */
+
+  for (row = 0;  row < vertical_margin;  ++row) {
+    uint8_t *out_sample = out_row;
+    const int *buf_row = filter->buf[row];
+
+    for (col = 0;  col < filter->samples_per_row;  ++col) {
+      unsigned int sample = clamp(buf_row[col] * filter->vertical.sum_taps,
+                                  filter->max_raw_output);
+      *out_sample = (sample * filter->normalize_factor) >> 24;
+      out_sample += sample_stride;
+    }
+
+    out_row += out_row_stride;
+  }
+
+  /* Main loop: Filter both horizontally and vertically. */
+
+  for (row = filter->vertical.num_taps;  ;  ++row) {
+    int buf_top, *buf_row, factor, r;
+    uint8_t *out_sample = out_row;
+
+    /* Vertically filter the row buffers to yield one output row. */
+    buf_top = row - filter->vertical.num_taps;
+    buf_row = filter->buf[buf_top];
+    factor = filter->vertical.taps[0];
+    for (col = 0;  col < filter->samples_per_row;  ++col) {
+      filter->tmp_row[col] = buf_row[col] * factor;
+    }
+    for (r = 1;  r < filter->vertical.num_taps;  ++r) {
+      buf_row = filter->buf[buf_top + r];
+      factor = filter->vertical.taps[r];
+      for (col = 0;  col < filter->samples_per_row;  ++col) {
+        filter->tmp_row[col] += buf_row[col] * factor;
+      }
+    }
+    for (col = 0;  col < filter->samples_per_row;  ++col) {
+      unsigned int sample = clamp(filter->tmp_row[col],
+                                  filter->max_raw_output);
+      *out_sample = (sample * filter->normalize_factor) >> 24;
+      out_sample += sample_stride;
+    }
+    out_row += out_row_stride;
+
+    if (row == filter->num_rows) break;
+
+    /* Recycle the top row buffer for a new horizontally filtered row. */
+    filter->buf[row] = filter->buf[row - filter->vertical.num_taps];
+    filter->buf[row - filter->vertical.num_taps] = NULL;
+    convolve_row(filter, sample_stride, in_row, filter->buf[row]);
+    in_row += in_row_stride;
+  }
+
+  /* Output the bottom vertical_margin rows. */
+
+  for (row = filter->num_rows - vertical_margin;
+       row < filter->num_rows;
+       ++row) {
+    uint8_t *out_sample = out_row;
+    int *buf_row = filter->buf[row];
+
+    for (col = 0;  col < filter->samples_per_row;  ++col) {
+      unsigned int sample = clamp(buf_row[col] * filter->vertical.sum_taps,
+                                  filter->max_raw_output);
+      *out_sample = (sample * filter->normalize_factor) >> 24;
+      out_sample += sample_stride;
+    }
+
+    out_row += out_row_stride;
+  }
+
+  /* Move the row buffers back to the top. */
+
+  for (row = 0;  row < filter->vertical.num_taps;  ++row) {
+    int from_row = row + filter->num_rows - filter->vertical.num_taps;
+    int *buf_row = filter->buf[from_row];
+    filter->buf[from_row] = NULL;
+    filter->buf[row] = buf_row;
+  }
+}
+
+
+static void init_filter_buffers(
+  struct channel_filter *filter,
+  int width,
+  int height)
+{
+  int row;
+  filter->samples_per_row = width;
+  filter->num_rows = height;
+  filter->tmp_row = malloc(width * sizeof filter->tmp_row[0]);
+  filter->buf = malloc(height * sizeof filter->buf[0]);
+
+  /* If the image is shorter/narrower than the filter, the unfiltered      */
+  /* margins occupy the entire image, and the filter does not apply.       */
+  /* Rather than make the filter code handle this corner case, it's easier */
+  /* to just change the filter to be one-tap, which has the same effect.   */
+
+  if (width < filter->horizontal.num_taps) {
+    filter->horizontal.num_taps = 1;
+    filter->horizontal.taps[0] = filter->horizontal.sum_taps;
+  }
+  if (height < filter->vertical.num_taps) {
+    filter->vertical.num_taps = 1;
+    filter->vertical.taps[0] = filter->vertical.sum_taps;
+  }
+
+  for (row = 0;  row < filter->vertical.num_taps;  ++row) {
+    filter->buf[row] = malloc(width * sizeof filter->buf[0][0]);
+  }
+  for (;  row < height;  ++row) {
+    filter->buf[row] = NULL;
+  }
+}
+
+
+static int config(
+  struct vf_instance_s *vf,
+  int width,
+  int height,
+  int d_width,
+  int d_height,
+  unsigned int flags,
+  unsigned int outfmt)
+{
+  int chroma_width, chroma_height;
+  mp_image_t mpi;
+
+  mpi.width = width;
+  mpi.height = height;
+  mpi.chroma_width = mpi.chroma_height = 0;
+  mp_image_setfmt(&mpi, outfmt);
+
+  /* The following calculation of chroma_width and chroma_height */
+  /* needs to be kept consistent with put_image().               */
+
+  /* Defaults appropriate for planar YCC: */
+  chroma_width = mpi.chroma_width;
+  chroma_height = mpi.chroma_height;
+
+  /* Exceptions: */
+  if (IMGFMT_IS_RGB(outfmt) || IMGFMT_IS_BGR(outfmt)) {
+    /* no chroma channels */
+    chroma_width = chroma_height = 0;
+  }
+  else switch (outfmt) {
+    case IMGFMT_UYVY:
+    case IMGFMT_YUY2:
+      /* mpi.chroma_width,height are not set for packed formats. */
+      chroma_width = width / 2;
+      chroma_height = height;
+      break;
+
+    case IMGFMT_NV12:
+    case IMGFMT_NV21:
+      chroma_width /= 2;
+      break;
+  }
+
+  init_filter_buffers(&vf->priv->non_chroma_filter, width, height);
+  init_filter_buffers(&vf->priv->chroma_filter, chroma_width, chroma_height);
+  return vf_next_config(vf, width, height, d_width, d_height, flags, outfmt);
+}
+
+
+static void set_min(int *out, int upper_bound) {
+  if (*out > upper_bound) *out = upper_bound;
+}
+
+
+static int put_image(struct vf_instance_s *vf, mp_image_t *mpi, double pts)
+{
+  int channel;
+  int imgfmt = mpi->imgfmt;
+  int width = mpi->width;
+  int height = mpi->height;
+  struct channel_filter *non_chroma_filter = &vf->priv->non_chroma_filter,
+                        *chroma_filter = &vf->priv->chroma_filter;
+  int non_chroma_buf_width = non_chroma_filter->samples_per_row;
+  int non_chroma_buf_height = non_chroma_filter->num_rows;
+  int chroma_buf_width = chroma_filter->samples_per_row;
+  int chroma_buf_height = chroma_filter->num_rows;
+
+  mp_image_t *dmpi = vf_get_image(vf->next,
+                                  imgfmt,
+                                  MP_IMGTYPE_TEMP,
+                                  MP_IMGFLAG_ACCEPT_STRIDE,
+                                  width,
+                                  height);
+
+  /* Only one-byte samples are supported, but we try to support as */
+  /* many arrangements of them as possible.  All we need to know   */
+  /* is whether the samples are one byte each, and which samples   */
+  /* are chroma samples, but there seems to be no reliable way of  */
+  /* determining that other than enumerating the image formats.    */
+
+  /* Defaults appropriate for planar YCC: */
+  const uint8_t *in_channel[3] =
+      { mpi->planes[0], mpi->planes[1], mpi->planes[2] };
+  uint8_t *out_channel[3] =
+      { dmpi->planes[0], dmpi->planes[1], dmpi->planes[2] };
+  int in_row_stride[3] = { mpi->stride[0], mpi->stride[1], mpi->stride[2] };
+  int out_row_stride[3] = { dmpi->stride[0], dmpi->stride[1], dmpi->stride[2] };
+  int sample_stride[3] = { 1, 1, 1 };
+  int is_chroma[3] = { 0, 1, 1 };
+  int chroma_width = mpi->chroma_width;
+  int chroma_height = mpi->chroma_height;
+
+  if (mpi->bpp == 0 || mpi->num_planes >= 4) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_FATAL,
+           "convol: Unsupported image format: %s\n",
+           vo_format_name(imgfmt));
+    return 0;
+  }
+  else if (IMGFMT_IS_RGB(imgfmt) || IMGFMT_IS_BGR(imgfmt)) {
+    if (mpi->bpp != 24) {
+      mp_msg(MSGT_VFILTER,
+             MSGL_FATAL,
+             "convol: Requires one-byte samples, cannot handle %s\n",
+             vo_format_name(imgfmt));
+      return 0;
+    }
+    /* packed, three non-chroma channels */
+    in_channel[1] = in_channel[0] + 1;
+    in_channel[2] = in_channel[0] + 2;
+    out_channel[1] = out_channel[0] + 1;
+    out_channel[2] = out_channel[0] + 2;
+    in_row_stride[1] = in_row_stride[2] = in_row_stride[0];
+    out_row_stride[1] = out_row_stride[2] = out_row_stride[0];
+    sample_stride[0] = sample_stride[1] = sample_stride[2] = 3;
+    is_chroma[0] = is_chroma[1] = is_chroma[2] = 0;
+    chroma_width = chroma_height = 0;
+  }
+  else switch (imgfmt) {
+    /* planar YCC */
+    case IMGFMT_I420:
+    case IMGFMT_IYUV:
+    case IMGFMT_YV12:
+    case IMGFMT_YVU9:
+    case IMGFMT_444P:
+    case IMGFMT_422P:
+    case IMGFMT_411P:
+      break;
+
+    /* grayscale */
+    case IMGFMT_Y800:
+    case IMGFMT_Y8:
+      in_channel[1] = in_channel[2] = NULL;
+      out_channel[1] = out_channel[2] = NULL;
+      break;
+
+    /* packed CYCY */
+    case IMGFMT_UYVY:
+      ++in_channel[0];
+      ++out_channel[0];
+      sample_stride[0] = 2;
+      in_channel[1] = in_channel[0] - 1;
+      in_channel[2] = in_channel[0] + 1;
+      out_channel[1] = out_channel[0] - 1;
+      out_channel[2] = out_channel[0] + 1;
+      in_row_stride[1] = in_row_stride[2] = in_row_stride[0];
+      out_row_stride[1] = out_row_stride[2] = out_row_stride[0];
+      sample_stride[1] = sample_stride[2] = 4;
+      chroma_width = width / 2;
+      chroma_height = height;
+      break;
+
+    /* packed YCYC */
+    case IMGFMT_YUY2:
+      sample_stride[0] = 2;
+      in_channel[1] = in_channel[0] + 1;
+      in_channel[2] = in_channel[0] + 3;
+      out_channel[1] = out_channel[0] + 1;
+      out_channel[2] = out_channel[0] + 3;
+      in_row_stride[1] = in_row_stride[2] = in_row_stride[0];
+      out_row_stride[1] = out_row_stride[2] = out_row_stride[0];
+      sample_stride[1] = sample_stride[2] = 4;
+      chroma_width = width / 2;
+      chroma_height = height;
+      break;
+
+    /* hybrid: Y-plane, packed-CC-plane */
+    case IMGFMT_NV12:
+    case IMGFMT_NV21:
+      in_channel[2] = in_channel[1] + 1;
+      out_channel[2] = out_channel[1] + 1;
+      in_row_stride[2] = in_row_stride[1];
+      out_row_stride[2] = out_row_stride[1];
+      sample_stride[1] = sample_stride[2] = 2;
+      chroma_width /= 2;
+      break;
+
+    default:
+      mp_msg(MSGT_VFILTER,
+             MSGL_FATAL,
+             "convol: Unsupported image format: %s\n",
+             vo_format_name(imgfmt));
+      return 0;
+  }
+
+  /* If the image dimensions are greater than the configured          */
+  /* dimensions, the excess is presumably padding containing garbage  */
+  /* that shouldn't infect the real image, so we should exclude it    */
+  /* from the filter (and we have to anyway, because the intermediate */
+  /* buffers are only big enough for the configured dimensions).  If  */
+  /* the image dimensions are less than the configured dimensions,    */
+  /* obviously we cannot filter parts of the image that aren't there. */
+  /* The upshot is we use the minimum of the configured dimensions    */
+  /* and the actual dimensions.  Note we already saved a copy of the  */
+  /* configured dimensions.                                           */
+
+  set_min(&non_chroma_filter->samples_per_row, width);
+  set_min(&non_chroma_filter->num_rows, height);
+  set_min(&chroma_filter->samples_per_row, chroma_width);
+  set_min(&chroma_filter->num_rows, chroma_height);
+
+  /* Optimization:  If the chroma channels are just being copied, */
+  /* and they are interleaved in the same plane, copy them in one */
+  /* pass rather than two.                                        */
+
+  if (is_chroma[1] && is_chroma[2] && sample_stride[1] == sample_stride[2] &&
+      in_channel[2] == in_channel[1] + sample_stride[1] / 2 &&
+      chroma_filter->horizontal.num_taps == 1 &&
+      chroma_filter->vertical.num_taps == 1) {
+    in_channel[2] = out_channel[2] = NULL;
+    sample_stride[1] /= 2;
+    chroma_width *= 2;
+    chroma_filter->samples_per_row = chroma_width;
+  }
+
+  for (channel = 0;  channel < 3;  ++channel) {
+    if (out_channel[channel] == NULL) continue;
+    convolve_channel(is_chroma[channel] ? chroma_filter : non_chroma_filter,
+                     sample_stride[channel],
+                     in_row_stride[channel],
+                     out_row_stride[channel],
+                     in_channel[channel],
+                     out_channel[channel]);
+  }
+
+  /* Restore the configured dimensions. */
+  non_chroma_filter->samples_per_row = non_chroma_buf_width;
+  non_chroma_filter->num_rows = non_chroma_buf_height;
+  chroma_filter->samples_per_row = chroma_buf_width;
+  chroma_filter->num_rows = chroma_buf_height;
+
+  return vf_next_put_image(vf, dmpi, pts);
+}
+
+
+static int query_format(struct vf_instance_s *vf, unsigned int fmt)
+{
+  int supported = 1;
+  mp_image_t mpi;
+  mp_image_setfmt(&mpi, fmt);
+
+  /* The following calculation of supported formats */
+  /* needs to be kept consistent with put_image().  */
+
+  if (mpi.bpp == 0 || mpi.num_planes >= 4) {
+    supported = 0;
+  }
+  else if (IMGFMT_IS_RGB(fmt) || IMGFMT_IS_BGR(fmt)) {
+    if (mpi.bpp != 24) supported = 0;
+  }
+  else switch (fmt) {
+    case IMGFMT_I420:
+    case IMGFMT_IYUV:
+    case IMGFMT_YV12:
+    case IMGFMT_YVU9:
+    case IMGFMT_444P:
+    case IMGFMT_422P:
+    case IMGFMT_411P:
+    case IMGFMT_Y800:
+    case IMGFMT_Y8:
+    case IMGFMT_UYVY:
+    case IMGFMT_YUY2:
+    case IMGFMT_NV12:
+    case IMGFMT_NV21:
+      break;
+
+    default:
+      supported = 0;
+  }
+
+  if (!supported) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_INFO,
+           "convol: Unsupported image format: %s\n",
+           vo_format_name(fmt));
+    return 0;
+  }
+
+  return vf_next_query_format(vf, fmt);
+}
+
+
+static void destroy_filter(struct channel_filter *filter)
+{
+  int row;
+  for (row = 0;  row < filter->vertical.num_taps;  ++row) {
+    free(filter->buf[row]);
+  }
+  free(filter->buf);
+  filter->buf = NULL;
+  free(filter->tmp_row);
+  filter->tmp_row = NULL;
+  free(filter->horizontal.taps);
+  filter->horizontal.taps = NULL;
+  free(filter->vertical.taps);
+  filter->vertical.taps = NULL;
+}
+
+
+static void uninit(struct vf_instance_s* vf)
+{
+  destroy_filter(&vf->priv->non_chroma_filter);
+  destroy_filter(&vf->priv->chroma_filter);
+  free(vf->priv);
+}
+
+
+/* Initialize a sequence filter from a text specification, which is  */
+/* a sequence of coefficients, each of which is a plus or minus sign */
+/* followed by one or more decimal digits.  Returns 0 on failure.    */
+
+static int init_sequence_filter(
+  struct sequence_filter *filter, 
+  const char *spec,
+  const char *spec_end)
+{
+  int num_taps, i;
+  const char *p;
+
+  if (filter->taps != NULL) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_FATAL,
+           "convol: Cannot specify the same filter more than once.\n");
+    return 0;
+  }
+
+  for (num_taps = 0, p = spec;  p < spec_end;  ++p) {
+    if (*p == '+' || *p == '-') ++num_taps;
+  }
+
+  if (num_taps % 2 == 0) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_FATAL,
+           "convol: Each filter must have an odd number of coefficients.\n");
+    return 0;
+  }
+
+  filter->num_taps = num_taps;
+  filter->taps = malloc(num_taps * sizeof filter->taps[0]);
+  filter->sum_taps = 0;
+
+  for (p = spec, i = 0;  i < num_taps;  ++i) {
+    int num_fields, field_width;
+    if (p >= spec_end || (*p != '+' && *p != '-')) break;
+    num_fields = sscanf(p, "%d%n", filter->taps + i, &field_width);
+    if (num_fields != 1) break;
+    filter->sum_taps += filter->taps[i];
+    p += field_width;
+  }
+
+  if (p != spec_end || i != num_taps) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_FATAL,
+           "convol: Parse error in filter spec: %.*s\n",
+           spec_end - spec,
+           spec);
+    return 0;
+  }
+
+  if (filter->sum_taps <= 0) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_FATAL,
+           "convol: Each filter's coefficients must have a positive sum.\n");
+    return 0;
+  }
+
+  return 1;
+}
+
+
+/* Assumes the sequence filters have already been    */
+/* intialized.  Does not allocate the buffers        */
+/* because the channel dimensions are not yet known. */
+
+static void init_channel_filter(struct channel_filter *filter) {
+  int i, sum_pos = 0, sum_neg = 0, horizontal_max, vertical_max;
+
+  for (i = 0;  i < filter->horizontal.num_taps;  ++i) {
+    int c = filter->horizontal.taps[i];
+    if (c >= 0) sum_pos += c;
+    else sum_neg += c;
+  }
+
+  horizontal_max = max(sum_pos, -sum_neg);
+
+  for (i = 0;  i < filter->vertical.num_taps;  ++i) {
+    int c = filter->vertical.taps[i];
+    if (c >= 0) sum_pos += c;
+    else sum_neg += c;
+  }
+
+  vertical_max = max(sum_pos, -sum_neg);
+
+  if (0x7FFFFFFF / 0xFF / horizontal_max < vertical_max) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_WARN,
+           "convol: Filter coefficients are too large, may cause overflow.\n");
+  }
+
+  filter->buf = NULL;
+  filter->tmp_row = NULL;
+  filter->samples_per_row = 0;
+  filter->num_rows = 0;
+  filter->max_raw_output =
+      0xFF * filter->horizontal.sum_taps * filter->vertical.sum_taps;
+  filter->normalize_factor = 0xFFFFFFFF / filter->max_raw_output;
+
+  if (filter->normalize_factor < 512) {
+    mp_msg(MSGT_VFILTER,
+           MSGL_WARN,
+           "convol: Filter coefficients are too large, "
+           "may cause significant roundoff error.\n");
+  }
+}
+
+
+static int open(vf_instance_t *vf, char *args)
+{
+  int args_len = strlen(args), status;
+  const char *args_end = args + args_len, *arg;
+  const char *default_value = "+1";
+  const char *default_value_end = default_value + strlen(default_value);
+  struct vf_priv_s *priv;
+  struct sequence_filter *non_chroma_h, *non_chroma_v, *chroma_h, *chroma_v;
+
+  vf->query_format = query_format;
+  vf->config = config;
+  vf->put_image = put_image;
+  vf->uninit = uninit;
+
+  priv = malloc(sizeof (struct vf_priv_s));
+  priv->non_chroma_filter.horizontal.taps = NULL;
+  priv->non_chroma_filter.vertical.taps = NULL;
+  priv->chroma_filter.horizontal.taps = NULL;
+  priv->chroma_filter.vertical.taps = NULL;
+  vf->priv = priv;
+
+  for (arg = args;  arg < args_end; ) {
+    int name_len;
+    const char *value;
+    const char *arg_end = strchr(arg, ':');
+    if (arg_end == NULL) arg_end = args_end;
+    value = strchr(arg, '=');
+
+    if (value != NULL && value < arg_end) {
+      name_len = value - arg;
+      ++value;
+    }
+    else {
+      name_len = 0;
+      value = arg;
+    }
+
+    non_chroma_h = &priv->non_chroma_filter.horizontal;
+    non_chroma_v = &priv->non_chroma_filter.vertical;
+    chroma_h = &priv->chroma_filter.horizontal;
+    chroma_v = &priv->chroma_filter.vertical;
+
+    if (name_len == 0) {
+      status =
+          init_sequence_filter(non_chroma_h, value, arg_end) &&
+          init_sequence_filter(non_chroma_v, value, arg_end) &&
+          init_sequence_filter(chroma_h, value, arg_end) &&
+          init_sequence_filter(chroma_v, value, arg_end);
+    }
+    else if (name_len == 1 && strncmp(arg, "x", name_len) == 0) {
+      status =
+          init_sequence_filter(non_chroma_h, value, arg_end) &&
+          init_sequence_filter(chroma_h, value, arg_end);
+    }
+    else if (name_len == 1 && strncmp(arg, "y", name_len) == 0) {
+      status =
+          init_sequence_filter(non_chroma_v, value, arg_end) &&
+          init_sequence_filter(chroma_v, value, arg_end);
+    }
+    else if (name_len == 4 && strncmp(arg, "luma", name_len) == 0) {
+      status =
+          init_sequence_filter(non_chroma_h, value, arg_end) &&
+          init_sequence_filter(non_chroma_v, value, arg_end);
+    }
+    else if (name_len == 6 && strncmp(arg, "chroma", name_len) == 0) {
+      status =
+          init_sequence_filter(chroma_h, value, arg_end) &&
+          init_sequence_filter(chroma_v, value, arg_end);
+    }
+    else if (name_len == 6 && strncmp(arg, "luma-x", name_len) == 0) {
+      status = init_sequence_filter(non_chroma_h, value, arg_end);
+    }
+    else if (name_len == 6 && strncmp(arg, "luma-y", name_len) == 0) {
+      status = init_sequence_filter(non_chroma_v, value, arg_end);
+    }
+    else if (name_len == 8 && strncmp(arg, "chroma-x", name_len) == 0) {
+      status = init_sequence_filter(chroma_h, value, arg_end);
+    }
+    else if (name_len == 8 && strncmp(arg, "chroma-y", name_len) == 0) {
+      status = init_sequence_filter(chroma_v, value, arg_end);
+    }
+    else {
+      mp_msg(MSGT_VFILTER,
+             MSGL_FATAL,
+             "convol: Unrecognized parameter: %.*s\n",
+             name_len,
+             arg);
+      status = 0;
+    }
+
+    if (!status) return 0;
+    arg = arg_end + 1;
+  }
+
+  if (priv->non_chroma_filter.horizontal.taps == NULL) {
+    init_sequence_filter(&priv->non_chroma_filter.horizontal,
+                         default_value,
+                         default_value_end);
+  }
+  if (priv->non_chroma_filter.vertical.taps == NULL) {
+    init_sequence_filter(&priv->non_chroma_filter.vertical,
+                         default_value,
+                         default_value_end);
+  }
+  if (priv->chroma_filter.horizontal.taps == NULL) {
+    init_sequence_filter(&priv->chroma_filter.horizontal,
+                         default_value,
+                         default_value_end);
+  }
+  if (priv->chroma_filter.vertical.taps == NULL) {
+    init_sequence_filter(&priv->chroma_filter.vertical,
+                         default_value,
+                         default_value_end);
+  }
+
+  init_channel_filter(&priv->non_chroma_filter);
+  init_channel_filter(&priv->chroma_filter);
+  return 1;
+}
+
+
+const vf_info_t vf_info_convol = {
+  "general convolution filter",
+  "convol",
+  "Adam M. Costello http://www.nicemice.net/amc/",
+  "",
+  open,
+  NULL,
+};
Index: libmpcodecs/vf.c
===================================================================
--- libmpcodecs/vf.c	(revision 29464)
+++ libmpcodecs/vf.c	(working copy)
@@ -100,6 +100,7 @@
 extern const vf_info_t vf_info_blackframe;
 extern const vf_info_t vf_info_geq;
 extern const vf_info_t vf_info_ow;
+extern const vf_info_t vf_info_convol;
 
 // list of available filters:
 static const vf_info_t* const filter_list[]={
@@ -193,6 +194,7 @@
     &vf_info_yadif,
     &vf_info_blackframe,
     &vf_info_ow,
+    &vf_info_convol,
     NULL
 };
 
Index: AUTHORS
===================================================================
--- AUTHORS	(revision 29464)
+++ AUTHORS	(working copy)
@@ -184,6 +184,9 @@
     * dvdnav hacks
     * rawdv demuxer fixes
 
+Costello, Adam <http://www.nicemice.net/amc/>
+    * convol filter
+
 Curry, Alan (pacman, tcsetattr) <pacman at TheWorld.com>
     * swscale AltiVec/big-endian fixes
     * misc vo_fbdev fixes
Index: Makefile
===================================================================
--- Makefile	(revision 29464)
+++ Makefile	(working copy)
@@ -405,6 +405,7 @@
               libmpcodecs/vf_2xsai.c \
               libmpcodecs/vf_blackframe.c \
               libmpcodecs/vf_boxblur.c \
+              libmpcodecs/vf_convol.c \
               libmpcodecs/vf_crop.c \
               libmpcodecs/vf_cropdetect.c \
               libmpcodecs/vf_decimate.c \
Index: DOCS/man/en/mplayer.1
===================================================================
--- DOCS/man/en/mplayer.1	(revision 29464)
+++ DOCS/man/en/mplayer.1	(working copy)
@@ -6224,6 +6224,43 @@
 .RE
 .
 .TP
+.B convol=filter[:filter...]
+General convolution filter.  There are four independent filters:
+horizontal chroma, vertical chroma, horizontal non-chroma, and vertical
+non-chroma.  The non-chroma filters apply to the Y or R,G,B channels,
+and the chroma filters apply to the U,V channels (if present).  Each
+row or column of each channel is convolved with the specified filter
+coefficients and divided by the sum of the coefficients (so that the
+average sample value is unchanged).
+.RSs
+.IPs <filter>
+An odd number of decimal integer filter coefficients whose sum
+is positive.  Each integer must have a sign, either - or +. The
+coefficients can optionally be preceded by <name>=.  If <name>= is
+omitted, the same coefficients are used for all four filters.  If <name>
+is luma-x, luma-y, chroma-x, or chroma-y, the coefficients are used for
+only one filter (here "luma" means non-chroma).  If <name> is luma,
+chroma, x, or y, the coefficients are used for two filters.  Multiple
+filter specifications can be combined if they don't overlap.  Any
+filters left unspecified default to +1 (the no-effect filter).
+.RE
+.sp 1
+.RS
+.I EXAMPLE:
+.RE
+.PD 0
+.RSs
+.IPs "-vf convol=luma=-1+4+26+4-1:chroma-y=-1+4+10+4-1"
+Mildly blur the luma channel, and strongly blur the chroma channels
+vertically (but not horizontally).  [Tip: -1+4+a+4-1 where a >= 10 is a
+good low-pass filter that attenuates the highest frequencies by a factor
+of (a-10)/(a+6).]
+.IPs "-vf convol=-1+6-1"
+Sharpen all channels.
+.RE
+.PD 1
+.
+.TP
 .B "test\ \ \ "
 Generate various test patterns.
 .


More information about the MPlayer-dev-eng mailing list