aasink & cacasink: add filter aatv & cacatv
authorEric Marks <bigmarkslp@gmail.com>
Sat, 28 Dec 2019 23:01:19 +0000 (23:01 +0000)
committerNicolas Dufresne <nicolas@ndufresne.ca>
Sat, 28 Dec 2019 23:01:19 +0000 (23:01 +0000)
Add transform filter capabilities to aasink and cacasink in the form of new elements aatv and cacatv.

ext/aalib/gstaasink.c
ext/aalib/gstaatv.c [new file with mode: 0644]
ext/aalib/gstaatv.h [new file with mode: 0644]
ext/aalib/meson.build
ext/libcaca/gstcacasink.c
ext/libcaca/gstcacatv.c [new file with mode: 0644]
ext/libcaca/gstcacatv.h [new file with mode: 0644]
ext/libcaca/meson.build

index ba21633..18d361f 100644 (file)
@@ -42,6 +42,7 @@
 
 #include <gst/video/gstvideometa.h>
 #include "gstaasink.h"
+#include "gstaatv.h"
 
 /* aasink signals and args */
 enum
@@ -585,11 +586,14 @@ plugin_init (GstPlugin * plugin)
   if (!gst_element_register (plugin, "aasink", GST_RANK_NONE, GST_TYPE_AASINK))
     return FALSE;
 
+  if (!gst_element_register (plugin, "aatv", GST_RANK_NONE, GST_TYPE_AATV))
+    return FALSE;
+
   return TRUE;
 }
 
 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
     GST_VERSION_MINOR,
     aasink,
-    "ASCII Art video sink",
+    "ASCII Art video sink & filter",
     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
