diff --git a/gst-libs/gst/video/video-blend.c b/gst-libs/gst/video/video-blend.c index 2eb8ac0e31..9b6c172d73 100644 --- a/gst-libs/gst/video/video-blend.c +++ b/gst-libs/gst/video/video-blend.c @@ -1333,12 +1333,14 @@ video_blend_scale_linear_RGBA (GstBlendVideoFormatInfo * src, * @dest * @x: The x offset in pixel where the @src image should be blended * @y: the y offset in pixel where the @src image should be blended + * @global_alpha: the global_alpha each per-pixel alpha value is multiplied + * with * * Lets you blend the @src image into the @dest image */ gboolean video_blend (GstBlendVideoFormatInfo * dest, - GstBlendVideoFormatInfo * src, gint x, gint y) + GstBlendVideoFormatInfo * src, guint x, guint y, gfloat global_alpha) { guint i, j; guint8 alpha; @@ -1349,6 +1351,7 @@ video_blend (GstBlendVideoFormatInfo * dest, g_return_val_if_fail (dest, FALSE); g_return_val_if_fail (src, FALSE); + g_return_val_if_fail (global_alpha >= 0 && global_alpha <= 1, FALSE); /* we do no support writing to premultiplied alpha, though that should just be a matter of adding blenders below (BLEND01 and BLEND11) */ @@ -1414,7 +1417,7 @@ video_blend (GstBlendVideoFormatInfo * dest, #define BLENDLOOP(blender) \ do { \ for (j = 0; j < src->width * 4; j += 4) { \ - alpha = tmpsrcline[j]; \ + alpha = (guint8) tmpsrcline[j] * global_alpha; \ \ blender (tmpdestline[j + 1], alpha, tmpsrcline[j + 1], tmpdestline[j + 1]); \ blender (tmpdestline[j + 2], alpha, tmpsrcline[j + 2], tmpdestline[j + 2]); \ diff --git a/gst-libs/gst/video/video-blend.h b/gst-libs/gst/video/video-blend.h index 32e5d12028..7e4c50fcb7 100644 --- a/gst-libs/gst/video/video-blend.h +++ b/gst-libs/gst/video/video-blend.h @@ -71,6 +71,7 @@ void video_blend_scale_linear_RGBA (GstBlendVideoFormatInfo * src, gboolean video_blend (GstBlendVideoFormatInfo * dest, GstBlendVideoFormatInfo * src, - gint x, gint y); + guint x, guint y, + gfloat global_alpha); #endif diff --git a/gst-libs/gst/video/video-overlay-composition.c b/gst-libs/gst/video/video-overlay-composition.c index ad9788670f..a1de051a46 100644 --- a/gst-libs/gst/video/video-overlay-composition.c +++ b/gst-libs/gst/video/video-overlay-composition.c @@ -69,6 +69,7 @@ #include "video-overlay-composition.h" #include "video-blend.h" +#include struct _GstVideoOverlayComposition { @@ -134,6 +135,19 @@ struct _GstVideoOverlayRectangle * rectangles have expired. */ guint seq_num; + /* global alpha: global alpha value of the rectangle. Each each per-pixel + * alpha value of image-data will be multiplied with the global alpha value + * during blending. + * Can be used for efficient fading in/out of overlay rectangles. + * GstElements that render OverlayCompositions and don't support global alpha + * should simply ignore it.*/ + gfloat global_alpha; + + /* track alpha-values already applied: */ + gfloat applied_global_alpha; + /* store initial per-pixel alpha values: */ + guint8 *initial_alpha; + /* FIXME: we may also need a (private) way to cache converted/scaled * pixel blobs */ #if !GLIB_CHECK_VERSION (2, 31, 0) @@ -522,7 +536,7 @@ gst_video_overlay_composition_blend (GstVideoOverlayComposition * comp, video_blend_format_info_init (&rectangle_info, GST_BUFFER_DATA (rect->pixels), rect->height, rect->width, rect->format, - !!(rect->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)); + ! !(rect->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)); needs_scaling = gst_video_overlay_rectangle_needs_scaling (rect); if (needs_scaling) { @@ -530,7 +544,8 @@ gst_video_overlay_composition_blend (GstVideoOverlayComposition * comp, rect->render_width); } - ret = video_blend (&video_info, &rectangle_info, rect->x, rect->y); + ret = video_blend (&video_info, &rectangle_info, rect->x, rect->y, + rect->global_alpha); if (!ret) { GST_WARNING ("Could not blend overlay rectangle onto video buffer"); } @@ -683,6 +698,8 @@ gst_video_overlay_rectangle_finalize (GstMiniObject * mini_obj) rect->scaled_rectangles = g_list_delete_link (rect->scaled_rectangles, rect->scaled_rectangles); } + + g_free (rect->initial_alpha); #if !GLIB_CHECK_VERSION (2, 31, 0) g_static_mutex_free (&rect->lock); #else @@ -714,7 +731,8 @@ static inline gboolean gst_video_overlay_rectangle_check_flags (GstVideoOverlayFormatFlags flags) { /* Check flags only contains flags we know about */ - return (flags & ~(GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)) == 0; + return (flags & ~(GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA | + GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)) == 0; } static gboolean @@ -790,13 +808,18 @@ gst_video_overlay_rectangle_new_argb (GstBuffer * pixels, rect->render_width = render_width; rect->render_height = render_height; + rect->global_alpha = 1.0; + rect->applied_global_alpha = 1.0; + rect->initial_alpha = NULL; + rect->flags = flags; rect->seq_num = gst_video_overlay_get_seqnum (); GST_LOG ("new rectangle %p: %ux%u => %ux%u @ %u,%u, seq_num %u, format %u, " - "flags %x, pixels %p", rect, width, height, render_width, render_height, - render_x, render_y, rect->seq_num, rect->format, rect->flags, pixels); + "flags %x, pixels %p, global_alpha=%f", rect, width, height, render_width, + render_height, render_x, render_y, rect->seq_num, rect->format, + rect->flags, pixels, rect->global_alpha); return rect; } @@ -917,6 +940,70 @@ gst_video_overlay_rectangle_unpremultiply (GstBlendVideoFormatInfo * info) } } + +static gboolean +gst_video_overlay_rectangle_extract_alpha (GstVideoOverlayRectangle * rect) +{ + guint8 *src, *dst; + int offset = 0; + int alpha_size = rect->stride * rect->height / 4; + + g_free (rect->initial_alpha); + rect->initial_alpha = NULL; + + rect->initial_alpha = g_malloc (alpha_size); + src = GST_BUFFER_DATA (rect->pixels); + dst = rect->initial_alpha; + while (offset < alpha_size) { + dst[offset] = src[offset * 4 + ARGB_A]; + ++offset; + } + return TRUE; +} + + +static gboolean +gst_video_overlay_rectangle_apply_global_alpha (GstVideoOverlayRectangle * rect, + float global_alpha) +{ + guint8 *src, *dst; + guint offset = 0; + + g_return_val_if_fail (!(rect->applied_global_alpha != 1.0 + && rect->initial_alpha == NULL), FALSE); + + if (global_alpha == rect->applied_global_alpha) + return TRUE; + + if (rect->initial_alpha == NULL && + !gst_video_overlay_rectangle_extract_alpha (rect)) + return FALSE; + + src = rect->initial_alpha; + rect->pixels = gst_buffer_make_writable (rect->pixels); + dst = GST_BUFFER_DATA (rect->pixels); + while (offset < (rect->height * rect->stride / 4)) { + guint doff = offset * 4; + guint8 na = (guint8) src[offset] * global_alpha; + if (! !(rect->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)) { + dst[doff + ARGB_R] = + (guint8) ((double) (dst[doff + ARGB_R] * 255) / (double) dst[doff + + ARGB_A]) * na / 255; + dst[doff + ARGB_G] = + (guint8) ((double) (dst[doff + ARGB_G] * 255) / (double) dst[doff + + ARGB_A]) * na / 255; + dst[doff + ARGB_B] = + (guint8) ((double) (dst[doff + ARGB_B] * 255) / (double) dst[doff + + ARGB_A]) * na / 255; + } + dst[doff + ARGB_A] = na; + ++offset; + } + + rect->applied_global_alpha = global_alpha; + return TRUE; +} + static GstBuffer * gst_video_overlay_rectangle_get_pixels_argb_internal (GstVideoOverlayRectangle * rectangle, guint * stride, GstVideoOverlayFormatFlags flags, @@ -929,18 +1016,36 @@ gst_video_overlay_rectangle_get_pixels_argb_internal (GstVideoOverlayRectangle * GList *l; guint wanted_width = unscaled ? rectangle->width : rectangle->render_width; guint wanted_height = unscaled ? rectangle->height : rectangle->render_height; + gboolean apply_global_alpha; + gboolean revert_global_alpha; g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); g_return_val_if_fail (stride != NULL, NULL); g_return_val_if_fail (gst_video_overlay_rectangle_check_flags (flags), NULL); + apply_global_alpha = + (! !(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA) + && !(flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)); + revert_global_alpha = + (! !(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA) + && ! !(flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)); + /* This assumes we don't need to adjust the format */ if (wanted_width == rectangle->width && wanted_height == rectangle->height && gst_video_overlay_rectangle_is_same_alpha_type (rectangle->flags, flags)) { - *stride = rectangle->stride; - return rectangle->pixels; + /* don't need to apply/revert global-alpha either: */ + if ((!apply_global_alpha + || rectangle->applied_global_alpha == rectangle->global_alpha) + && (!revert_global_alpha || rectangle->applied_global_alpha == 1.0)) { + *stride = rectangle->stride; + return rectangle->pixels; + } else { + /* only apply/revert global-alpha */ + scaled_rect = rectangle; + goto done; + } } /* see if we've got one cached already */ @@ -962,12 +1067,14 @@ gst_video_overlay_rectangle_get_pixels_argb_internal (GstVideoOverlayRectangle * if (scaled_rect != NULL) goto done; - /* not cached yet, do the scaling and put the result into our cache */ + /* not cached yet, do the preprocessing and put the result into our cache */ video_blend_format_info_init (&info, GST_BUFFER_DATA (rectangle->pixels), rectangle->height, rectangle->width, rectangle->format, - !!(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)); + ! !(rectangle->flags & + GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)); if (wanted_width != rectangle->width || wanted_height != rectangle->height) { + /* we could check the cache for a scaled rect with global_alpha == 1 here */ video_blend_scale_linear_RGBA (&info, wanted_height, wanted_width); } else { /* if we don't have to scale, we have to modify the alpha values, so we @@ -994,7 +1101,9 @@ gst_video_overlay_rectangle_get_pixels_argb_internal (GstVideoOverlayRectangle * scaled_rect = gst_video_overlay_rectangle_new_argb (buf, wanted_width, wanted_height, info.stride[0], 0, 0, wanted_width, wanted_height, new_flags); - + if (rectangle->global_alpha != 1.0) + gst_video_overlay_rectangle_set_global_alpha (scaled_rect, + rectangle->global_alpha); gst_buffer_unref (buf); GST_RECTANGLE_LOCK (rectangle); @@ -1003,17 +1112,33 @@ gst_video_overlay_rectangle_get_pixels_argb_internal (GstVideoOverlayRectangle * GST_RECTANGLE_UNLOCK (rectangle); done: + if (apply_global_alpha + && scaled_rect->applied_global_alpha != rectangle->global_alpha) { + if (!gst_video_overlay_rectangle_apply_global_alpha (scaled_rect, + rectangle->global_alpha)) + return NULL; /* return original data? */ + gst_video_overlay_rectangle_set_global_alpha (scaled_rect, + rectangle->global_alpha); + } else if (revert_global_alpha && scaled_rect->applied_global_alpha != 1.0) { + if (!gst_video_overlay_rectangle_apply_global_alpha (scaled_rect, 1.0)) + return NULL; /* return original data? */ + } *stride = scaled_rect->stride; return scaled_rect->pixels; } + /** * gst_video_overlay_rectangle_get_pixels_argb: * @rectangle: a #GstVideoOverlayRectangle * @stride: (out) (allow-none): address of guint variable where to store the * row stride of the ARGB pixel data in the buffer * @flags: flags + * If a global_alpha value != 1 is set for the rectangle, the caller + * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag + * if he wants to apply global-alpha himself. If the flag is not set + * global_alpha is applied internally before returning the pixel-data. * * Returns: (transfer none): a #GstBuffer holding the ARGB pixel data with * row stride @stride and width and height of the render dimensions as per @@ -1040,7 +1165,11 @@ gst_video_overlay_rectangle_get_pixels_argb (GstVideoOverlayRectangle * * rectangle in pixels * @stride: (out): address of guint variable where to store the row * stride of the ARGB pixel data in the buffer - * @flags: flags + * @flags: flags. + * If a global_alpha value != 1 is set for the rectangle, the caller + * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag + * if he wants to apply global-alpha himself. If the flag is not set + * global_alpha is applied internally before returning the pixel-data. * * Retrieves the pixel data as it is. This is useful if the caller can * do the scaling itself when handling the overlaying. The rectangle will @@ -1092,6 +1221,61 @@ gst_video_overlay_rectangle_get_flags (GstVideoOverlayRectangle * rectangle) return rectangle->flags; } +/** + * gst_video_overlay_rectangle_get_global_alpha: + * @rectangle: a #GstVideoOverlayRectangle + * + * Retrieves the global-alpha value associated with a #GstVideoOverlayRectangle. + * + * Returns: the global-alpha value associated with the rectangle. + * + * Since: 0.10.37 + */ +gfloat +gst_video_overlay_rectangle_get_global_alpha (GstVideoOverlayRectangle * + rectangle) +{ + g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), -1); + + return rectangle->global_alpha; +} + +/** + * gst_video_overlay_rectangle_set_global_alpha: + * @rectangle: a #GstVideoOverlayRectangle + * + * Sets the global alpha value associated with a #GstVideoOverlayRectangle. Per- + * pixel alpha values are multiplied with this value. Valid + * values: 0 <= global_alpha <= 1; 1 to deactivate. + * + # @rectangle must be writable, meaning its refcount must be 1. You can + * make the rectangles inside a #GstVideoOverlayComposition writable using + * gst_video_overlay_composition_make_writable() or + * gst_video_overlay_composition_copy(). + * + * Since: 0.10.37 + */ +void +gst_video_overlay_rectangle_set_global_alpha (GstVideoOverlayRectangle * + rectangle, gfloat global_alpha) +{ + g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); + g_return_if_fail (global_alpha >= 0 && global_alpha <= 1); + + if (rectangle->global_alpha != global_alpha) { + rectangle->global_alpha = global_alpha; + if (global_alpha != 1) + rectangle->flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA; + else + rectangle->flags &= ~GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA; + /* update seq_num automatically to signal the consumer, that data has changed + * note, that this might mislead renderers, that can handle global-alpha + * themselves, because what they want to know is whether the actual pixel data + * has changed. */ + rectangle->seq_num = gst_video_overlay_get_seqnum (); + } +} + /** * gst_video_overlay_rectangle_copy: * @rectangle: (transfer none): a #GstVideoOverlayRectangle to copy @@ -1117,6 +1301,9 @@ gst_video_overlay_rectangle_copy (GstVideoOverlayRectangle * rectangle) rectangle->width, rectangle->height, rectangle->stride, rectangle->x, rectangle->y, rectangle->render_width, rectangle->render_height, rectangle->flags); + if (rectangle->global_alpha != 1) + gst_video_overlay_rectangle_set_global_alpha (copy, + rectangle->global_alpha); return copy; } @@ -1130,6 +1317,16 @@ gst_video_overlay_rectangle_copy (GstVideoOverlayRectangle * rectangle) * (meaning there will never be a rectangle with the same sequence number as * a composition). * + * Using the sequence number of a rectangle as an indicator for changed + * pixel-data of a rectangle is dangereous. Some API calls, like e.g. + * gst_video_overlay_rectangle_set_global_alpha(), automatically update + * the per rectangle sequence number, which is misleading for renderers/ + * consumers, that handle global-alpha themselves. For them the + * pixel-data returned by gst_video_overlay_rectangle_get_pixels_*() + * wont be different for different global-alpha values. In this case a + * renderer could also use the GstBuffer pointers as a hint for changed + * pixel-data. + * * Returns: the sequence number of @rectangle * * Since: 0.10.36 diff --git a/gst-libs/gst/video/video-overlay-composition.h b/gst-libs/gst/video/video-overlay-composition.h index b91404ba91..65538e487c 100644 --- a/gst-libs/gst/video/video-overlay-composition.h +++ b/gst-libs/gst/video/video-overlay-composition.h @@ -96,6 +96,7 @@ gst_video_overlay_rectangle_unref (GstVideoOverlayRectangle * comp) * GstVideoOverlayFormatFlags: * @GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE: no flags * @GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA: RGB are premultiplied by A/255. Since: 0.10.37 + * @GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA: a global-alpha value != 1 is set. Since: 0.10.37 * * Overlay format flags. * @@ -103,7 +104,8 @@ gst_video_overlay_rectangle_unref (GstVideoOverlayRectangle * comp) */ typedef enum { GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE = 0, - GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA = 1 + GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA = 1, + GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA = 2 } GstVideoOverlayFormatFlags; GType gst_video_overlay_rectangle_get_type (void); @@ -142,6 +144,10 @@ GstBuffer * gst_video_overlay_rectangle_get_pixels_unscaled_arg GstVideoOverlayFormatFlags gst_video_overlay_rectangle_get_flags (GstVideoOverlayRectangle * rectangle); +gfloat gst_video_overlay_rectangle_get_global_alpha (GstVideoOverlayRectangle * rectangle); +void gst_video_overlay_rectangle_set_global_alpha (GstVideoOverlayRectangle * rectangle, + gfloat global_alpha); + /** * GstVideoOverlayComposition: *