diff --git a/ext/aalib/gstaasink.c b/ext/aalib/gstaasink.c index ba21633cda..18d361faed 100644 --- a/ext/aalib/gstaasink.c +++ b/ext/aalib/gstaasink.c @@ -42,6 +42,7 @@ #include #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 index 0000000000..247af83bf5 --- /dev/null +++ b/ext/aalib/gstaatv.c @@ -0,0 +1,978 @@ +/* GStreamer + * Copyright (C) <2019> Eric Marks + * + * 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. + * + * + * Example launch line + * |[ + * gst-launch-1.0 videotestsrc ! aatv ! videoconvert ! autovideosink + * ]| This pipeline shows the effect of aatv on a test stream. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstaatv.h" +#include +#include + +#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 "); + + 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 index 0000000000..514ffd9610 --- /dev/null +++ b/ext/aalib/gstaatv.h @@ -0,0 +1,111 @@ +/* GStreamer + * Copyright (C) <2019> Eric Marks + * + * 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 +#include +#include +#include + + +#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__ */ diff --git a/ext/aalib/meson.build b/ext/aalib/meson.build index 21eae20fea..613ccbe07b 100644 --- a/ext/aalib/meson.build +++ b/ext/aalib/meson.build @@ -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], diff --git a/ext/libcaca/gstcacasink.c b/ext/libcaca/gstcacasink.c index 843fbf5044..612ac917d9 100644 --- a/ext/libcaca/gstcacasink.c +++ b/ext/libcaca/gstcacasink.c @@ -40,6 +40,7 @@ #include #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 index 0000000000..0c0a5d1540 --- /dev/null +++ b/ext/libcaca/gstcacatv.c @@ -0,0 +1,401 @@ +/* GStreamer + * Copyright (C) <2019> Eric Marks + * + * 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. + * + * + * Example launch line + * |[ + * gst-launch-1.0 videotestsrc ! cacatv ! videoconvert ! autovideosink + * ]| This pipeline shows the effect of cacatv on a test stream. + * + */ + +#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 "); + + 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 index 0000000000..d24454430c --- /dev/null +++ b/ext/libcaca/gstcacatv.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) <2019> Eric Marks + * + * 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 +#include +#include + +#include +#ifdef CACA_API_VERSION_1 +# include +#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__ */ diff --git a/ext/libcaca/meson.build b/ext/libcaca/meson.build index 2c6b0083c0..b46607c62d 100644 --- a/ext/libcaca/meson.build +++ b/ext/libcaca/meson.build @@ -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],