diff --git a/ext/aalib/gstaatv.c b/ext/aalib/gstaatv.c
new file mode 100644 (file)
index 0000000..247af83
--- /dev/null
@@ -0,0 +1,978 @@
+/* GStreamer
+ * Copyright (C) <2019> Eric Marks <bigmarkslp@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/**
+ * SECTION:element-aatv
+ * @see_also: #GstAASink
+ *
+ * Transforms video into ascii art.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch-1.0 videotestsrc ! aatv ! videoconvert ! autovideosink
+ * ]| This pipeline shows the effect of aatv on a test stream.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstaatv.h"
+#include <string.h>
+#include <stdlib.h>
+
+#define PROP_AATV_COLOR_TEXT_DEFAULT        0xffffffff  /* White */
+#define PROP_AATV_COLOR_BACKGROUND_DEFAULT  0xff000000  /* Black */
+#define PROP_AATV_COLOR_RAIN_DEFAULT        0xff00ff00  /* Green */
+#define PROP_AATV_RAIN_MODE_DEFAULT         GST_RAIN_OFF
+#define PROP_BRIGHTNESS_TARGET_MIN_DEFAULT  0.3
+#define PROP_BRIGHTNESS_TARGET_MAX_DEFAULT  0.4
+#define PROP_RAIN_SPAWN_DEFAULT             0.2
+#define PROP_RAIN_DELAY_MIN_DEFAULT         0
+#define PROP_RAIN_DELAY_MAX_DEFAULT         3
+#define PROP_RAIN_LENGTH_MIN_DEFAULT        4
+#define PROP_RAIN_LENGTH_MAX_DEFAULT        30
+
+/* aatv signals and args */
+enum
+{
+  LAST_SIGNAL
+};
+
+#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
+
+enum
+{
+  PROP_0,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_DITHER,
+  PROP_FONT,
+  PROP_CONTRAST,
+  PROP_GAMMA,
+  PROP_RANDOMVAL,
+  PROP_BRIGHTNESS_AUTO,
+  PROP_BRIGHTNESS_ACTUAL,
+  PROP_BRIGHTNESS,
+  PROP_BRIGHTNESS_TARGET_MIN,
+  PROP_BRIGHTNESS_TARGET_MAX,
+  PROP_COLOR_BACKGROUND,
+  PROP_COLOR_TEXT,
+  PROP_COLOR_TEXT_BOLD,
+  PROP_COLOR_TEXT_NORMAL,
+  PROP_COLOR_TEXT_DIM,
+  PROP_COLOR_RAIN,
+  PROP_COLOR_RAIN_BOLD,
+  PROP_COLOR_RAIN_NORMAL,
+  PROP_COLOR_RAIN_DIM,
+  PROP_RAIN_MODE,
+  PROP_RAIN_SPAWN_RATE,
+  PROP_RAIN_DELAY_MIN,
+  PROP_RAIN_DELAY_MAX,
+  PROP_RAIN_LENGTH_MIN,
+  PROP_RAIN_LENGTH_MAX
+};
+
+static GstStaticPadTemplate sink_template_tv = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ I420 }"))
+    );
+static GstStaticPadTemplate src_template_tv = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ RGBA }"))
+    );
+
+static void gst_aatv_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_aatv_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+#define GST_TYPE_AATV_RAIN_MODE (gst_aatv_rain_mode_get_type())
+
+static GType
+gst_aatv_rain_mode_get_type (void)
+{
+  static GType rain_mode = 0;
+
+  static const GEnumValue rain_modes[] = {
+    {GST_RAIN_OFF, "No Rain", "none"},
+    {GST_RAIN_DOWN, "Rain Down", "down"},
+    {GST_RAIN_UP, "Rain Up", "up"},
+    {GST_RAIN_LEFT, "Rain Left", "left"},
+    {GST_RAIN_RIGHT, "Rain Right", "right"},
+    {0, NULL, NULL},
+  };
+
+  if (!rain_mode) {
+    rain_mode = g_enum_register_static ("GstAATvRainModes", rain_modes);
+  }
+  return rain_mode;
+}
+
+#define gst_aatv_parent_class parent_class
+G_DEFINE_TYPE (GstAATv, gst_aatv, GST_TYPE_VIDEO_FILTER);
+
+static void
+gst_aatv_scale (GstAATv * aatv, guchar * src, guchar * dest,
+    gint sw, gint sh, gint ss, gint dw, gint dh)
+{
+  gint ypos, yinc, y;
+  gint xpos, xinc, x;
+
+  g_return_if_fail ((dw != 0) && (dh != 0));
+
+  ypos = 0x10000;
+  yinc = (sh << 16) / dh;
+  xinc = (sw << 16) / dw;
+
+  for (y = dh; y; y--) {
+    while (ypos > 0x10000) {
+      ypos -= 0x10000;
+      src += ss;
+    }
+
+    xpos = 0x10000;
+
+    {
+      guchar *destp = dest;
+      guchar *srcp = src;
+
+      for (x = dw; x; x--) {
+        while (xpos >= 0x10000L) {
+          srcp++;
+          xpos -= 0x10000L;
+        }
+        *destp++ = *srcp;
+        xpos += xinc;
+      }
+    }
+    dest += dw;
+    ypos += yinc;
+  }
+}
+
+static void
+gst_aatv_rain (GstAATv * aatv)
+{
+  gint i;
+  gboolean obstructed;
+
+  GstAATvDroplet *raindrops = aatv->raindrops;
+
+  for (i = 0; i < aatv->rain_width; i++) {
+    if (raindrops[i].enabled == FALSE) {
+      if (g_random_double () < aatv->rain_spawn_rate) {
+
+        obstructed = FALSE;
+
+        /* Don't let adjacent lines be enabled at the same time. */
+        if (i > 0)
+          if (raindrops[i - 1].enabled == TRUE)
+            if (raindrops[i - 1].location - raindrops[i - 1].length <
+                aatv->rain_height / 4)
+              obstructed = TRUE;
+
+        if (i < aatv->rain_width)
+          if (raindrops[i + 1].enabled == TRUE)
+            if (raindrops[i + 1].location - raindrops[i + 1].length <
+                aatv->rain_height / 4)
+              obstructed = TRUE;
+
+        if (obstructed == FALSE) {
+          raindrops[i].location = 0;
+          raindrops[i].length =
+              g_random_int_range (aatv->rain_length_min, aatv->rain_length_max);
+          raindrops[i].delay =
+              g_random_int_range (aatv->rain_delay_min, aatv->rain_delay_max);
+          raindrops[i].delay_counter = 0;
+          raindrops[i].enabled = TRUE;
+        }
+      }
+    } else {
+      raindrops[i].delay_counter++;
+      if (raindrops[i].delay_counter > raindrops[i].delay) {
+        raindrops[i].delay_counter = 0;
+        raindrops[i].location++;
+      }
+      if (raindrops[i].location - raindrops[i].length > aatv->rain_height) {
+        raindrops[i].enabled = FALSE;
+      }
+    }
+  }
+}
+
+static void
+gst_aatv_render (GstAATv * aatv, gint32 * dest)
+{
+  gint x, y;
+  guint font_x, font_y;
+  guint background_pixels = 0;
+  guint foreground_pixels = 0;
+  guint char_index = 0;
+  guint dest_index = 0;
+
+  gchar input_letter, input_glyph, attribute;
+  gboolean rain_pixel;
+
+  GstAATvDroplet *raindrops = aatv->raindrops;
+
+  const guchar *font_base_address = aa_currentfont (aatv->context)->data;
+  guint font_height = aa_currentfont (aatv->context)->height;
+
+  /* loop through the canvas height */
+  for (y = 0; y < aa_scrheight (aatv->context); y++) {
+    /* loop through the height of a character's font */
+    for (font_y = 0; font_y < font_height; font_y++) {
+      /* loop through the canvas width */
+      for (x = 0; x < aa_scrwidth (aatv->context); x++) {
+
+        /* which char are we working on */
+        char_index = x + y * aa_scrwidth (aatv->context);
+        /* lookup what character we need to render */
+        input_letter = aa_text (aatv->context)[char_index];
+        /* check for special attributes like bold or dimmed */
+        attribute = aa_attrs (aatv->context)[char_index];
+        /* look that character up in the font glyph table */
+        input_glyph = font_base_address[input_letter * font_height + font_y];
+
+        /* check if we need to re-color this character for rain effect */
+        rain_pixel = FALSE;
+
+        if (aatv->rain_mode == GST_RAIN_DOWN) {
+          if (raindrops[x].enabled)
+            if (y <= raindrops[x].location)
+              if (y >= raindrops[x].location - raindrops[x].length)
+                rain_pixel = TRUE;
+        } else if (aatv->rain_mode == GST_RAIN_UP) {
+          if (raindrops[x].enabled)
+            if (aatv->rain_height - y <= raindrops[x].location)
+              if (aatv->rain_height - y >=
+                  raindrops[x].location - raindrops[x].length)
+                rain_pixel = TRUE;
+        } else if (aatv->rain_mode == GST_RAIN_LEFT) {
+          if (raindrops[y].enabled)
+            if (x <= raindrops[y].location)
+              if (x >= raindrops[y].location - raindrops[y].length)
+                rain_pixel = TRUE;
+        } else if (aatv->rain_mode == GST_RAIN_RIGHT) {
+          if (raindrops[y].enabled)
+            if (aatv->rain_height - x <= raindrops[y].location)
+              if (aatv->rain_height - x >=
+                  raindrops[y].location - raindrops[y].length)
+                rain_pixel = TRUE;
+        }
+        /* loop through the width of a character's font (always 8 pixels wide) */
+        for (font_x = 0; font_x < 8; font_x++) {
+          guint32 *pixel_argb;
+          if (CHECK_BIT (input_glyph, font_x)) {
+            if (attribute == AA_DIM) {
+              if (rain_pixel)
+                pixel_argb = &aatv->color_rain_dim;
+              else
+                pixel_argb = &aatv->color_text_dim;
+            } else if (attribute == AA_BOLD) {
+              if (rain_pixel)
+                pixel_argb = &aatv->color_rain_bold;
+              else
+                pixel_argb = &aatv->color_text_bold;
+            } else {
+              if (rain_pixel)
+                pixel_argb = &aatv->color_rain_normal;
+              else
+                pixel_argb = &aatv->color_text_normal;
+            }
+            foreground_pixels++;
+          } else {
+            pixel_argb = &aatv->color_background;
+            background_pixels++;
+          }
+          dest[dest_index++] = *pixel_argb;
+        }
+      }
+    }
+  }
+
+  aatv->lit_percentage =
+      0.2 * (aatv->lit_percentage) +
+      0.8 * (float) foreground_pixels / background_pixels;
+
+  if (aatv->auto_brightness) {
+    if (aatv->lit_percentage > aatv->brightness_target_max)
+      if (aatv->ascii_parms.bright > -254)
+        aatv->ascii_parms.bright--;
+    if (aatv->lit_percentage < aatv->brightness_target_min)
+      if (aatv->ascii_parms.bright < 254)
+        aatv->ascii_parms.bright++;
+  }
+}
+
+static GstFlowReturn
+gst_aatv_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * in_frame,
+    GstVideoFrame * out_frame)
+{
+  GstAATv *aatv = GST_AATV (vfilter);
+
+  if (aatv->rain_mode != GST_RAIN_OFF)
+    gst_aatv_rain (aatv);
+
+  GST_OBJECT_LOCK (aatv);
+
+  gst_aatv_scale (aatv, GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0),       /* src */
+      aa_image (aatv->context), /* dest */
+      GST_VIDEO_FRAME_WIDTH (in_frame), /* sw */
+      GST_VIDEO_FRAME_HEIGHT (in_frame),        /* sh */
+      GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0),       /* ss */
+      aa_imgwidth (aatv->context),      /* dw */
+      aa_imgheight (aatv->context));    /* dh */
+
+  aa_render (aatv->context, &aatv->ascii_parms, 0, 0,
+      aa_imgwidth (aatv->context), aa_imgheight (aatv->context));
+  gst_aatv_render (aatv, GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0));
+
+  GST_OBJECT_UNLOCK (aatv);
+
+  return GST_FLOW_OK;
+}
+
+
+#define GST_TYPE_AADITHER (gst_aatv_dither_get_type())
+static GType
+gst_aatv_dither_get_type (void)
+{
+  static GType dither_type = 0;
+
+  if (!dither_type) {
+    GEnumValue *ditherers;
+    gint n_ditherers;
+    gint i;
+
+    for (n_ditherers = 0; aa_dithernames[n_ditherers]; n_ditherers++) {
+      /* count number of ditherers */
+    }
+
+    ditherers = g_new0 (GEnumValue, n_ditherers + 1);
+
+    for (i = 0; i < n_ditherers; i++) {
+      ditherers[i].value = i;
+      ditherers[i].value_name = g_strdup (aa_dithernames[i]);
+      ditherers[i].value_nick =
+          g_strdelimit (g_strdup (aa_dithernames[i]), " _", '-');
+    }
+
+    ditherers[i].value = 0;
+    ditherers[i].value_name = NULL;
+    ditherers[i].value_nick = NULL;
+
+    dither_type = g_enum_register_static ("GstAATvDitherers", ditherers);
+  }
+  return dither_type;
+}
+
+#define GST_TYPE_AAFONT (gst_aatv_font_get_type())
+static GType
+gst_aatv_font_get_type (void)
+{
+  static GType font_type = 0;
+
+  if (!font_type) {
+    GEnumValue *fonts;
+    gint n_fonts;
+    gint i;
+
+    for (n_fonts = 0; aa_fonts[n_fonts]; n_fonts++) {
+      /* count number of fonts  */
+    }
+
+    fonts = g_new0 (GEnumValue, n_fonts + 1);
+
+    for (i = 0; i < n_fonts; i++) {
+      fonts[i].value = i;
+      fonts[i].value_name = g_strdup (aa_fonts[i]->shortname);
+      fonts[i].value_nick =
+          g_strdelimit (g_strdup (aa_fonts[i]->name), " _", '-');
+    }
+    fonts[i].value = 0;
+    fonts[i].value_name = NULL;
+    fonts[i].value_nick = NULL;
+
+    font_type = g_enum_register_static ("GstAATvFonts", fonts);
+  }
+  return font_type;
+}
+
+/* use a custom transform_caps */
+static GstCaps *
+gst_aatv_transform_caps (GstBaseTransform * trans, GstPadDirection direction,
+    GstCaps * caps, GstCaps * filter)
+{
+  GstCaps *ret;
+  GstAATv *aatv = GST_AATV (trans);
+  GValue formats = G_VALUE_INIT;
+  GValue value = G_VALUE_INIT;
+  GValue src_width = G_VALUE_INIT;
+  GValue src_height = G_VALUE_INIT;
+
+  if (direction == GST_PAD_SINK) {
+
+    ret = gst_caps_copy (caps);
+
+    g_value_init (&src_width, G_TYPE_INT);
+    g_value_init (&src_height, G_TYPE_INT);
+    /* calculate output resolution from canvas size and font size */
+
+    g_value_set_int (&src_width, aa_defparams.width * 8);
+    g_value_set_int (&src_height,
+        aa_defparams.height * aa_currentfont (aatv->context)->height);
+
+    gst_caps_set_value (ret, "width", &src_width);
+    gst_caps_set_value (ret, "height", &src_height);
+    /* force RGBA output format */
+    g_value_init (&formats, GST_TYPE_LIST);
+    g_value_init (&value, G_TYPE_STRING);
+    g_value_set_string (&value, "RGBA");
+    gst_value_list_append_value (&formats, &value);
+
+    gst_caps_set_value (ret, "format", &formats);
+
+  } else {
+    ret = gst_static_pad_template_get_caps (&sink_template_tv);
+  }
+
+  return ret;
+}
+
+
+static void
+gst_aatv_finalize (GObject * object)
+{
+  GstAATv *aatv = GST_AATV (object);
+  free (aatv->raindrops);
+  if (aatv->context != NULL)
+    aa_close (aatv->context);
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gst_aatv_class_init (GstAATvClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstVideoFilterClass *videofilter_class;
+  GstBaseTransformClass *transform_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  videofilter_class = (GstVideoFilterClass *) klass;
+  transform_class = (GstBaseTransformClass *) klass;
+
+  gobject_class->set_property = gst_aatv_set_property;
+  gobject_class->get_property = gst_aatv_get_property;
+  gobject_class->finalize = gst_aatv_finalize;
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH,
+      g_param_spec_int ("width", "width", "Width of the ASCII canvas", 0,
+          G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT,
+      g_param_spec_int ("height", "height", "Height of the ASCII canvas", 0,
+          G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DITHER,
+      g_param_spec_enum ("dither", "dither",
+          "Add noise to more closely approximate gray levels.",
+          GST_TYPE_AADITHER, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT,
+      g_param_spec_enum ("font", "font", "AAlib Font", GST_TYPE_AAFONT, 0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_TEXT,
+      g_param_spec_uint ("color-text", "color-text",
+          "Automatically sets color-test-bold, color-text-normal, and color-text-dim with progressively dimmer values (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_TEXT_BOLD,
+      g_param_spec_uint ("color-text-bold", "color-text-bold",
+          "Sets the brightest color to use for foreground ASCII text (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_COLOR_TEXT_NORMAL, g_param_spec_uint ("color-text-normal",
+          "color-text-normal",
+          "Sets the normal brightness color to use for foreground ASCII text (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_TEXT_DIM,
+      g_param_spec_uint ("color-text-dim", "color-text-dim",
+          "Sets the dimmest brightness color to use for foreground ASCII text (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_COLOR_BACKGROUND, g_param_spec_uint ("color-background",
+          "color-background",
+          "Color to use as the background for the ASCII text (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BRIGHTNESS,
+      g_param_spec_int ("brightness", "brightness", "Brightness", -255,
+          255, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BRIGHTNESS_AUTO,
+      g_param_spec_boolean ("brightness-auto", "brightness-auto",
+          "Automatically adjust brightness based on the previous frame's foreground pixel fill percentage",
+          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_BRIGHTNESS_ACTUAL, g_param_spec_float ("brightness-actual",
+          "brightness-actual",
+          "Actual calculated foreground pixel fill percentage", 0.0, 1.0, 0.0,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_BRIGHTNESS_TARGET_MIN, g_param_spec_float ("brightness-min",
+          "brightness-min",
+          "Minimum target foreground pixel fill percentage for automatic brightness control",
+          0.0, 1.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_SPAWN_RATE,
+      g_param_spec_float ("rain-spawn-rate", "rain-spawn-rate",
+          "Percentage chance for a raindrop to spawn", 0.0, 1.0, 0.0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_BRIGHTNESS_TARGET_MAX, g_param_spec_float ("brightness-max",
+          "brightness-max",
+          "Maximum target foreground pixel fill percentage for automatic brightness control",
+          0.0, 1.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONTRAST,
+      g_param_spec_int ("contrast", "contrast", "Contrast", 0, G_MAXUINT8,
+          0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAMMA,
+      g_param_spec_float ("gamma", "gamma", "Gamma correction", 0.0, 5.0, 1.0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RANDOMVAL,
+      g_param_spec_int ("randomval", "randomval",
+          "Adds a random value in the range (-randomval/2,ranomval/2) to each pixel during rendering",
+          0, 255, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_DELAY_MIN,
+      g_param_spec_int ("rain-delay-min", "rain-delay-min",
+          "Minimum frame delay between rain motion", 0, G_MAXINT, 0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_DELAY_MAX,
+      g_param_spec_int ("rain-delay-max", "rain-delay-max",
+          "Maximum frame delay between rain motion", 0, G_MAXINT, 0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_LENGTH_MIN,
+      g_param_spec_int ("rain-length-min", "rain-length-min",
+          "Minimum length of a rain", 0, G_MAXINT, 0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_LENGTH_MAX,
+      g_param_spec_int ("rain-length-max", "rain-length-max",
+          "Maximum length of a rain", 0, G_MAXINT, 0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_MODE,
+      g_param_spec_enum ("rain-mode", "rain-mode",
+          "Set the direction of raindrops", GST_TYPE_AATV_RAIN_MODE, 0,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_RAIN,
+      g_param_spec_uint ("color-rain", "color-rain",
+          "Automatically sets color-rain-bold, color-rain-normal, and color-rain-dim with progressively dimmer values (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_RAIN_BOLD,
+      g_param_spec_uint ("color-rain-bold", "color-rain-bold",
+          "Sets the brightest color to use for foreground ASCII text rain overlays (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_COLOR_RAIN_NORMAL, g_param_spec_uint ("color-rain-normal",
+          "color-rain-normal",
+          "Sets the normal brightness color to use for foreground ASCII text rain overlays (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_RAIN_DIM,
+      g_param_spec_uint ("color-rain-dim", "color-rain-dim",
+          "Sets the dimmest brightness color to use for foreground ASCII text rain overlays (big-endian ARGB).",
+          0, G_MAXUINT32, 0,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &sink_template_tv);
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &src_template_tv);
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "aaTV effect", "Filter/Effect/Video",
+      "ASCII art effect", "Eric Marks <bigmarkslp@gmail.com>");
+
+  transform_class->transform_caps = GST_DEBUG_FUNCPTR (gst_aatv_transform_caps);
+  videofilter_class->transform_frame =
+      GST_DEBUG_FUNCPTR (gst_aatv_transform_frame);
+}
+
+static void
+gst_aatv_rain_init (GstAATv * aatv)
+{
+  switch (aatv->rain_mode) {
+    case GST_RAIN_DOWN:
+    case GST_RAIN_UP:
+      aatv->rain_width = aa_defparams.width;
+      aatv->rain_height = aa_defparams.height;
+      break;
+    case GST_RAIN_LEFT:
+    case GST_RAIN_RIGHT:
+      aatv->rain_width = aa_defparams.height;
+      aatv->rain_height = aa_defparams.width;
+      break;
+    case GST_RAIN_OFF:
+      aatv->rain_width = 0;
+      aatv->rain_height = 0;
+  }
+
+  if (aatv->context != NULL)
+    aa_close (aatv->context);
+  aatv->context = aa_init (&mem_d, &aa_defparams, NULL);
+  aa_setfont (aatv->context, aa_fonts[0]);
+
+  aatv->raindrops =
+      realloc (aatv->raindrops,
+      aatv->rain_width * sizeof (struct _GstAATvDroplet));
+  for (gint i = 0; i < aatv->rain_width; i++)
+    aatv->raindrops[i].enabled = FALSE;
+
+}
+
+static guint32
+gst_aatv_set_color (guint32 input_color, guint8 dim)
+{
+  guint8 a = ((input_color >> 24) & 0xff);
+  guint8 b = ((input_color >> 16) & 0xff) >> dim;
+  guint8 g = ((input_color >> 8) & 0xff) >> dim;
+  guint8 r = ((input_color >> 0) & 0xff) >> dim;
+
+  return ((a << 24) | (b << 16) | (g << 8) | (r << 0));
+}
+
+static void
+gst_aatv_set_color_rain (GstAATv * aatv, guint input_color)
+{
+  aatv->color_rain = input_color;
+  aatv->color_rain_bold = gst_aatv_set_color (input_color, 0);
+  aatv->color_rain_normal = gst_aatv_set_color (aatv->color_rain_bold, 1);
+  aatv->color_rain_dim = gst_aatv_set_color (aatv->color_rain_normal, 1);
+}
+
+static void
+gst_aatv_set_color_text (GstAATv * aatv, guint input_color)
+{
+  aatv->color_text = input_color;
+  aatv->color_text_bold = gst_aatv_set_color (input_color, 0);
+  aatv->color_text_normal = gst_aatv_set_color (aatv->color_text_bold, 1);
+  aatv->color_text_dim = gst_aatv_set_color (aatv->color_text_normal, 1);
+}
+
+static void
+gst_aatv_init (GstAATv * aatv)
+{
+  aa_defparams.width = 80;
+  aa_defparams.height = 24;
+
+  aatv->ascii_parms.bright = 0;
+  aatv->ascii_parms.contrast = 0;
+  aatv->ascii_parms.gamma = 1.0;
+  aatv->ascii_parms.dither = 0;
+  aatv->ascii_parms.inversion = 0;
+  aatv->ascii_parms.randomval = 0;
+
+  aatv->color_background =
+      gst_aatv_set_color (PROP_AATV_COLOR_BACKGROUND_DEFAULT, 0);
+  gst_aatv_set_color_rain (aatv, PROP_AATV_COLOR_RAIN_DEFAULT);
+  gst_aatv_set_color_text (aatv, PROP_AATV_COLOR_TEXT_DEFAULT);
+
+  aatv->rain_mode = PROP_AATV_RAIN_MODE_DEFAULT;
+
+  gst_aatv_rain_init (aatv);
+
+  aatv->rain_spawn_rate = PROP_RAIN_SPAWN_DEFAULT;
+
+  aatv->auto_brightness = TRUE;
+  aatv->brightness_target_min = PROP_BRIGHTNESS_TARGET_MIN_DEFAULT;
+  aatv->brightness_target_max = PROP_BRIGHTNESS_TARGET_MAX_DEFAULT;
+  aatv->lit_percentage =
+      (PROP_BRIGHTNESS_TARGET_MIN_DEFAULT +
+      PROP_BRIGHTNESS_TARGET_MAX_DEFAULT) / 2;
+
+  aatv->rain_length_min = PROP_RAIN_LENGTH_MIN_DEFAULT;
+  aatv->rain_length_max = PROP_RAIN_LENGTH_MAX_DEFAULT;
+
+  aatv->rain_delay_min = PROP_RAIN_DELAY_MIN_DEFAULT;
+  aatv->rain_delay_max = PROP_RAIN_DELAY_MAX_DEFAULT;
+}
+
+static void
+gst_aatv_set_property (GObject * object, guint prop_id, const GValue * value,
+    GParamSpec * pspec)
+{
+  GstAATv *aatv = GST_AATV (object);
+
+  switch (prop_id) {
+    case PROP_WIDTH:{
+      aa_defparams.width = g_value_get_int (value);
+      /* recalculate output resolution based on new width */
+      gst_aatv_rain_init (aatv);
+      gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
+      break;
+    }
+    case PROP_HEIGHT:{
+      aa_defparams.height = g_value_get_int (value);
+      /* recalculate output resolution based on new height */
+      gst_aatv_rain_init (aatv);
+      gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
+      break;
+    }
+    case PROP_DITHER:{
+      aatv->ascii_parms.dither = g_value_get_enum (value);
+      break;
+    }
+    case PROP_FONT:{
+      aa_setfont (aatv->context, aa_fonts[g_value_get_enum (value)]);
+      /* recalculate output resolution based on new font */
+      gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
+      break;
+    }
+    case PROP_BRIGHTNESS:{
+      aatv->ascii_parms.bright = g_value_get_int (value);
+      break;
+    }
+    case PROP_CONTRAST:{
+      aatv->ascii_parms.contrast = g_value_get_int (value);
+      break;
+    }
+    case PROP_GAMMA:{
+      aatv->ascii_parms.gamma = g_value_get_float (value);
+      break;
+    }
+    case PROP_BRIGHTNESS_TARGET_MIN:{
+      if (g_value_get_float (value) <= aatv->brightness_target_max)
+        aatv->brightness_target_min = g_value_get_float (value);
+      break;
+    }
+    case PROP_BRIGHTNESS_TARGET_MAX:{
+      if (g_value_get_float (value) >= aatv->brightness_target_min)
+        aatv->brightness_target_max = g_value_get_float (value);
+      break;
+    }
+    case PROP_RAIN_SPAWN_RATE:{
+      aatv->rain_spawn_rate = g_value_get_float (value);
+      break;
+    }
+    case PROP_COLOR_TEXT:{
+      aatv->color_text = g_value_get_uint (value);
+      gst_aatv_set_color_text (aatv, aatv->color_text);
+      break;
+    }
+    case PROP_COLOR_TEXT_BOLD:{
+      aatv->color_text_bold = gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_COLOR_TEXT_NORMAL:{
+      aatv->color_text_normal =
+          gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_COLOR_TEXT_DIM:{
+      aatv->color_text_dim = gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_COLOR_BACKGROUND:{
+      aatv->color_background = gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_COLOR_RAIN:{
+      aatv->color_rain = g_value_get_uint (value);
+      gst_aatv_set_color_rain (aatv, aatv->color_rain);
+      break;
+    }
+    case PROP_COLOR_RAIN_BOLD:{
+      aatv->color_rain_bold = gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_COLOR_RAIN_NORMAL:{
+      aatv->color_rain_normal =
+          gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_COLOR_RAIN_DIM:{
+      aatv->color_rain_dim = gst_aatv_set_color (g_value_get_uint (value), 0);
+      break;
+    }
+    case PROP_BRIGHTNESS_AUTO:{
+      aatv->auto_brightness = g_value_get_boolean (value);
+      break;
+    }
+    case PROP_RANDOMVAL:{
+      aatv->ascii_parms.randomval = g_value_get_int (value);
+      break;
+    }
+    case PROP_RAIN_DELAY_MIN:{
+      if (g_value_get_float (value) <= aatv->rain_delay_max)
+        aatv->rain_delay_min = g_value_get_int (value);
+      break;
+    }
+    case PROP_RAIN_DELAY_MAX:{
+      if (g_value_get_float (value) >= aatv->rain_delay_min)
+        aatv->rain_delay_max = g_value_get_int (value);
+      break;
+    }
+    case PROP_RAIN_LENGTH_MIN:{
+      if (g_value_get_float (value) <= aatv->rain_length_max)
+        aatv->rain_length_min = g_value_get_int (value);
+      break;
+    }
+    case PROP_RAIN_LENGTH_MAX:{
+      if (g_value_get_float (value) >= aatv->rain_length_min)
+        aatv->rain_length_max = g_value_get_int (value);
+      break;
+    }
+    case PROP_RAIN_MODE:{
+      aatv->rain_mode = g_value_get_enum (value);
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+static void
+gst_aatv_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstAATv *aatv = GST_AATV (object);
+
+  switch (prop_id) {
+    case PROP_BRIGHTNESS_ACTUAL:{
+      g_value_set_float (value, aatv->lit_percentage);
+      break;
+    }
+    case PROP_WIDTH:{
+      g_value_set_int (value, aa_defparams.width);
+      break;
+    }
+    case PROP_HEIGHT:{
+      g_value_set_int (value, aa_defparams.height);
+
+      break;
+    }
+    case PROP_DITHER:{
+      g_value_set_enum (value, aatv->ascii_parms.dither);
+      break;
+    }
+    case PROP_FONT:{
+      g_value_set_enum (value, aatv->ascii_parms.dither);
+      break;
+    }
+    case PROP_BRIGHTNESS:{
+      g_value_set_int (value, aatv->ascii_parms.bright);
+      break;
+    }
+    case PROP_BRIGHTNESS_AUTO:{
+      g_value_set_boolean (value, aatv->auto_brightness);
+      break;
+    }
+    case PROP_CONTRAST:{
+      g_value_set_int (value, aatv->ascii_parms.contrast);
+      break;
+    }
+    case PROP_GAMMA:{
+      g_value_set_float (value, aatv->ascii_parms.gamma);
+      break;
+    }
+    case PROP_RAIN_SPAWN_RATE:{
+      g_value_set_float (value, aatv->rain_spawn_rate);
+      break;
+    }
+    case PROP_BRIGHTNESS_TARGET_MIN:{
+      g_value_set_float (value, aatv->brightness_target_min);
+      break;
+    }
+    case PROP_BRIGHTNESS_TARGET_MAX:{
+      g_value_set_float (value, aatv->brightness_target_max);
+      break;
+    }
+    case PROP_COLOR_TEXT:{
+      g_value_set_uint (value, aatv->color_text);
+      break;
+    }
+    case PROP_COLOR_TEXT_BOLD:{
+      g_value_set_uint (value, aatv->color_text_bold);
+      break;
+    }
+    case PROP_COLOR_TEXT_NORMAL:{
+      g_value_set_uint (value, aatv->color_text_normal);
+      break;
+    }
+    case PROP_COLOR_TEXT_DIM:{
+      g_value_set_uint (value, aatv->color_text_dim);
+      break;
+    }
+    case PROP_COLOR_BACKGROUND:{
+      g_value_set_uint (value, aatv->color_background);
+      break;
+    }
+    case PROP_COLOR_RAIN:{
+      g_value_set_uint (value, aatv->color_rain);
+      break;
+    }
+    case PROP_COLOR_RAIN_BOLD:{
+      g_value_set_uint (value, aatv->color_rain_bold);
+      break;
+    }
+    case PROP_COLOR_RAIN_NORMAL:{
+      g_value_set_uint (value, aatv->color_rain_normal);
+      break;
+    }
+    case PROP_COLOR_RAIN_DIM:{
+      g_value_set_uint (value, aatv->color_rain_dim);
+      break;
+    }
+    case PROP_RANDOMVAL:{
+      g_value_set_int (value, aatv->ascii_parms.randomval);
+      break;
+    }
+    case PROP_RAIN_MODE:{
+      g_value_set_enum (value, aatv->rain_mode);
+      break;
+    }
+    case PROP_RAIN_DELAY_MIN:{
+      g_value_set_int (value, aatv->rain_delay_min);
+      break;
+    }
+    case PROP_RAIN_DELAY_MAX:{
+      g_value_set_int (value, aatv->rain_delay_max);
+      break;
+    }
+    case PROP_RAIN_LENGTH_MIN:{
+      g_value_set_int (value, aatv->rain_length_min);
+      break;
+    }
+    case PROP_RAIN_LENGTH_MAX:{
+      g_value_set_int (value, aatv->rain_length_max);
+      break;
+    }
+    default:{
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+  }
+}
diff --git a/ext/aalib/gstaatv.h b/ext/aalib/gstaatv.h
new file mode 100644 (file)
index 0000000..514ffd9
--- /dev/null
@@ -0,0 +1,111 @@
+/* GStreamer
+ * Copyright (C) <2019> Eric Marks <bigmarkslp@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_AATV_H__
+#define __GST_AATV_H__
+
+#include <gst/gst.h>
+#include <gst/video/gstvideofilter.h>
+#include <gst/video/video.h>
+#include <aalib.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GST_TYPE_AATV \
+    (gst_aatv_get_type())
+#define GST_AATV(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AATV,GstAATv))
+#define GST_AATV_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AATV,GstAATvClass))
+#define GST_IS_AATV(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AATV))
+#define GST_IS_AATV_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AATV))
+
+  typedef struct _GstAATv GstAATv;
+  typedef struct _GstAATvClass GstAATvClass;
+  typedef struct _GstAATvDroplet GstAATvDroplet;
+  typedef struct _GstAATvARGB GstAATvARGB;
+
+  typedef enum {
+    GST_RAIN_OFF,
+    GST_RAIN_DOWN,
+    GST_RAIN_UP,
+    GST_RAIN_LEFT,
+    GST_RAIN_RIGHT
+  } GstRainMode;
+
+  struct _GstAATvDroplet {
+    gboolean enabled;
+    gint location;    
+    gint length;      
+    gint delay;
+    gint delay_counter;
+  };
+
+  struct _GstAATv {
+    GstVideoFilter videofilter;
+
+    aa_context *context;
+
+    guint32 color_text;
+    guint32 color_text_bold,color_text_normal,color_text_dim;
+    guint32 color_rain;
+    guint32 color_rain_bold,color_rain_normal,color_rain_dim;
+    guint32 color_background;
+    
+    GstRainMode rain_mode;
+
+    gint rain_width;
+    gint rain_height;
+    
+    gint rain_length_min;
+    gint rain_length_max;
+
+    gint rain_delay_min;
+    gint rain_delay_max;
+    
+    gfloat rain_spawn_rate;
+    
+    gboolean auto_brightness;
+    gfloat brightness_target_min;
+    gfloat brightness_target_max;
+    gfloat lit_percentage;
+    
+    GstAATvDroplet * raindrops;
+    struct aa_renderparams ascii_parms;
+  };
+
+  struct _GstAATvClass {
+    GstVideoFilterClass parent_class;
+  };
+
+  GType gst_aatv_get_type(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GST_AASINKE_H__ */
index 21eae20..613ccbe 100644 (file)
@@ -14,7 +14,7 @@ endif
 if have_aalib
   libaa_dep = cc.find_library('aa', required : aalib_option)
   if libaa_dep.found()
-    gstaasink = library('gstaasink', 'gstaasink.c',
+    gstaasink = library('gstaasink', ['gstaasink.c','gstaatv.c'],
       c_args : gst_plugins_good_args,
       link_args : noseh_link_args,
       include_directories : [configinc],
index 843fbf5..612ac91 100644 (file)
@@ -40,6 +40,7 @@
 
 #include <string.h>
 #include "gstcacasink.h"
+#include "gstcacatv.h"
 
 
 //#define GST_CACA_DEFAULT_RED_MASK R_MASK_32_REVERSE_INT
@@ -405,6 +406,10 @@ gst_cacasink_change_state (GstElement * element, GstStateChange transition)
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
+
+  if (!gst_element_register (plugin, "cacatv", GST_RANK_NONE, GST_TYPE_CACATV))
+    return FALSE;
+
   if (!gst_element_register (plugin, "cacasink", GST_RANK_NONE,
           GST_TYPE_CACASINK))
     return FALSE;
@@ -415,5 +420,5 @@ plugin_init (GstPlugin * plugin)
 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
     GST_VERSION_MINOR,
     cacasink,
-    "Colored ASCII Art video sink",
+    "Colored ASCII Art video sink & filter",
     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/ext/libcaca/gstcacatv.c b/ext/libcaca/gstcacatv.c
new file mode 100644 (file)
index 0000000..0c0a5d1
--- /dev/null
@@ -0,0 +1,401 @@
+/* GStreamer
+ * Copyright (C) <2019> Eric Marks <bigmarkslp@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/**
+ * SECTION:element-cacatv
+ * @see_also: #GstCacaSink
+ *
+ * Transforms video into color ascii art.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch-1.0 videotestsrc ! cacatv ! videoconvert ! autovideosink
+ * ]| This pipeline shows the effect of cacatv on a test stream.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "gstcacatv.h"
+
+/* cacatv signals and args */
+enum
+{
+  LAST_SIGNAL
+};
+
+#define GST_CACA_DEFAULT_FONT 0
+#define GST_CACA_DEFAULT_SCREEN_WIDTH 80
+#define GST_CACA_DEFAULT_SCREEN_HEIGHT 24
+#define GST_CACA_DEFAULT_DITHER CACA_DITHERING_NONE
+#define GST_CACA_DEFAULT_ANTIALIASING FALSE
+
+enum
+{
+  PROP_0,
+  PROP_CANVAS_WIDTH,
+  PROP_CANVAS_HEIGHT,
+  PROP_FONT,
+  PROP_DITHER,
+  PROP_ANTIALIASING
+};
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
+        ("{ RGB, BGR, RGBx, xRGB, BGRx, xBGR, RGBA, RGB16, RGB15 }"))
+    );
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ ARGB }"))
+    );
+
+static GstFlowReturn
+gst_cacatv_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * in_frame,
+    GstVideoFrame * out_frame)
+{
+  GstCACATv *cacatv = GST_CACATV (vfilter);
+
+  GST_OBJECT_LOCK (cacatv);
+  caca_clear_canvas (cacatv->canvas);
+  caca_dither_bitmap (cacatv->canvas, 0, 0,
+      caca_get_canvas_width (cacatv->canvas),
+      caca_get_canvas_height (cacatv->canvas), cacatv->dither,
+      GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0));
+  /* libcaca always renders ARGB */
+  caca_render_canvas (cacatv->canvas, cacatv->font,
+      GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0), cacatv->src_width,
+      cacatv->src_height, 4 * cacatv->src_width);
+  GST_OBJECT_UNLOCK (cacatv);
+
+  return GST_FLOW_OK;
+}
+
+static void gst_cacatv_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_cacatv_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+#define gst_cacatv_parent_class parent_class
+G_DEFINE_TYPE (GstCACATv, gst_cacatv, GST_TYPE_VIDEO_FILTER);
+
+#define GST_TYPE_CACADITHER (gst_cacatv_dither_get_type())
+static GType
+gst_cacatv_dither_get_type (void)
+{
+  static GType dither_type = 0;
+
+  static const GEnumValue dither_types[] = {
+    {CACA_DITHERING_NONE, "No dither_mode", "none"},
+    {CACA_DITHERING_ORDERED2, "Ordered 2x2 Bayer dither_mode", "2x2"},
+    {CACA_DITHERING_ORDERED4, "Ordered 4x4 Bayer dither_mode", "4x4"},
+    {CACA_DITHERING_ORDERED8, "Ordered 8x8 Bayer dither_mode", "8x8"},
+    {CACA_DITHERING_RANDOM, "Random dither_mode", "random"},
+    {0, NULL, NULL},
+  };
+
+  if (!dither_type) {
+    dither_type = g_enum_register_static ("GstCACATvDithering", dither_types);
+  }
+  return dither_type;
+}
+
+static gboolean
+gst_cacatv_setcaps (GstVideoFilter * filter, GstCaps * incaps,
+    GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
+{
+  GstCACATv *cacatv = GST_CACATV (filter);
+  GstVideoInfo info;
+  guint bpp, red_mask, green_mask, blue_mask, depth;
+
+  if (!gst_video_info_from_caps (&info, incaps))
+    goto caps_error;
+
+  cacatv->sink_width = GST_VIDEO_INFO_WIDTH (&info);
+  cacatv->sink_height = GST_VIDEO_INFO_HEIGHT (&info);
+
+  switch (GST_VIDEO_INFO_FORMAT (&info)) {
+    case GST_VIDEO_FORMAT_RGB:
+    case GST_VIDEO_FORMAT_BGR:
+      bpp = 8 * info.finfo->pixel_stride[0];
+      depth = 3;
+      red_mask = 0xff << (8 * info.finfo->poffset[GST_VIDEO_COMP_R]);
+      green_mask = 0xff << (8 * info.finfo->poffset[GST_VIDEO_COMP_G]);
+      blue_mask = 0xff << (8 * info.finfo->poffset[GST_VIDEO_COMP_B]);
+      break;
+    case GST_VIDEO_FORMAT_RGBA:
+    case GST_VIDEO_FORMAT_RGBx:
+    case GST_VIDEO_FORMAT_xRGB:
+    case GST_VIDEO_FORMAT_BGRx:
+    case GST_VIDEO_FORMAT_xBGR:
+      bpp = 8 * info.finfo->pixel_stride[0];
+      depth = 4;
+      red_mask = 0xff << (8 * info.finfo->poffset[GST_VIDEO_COMP_R]);
+      green_mask = 0xff << (8 * info.finfo->poffset[GST_VIDEO_COMP_G]);
+      blue_mask = 0xff << (8 * info.finfo->poffset[GST_VIDEO_COMP_B]);
+      break;
+    case GST_VIDEO_FORMAT_RGB16:
+      bpp = 16;
+      depth = 2;
+      red_mask = 0xf800;
+      green_mask = 0x07e0;
+      blue_mask = 0x001f;
+      break;
+    case GST_VIDEO_FORMAT_RGB15:
+      bpp = 16;
+      depth = 2;
+      red_mask = 0x7c00;
+      green_mask = 0x03e0;
+      blue_mask = 0x001f;
+      break;
+    default:
+      goto invalid_format;
+  }
+
+  /* free if already exists (there is no dither resize) */
+  caca_free_dither (cacatv->dither);
+  cacatv->dither =
+      caca_create_dither (bpp, cacatv->sink_width, cacatv->sink_height,
+      depth * cacatv->sink_width, red_mask, green_mask, blue_mask, 0x00000000);
+  caca_set_canvas_size (cacatv->canvas, cacatv->canvas_width,
+      cacatv->canvas_height);
+
+  return TRUE;
+  /* ERRORS */
+caps_error:
+  {
+    GST_ERROR_OBJECT (cacatv, "error parsing caps");
+    return FALSE;
+  }
+invalid_format:
+  {
+    GST_ERROR_OBJECT (cacatv, "invalid format");
+    return FALSE;
+  }
+}
+
+/* use a custom transform_caps */
+static GstCaps *
+gst_cacatv_transform_caps (GstBaseTransform * trans, GstPadDirection direction,
+    GstCaps * caps, GstCaps * filter)
+{
+  GstCaps *ret;
+  GstCACATv *cacatv = GST_CACATV (trans);
+  GValue formats = G_VALUE_INIT;
+  GValue value = G_VALUE_INIT;
+  GValue src_width = G_VALUE_INIT;
+  GValue src_height = G_VALUE_INIT;
+
+  if (direction == GST_PAD_SINK) {
+
+    ret = gst_caps_copy (caps);
+
+    g_value_init (&src_width, G_TYPE_INT);
+    g_value_init (&src_height, G_TYPE_INT);
+    /* calculate output resolution from canvas size and font size */
+    cacatv->src_width =
+        cacatv->canvas_width * caca_get_font_width (cacatv->font);
+    cacatv->src_height =
+        cacatv->canvas_height * caca_get_font_height (cacatv->font);
+
+    g_value_set_int (&src_width, cacatv->src_width);
+    g_value_set_int (&src_height, cacatv->src_height);
+
+    gst_caps_set_value (ret, "width", &src_width);
+    gst_caps_set_value (ret, "height", &src_height);
+    /* force ARGB output format */
+    g_value_init (&formats, GST_TYPE_LIST);
+    g_value_init (&value, G_TYPE_STRING);
+    g_value_set_string (&value, "ARGB");
+    gst_value_list_append_value (&formats, &value);
+
+    gst_caps_set_value (ret, "format", &formats);
+  } else {
+    ret = gst_static_pad_template_get_caps (&sink_template);
+  }
+
+  return ret;
+}
+
+static void
+gst_cacatv_finalize (GObject * object)
+{
+  GstCACATv *cacatv = GST_CACATV (object);
+  caca_free_font (cacatv->font);
+  caca_free_dither (cacatv->dither);
+  caca_free_canvas (cacatv->canvas);
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_cacatv_class_init (GstCACATvClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstVideoFilterClass *videofilter_class;
+  GstBaseTransformClass *transform_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  videofilter_class = (GstVideoFilterClass *) klass;
+  transform_class = (GstBaseTransformClass *) klass;
+
+  gobject_class->set_property = gst_cacatv_set_property;
+  gobject_class->get_property = gst_cacatv_get_property;
+  gobject_class->finalize = gst_cacatv_finalize;
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CANVAS_WIDTH,
+      g_param_spec_int ("canvas-width", "Canvas Width",
+          "The width of the canvas in characters", 0, G_MAXINT,
+          GST_CACA_DEFAULT_SCREEN_WIDTH,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CANVAS_HEIGHT,
+      g_param_spec_int ("canvas-height", "Canvas Height",
+          "The height of the canvas in characters", 0, G_MAXINT,
+          GST_CACA_DEFAULT_SCREEN_HEIGHT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT,
+      g_param_spec_int ("font", "Font", "selected libcaca font", 0, G_MAXINT,
+          GST_CACA_DEFAULT_SCREEN_HEIGHT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DITHER,
+      g_param_spec_enum ("dither", "Dither Type", "Set type of Dither",
+          GST_TYPE_CACADITHER, GST_CACA_DEFAULT_DITHER,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ANTIALIASING,
+      g_param_spec_boolean ("anti-aliasing", "Anti Aliasing",
+          "Enables Anti-Aliasing", GST_CACA_DEFAULT_ANTIALIASING,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "CacaTV effect", "Filter/Effect/Video",
+      "Colored ASCII art effect", "Eric Marks <bigmarkslp@gmail.com>");
+
+  gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
+  gst_element_class_add_static_pad_template (gstelement_class, &src_template);
+
+  videofilter_class->transform_frame =
+      GST_DEBUG_FUNCPTR (gst_cacatv_transform_frame);
+  videofilter_class->set_info = GST_DEBUG_FUNCPTR (gst_cacatv_setcaps);
+  transform_class->transform_caps =
+      GST_DEBUG_FUNCPTR (gst_cacatv_transform_caps);
+}
+
+static void
+gst_cacatv_init (GstCACATv * cacatv)
+{
+  char const *const *fonts = caca_get_font_list ();
+  cacatv->font_index = GST_CACA_DEFAULT_FONT;
+  cacatv->font = caca_load_font (fonts[cacatv->font_index], 0);
+
+  cacatv->canvas_width = GST_CACA_DEFAULT_SCREEN_WIDTH;
+  cacatv->canvas_height = GST_CACA_DEFAULT_SCREEN_HEIGHT;
+  cacatv->canvas =
+      caca_create_canvas (cacatv->canvas_width, cacatv->canvas_height);
+
+  cacatv->antialiasing = FALSE;
+  caca_set_feature (CACA_ANTIALIASING_MIN);
+
+  cacatv->dither_mode = 0;
+  caca_set_dithering (CACA_DITHERING_NONE);
+}
+
+static void
+gst_cacatv_set_property (GObject * object, guint prop_id, const GValue * value,
+    GParamSpec * pspec)
+{
+  GstCACATv *cacatv = GST_CACATV (object);
+
+  switch (prop_id) {
+    case PROP_DITHER:{
+      cacatv->dither_mode = g_value_get_enum (value);
+      caca_set_dithering (cacatv->dither_mode + CACA_DITHERING_NONE);
+      break;
+    }
+    case PROP_ANTIALIASING:{
+      cacatv->antialiasing = g_value_get_boolean (value);
+      if (cacatv->antialiasing) {
+        caca_set_feature (CACA_ANTIALIASING_MAX);
+      } else {
+        caca_set_feature (CACA_ANTIALIASING_MIN);
+      }
+      break;
+    }
+    case PROP_CANVAS_WIDTH:{
+      cacatv->canvas_width = g_value_get_int (value);
+      /* recalculate output resolution based on new width */
+      gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
+      break;
+    }
+    case PROP_CANVAS_HEIGHT:{
+      cacatv->canvas_height = g_value_get_int (value);
+      /* recalculate output resolution based on new height */
+      gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
+      break;
+    }
+    case PROP_FONT:{
+      char const *const *fonts = caca_get_font_list ();
+      cacatv->font_index = g_value_get_int (value);
+      caca_free_font (cacatv->font);
+      cacatv->font = caca_load_font (fonts[cacatv->font_index], 0);
+      /* recalculate output resolution based on new font */
+      gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+static void
+gst_cacatv_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstCACATv *cacatv = GST_CACATV (object);
+
+  switch (prop_id) {
+    case PROP_CANVAS_WIDTH:{
+      g_value_set_int (value, cacatv->canvas_width);
+      break;
+    }
+    case PROP_CANVAS_HEIGHT:{
+      g_value_set_int (value, cacatv->canvas_height);
+      break;
+    }
+    case PROP_DITHER:{
+      g_value_set_enum (value, cacatv->dither_mode);
+      break;
+    }
+    case PROP_ANTIALIASING:{
+      g_value_set_boolean (value, cacatv->antialiasing);
+      break;
+    }
+    case PROP_FONT:{
+      g_value_set_int (value, cacatv->font_index);
+      break;
+    }
+    default:{
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+  }
+}
diff --git a/ext/libcaca/gstcacatv.h b/ext/libcaca/gstcacatv.h
new file mode 100644 (file)
index 0000000..d244544
--- /dev/null
@@ -0,0 +1,76 @@
+/* GStreamer
+ * Copyright (C) <2019> Eric Marks <bigmarkslp@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_CACATV_H__
+#define __GST_CACATV_H__
+
+#include <gst/gst.h>
+#include <gst/video/gstvideofilter.h>
+#include <gst/video/video.h>
+
+#include <caca.h>
+#ifdef CACA_API_VERSION_1
+#   include <caca0.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_CACATV \
+  (gst_cacatv_get_type())
+#define GST_CACATV(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CACATV,GstCACATv))
+#define GST_CACATV_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CACATV,GstCACATvClass))
+#define GST_IS_CACATV(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CACATV))
+#define GST_IS_CACATV_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CACATV))
+
+typedef struct _GstCACATv GstCACATv;
+typedef struct _GstCACATvClass GstCACATvClass;
+
+struct _GstCACATv {
+  GstVideoFilter videofilter;
+  GstVideoInfo info;
+  
+  gint sink_width, sink_height;
+  gint canvas_height, canvas_width;
+  gint src_width,  src_height;
+  gint font_index;
+  
+  guint dither_mode;
+  gboolean antialiasing;
+  
+  caca_canvas_t *canvas;
+  struct caca_dither *dither;
+  caca_font_t *font;
+};
+
+struct _GstCACATvClass {
+  GstVideoFilterClass parent_class;
+
+  /* signals */
+};
+
+GType gst_cacatv_get_type(void);
+
+G_END_DECLS
+
+#endif /* __GST_CACATV_H__ */
index 2c6b008..b46607c 100644 (file)
@@ -1,7 +1,7 @@
 libcaca_dep = dependency('caca', required : get_option('libcaca'))
 
 if libcaca_dep.found()
-  caca = library('gstcacasink', 'gstcacasink.c',
+  caca = library('gstcacasink', ['gstcacasink.c','gstcacatv.c'],
     c_args : gst_plugins_good_args,
     link_args : noseh_link_args,
     include_directories : [configinc],