/src/gstreamer/subprojects/gst-plugins-base/gst-libs/gst/video/video-overlay-composition.c
Line | Count | Source |
1 | | /* GStreamer Video Overlay Composition |
2 | | * Copyright (C) 2011 Intel Corporation |
3 | | * Copyright (C) 2011 Collabora Ltd. |
4 | | * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net> |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Library General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Library General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Library General Public |
17 | | * License along with this library; if not, write to the |
18 | | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
19 | | * Boston, MA 02110-1301, USA. |
20 | | */ |
21 | | |
22 | | /** |
23 | | * SECTION:gstvideooverlaycomposition |
24 | | * @title: GstVideoOverlayRectangle |
25 | | * @short_description: Video Buffer Overlay Compositions (Subtitles, Logos) |
26 | | * |
27 | | * Functions to create and handle overlay compositions on video buffers. |
28 | | * |
29 | | * An overlay composition describes one or more overlay rectangles to be |
30 | | * blended on top of a video buffer. |
31 | | * |
32 | | * This API serves two main purposes: |
33 | | * |
34 | | * * it can be used to attach overlay information (subtitles or logos) |
35 | | * to non-raw video buffers such as GL/VAAPI/VDPAU surfaces. The actual |
36 | | * blending of the overlay can then be done by e.g. the video sink that |
37 | | * processes these non-raw buffers. |
38 | | * |
39 | | * * it can also be used to blend overlay rectangles on top of raw video |
40 | | * buffers, thus consolidating blending functionality for raw video in |
41 | | * one place. |
42 | | * |
43 | | * Together, this allows existing overlay elements to easily handle raw |
44 | | * and non-raw video as input in without major changes (once the overlays |
45 | | * have been put into a #GstVideoOverlayComposition object anyway) - for raw |
46 | | * video the overlay can just use the blending function to blend the data |
47 | | * on top of the video, and for surface buffers it can just attach them to |
48 | | * the buffer and let the sink render the overlays. |
49 | | * |
50 | | */ |
51 | | |
52 | | /* TODO: |
53 | | * - provide accessors for seq_num and other fields (as needed) |
54 | | * - allow overlay to set/get original pango markup string on/from rectangle |
55 | | */ |
56 | | |
57 | | #ifdef HAVE_CONFIG_H |
58 | | #include "config.h" |
59 | | #endif |
60 | | |
61 | | #include "video-overlay-composition.h" |
62 | | #include "video-blend.h" |
63 | | #include "gstvideometa.h" |
64 | | #include <string.h> |
65 | | |
66 | | struct _GstVideoOverlayComposition |
67 | | { |
68 | | GstMiniObject parent; |
69 | | |
70 | | guint num_rectangles; |
71 | | GstVideoOverlayRectangle **rectangles; |
72 | | |
73 | | /* lowest rectangle sequence number still used by the upstream |
74 | | * overlay element. This way a renderer maintaining some kind of |
75 | | * rectangles <-> surface cache can know when to free cached |
76 | | * surfaces/rectangles. */ |
77 | | guint min_seq_num_used; |
78 | | |
79 | | /* sequence number for the composition (same series as rectangles) */ |
80 | | guint seq_num; |
81 | | }; |
82 | | |
83 | | struct _GstVideoOverlayRectangle |
84 | | { |
85 | | GstMiniObject parent; |
86 | | |
87 | | /* Position on video frame and dimension of output rectangle in |
88 | | * output frame terms (already adjusted for the PAR of the output |
89 | | * frame). x/y can be negative (overlay will be clipped then) */ |
90 | | gint x, y; |
91 | | guint render_width, render_height; |
92 | | |
93 | | /* Info on overlay pixels (format, width, height) */ |
94 | | GstVideoInfo info; |
95 | | |
96 | | /* The flags associated to this rectangle */ |
97 | | GstVideoOverlayFormatFlags flags; |
98 | | |
99 | | /* Refcounted blob of memory, no caps or timestamps */ |
100 | | GstBuffer *pixels; |
101 | | |
102 | | /* FIXME: how to express source like text or pango markup? |
103 | | * (just add source type enum + source buffer with data) |
104 | | * |
105 | | * FOR 0.10: always send pixel blobs, but attach source data in |
106 | | * addition (reason: if downstream changes, we can't renegotiate |
107 | | * that properly, if we just do a query of supported formats from |
108 | | * the start). Sink will just ignore pixels and use pango markup |
109 | | * from source data if it supports that. |
110 | | * |
111 | | * FOR 0.11: overlay should query formats (pango markup, pixels) |
112 | | * supported by downstream and then only send that. We can |
113 | | * renegotiate via the reconfigure event. |
114 | | */ |
115 | | |
116 | | /* sequence number: useful for backends/renderers/sinks that want |
117 | | * to maintain a cache of rectangles <-> surfaces. The value of |
118 | | * the min_seq_num_used in the composition tells the renderer which |
119 | | * rectangles have expired. */ |
120 | | guint seq_num; |
121 | | |
122 | | /* global alpha: global alpha value of the rectangle. Each each per-pixel |
123 | | * alpha value of image-data will be multiplied with the global alpha value |
124 | | * during blending. |
125 | | * Can be used for efficient fading in/out of overlay rectangles. |
126 | | * GstElements that render OverlayCompositions and don't support global alpha |
127 | | * should simply ignore it.*/ |
128 | | gfloat global_alpha; |
129 | | |
130 | | /* track alpha-values already applied: */ |
131 | | gfloat applied_global_alpha; |
132 | | /* store initial per-pixel alpha values: */ |
133 | | guint8 *initial_alpha; |
134 | | |
135 | | /* FIXME: we may also need a (private) way to cache converted/scaled |
136 | | * pixel blobs */ |
137 | | GMutex lock; |
138 | | |
139 | | GList *scaled_rectangles; |
140 | | }; |
141 | | |
142 | 0 | #define GST_RECTANGLE_LOCK(rect) g_mutex_lock(&rect->lock) |
143 | 0 | #define GST_RECTANGLE_UNLOCK(rect) g_mutex_unlock(&rect->lock) |
144 | | |
145 | | /* --------------------------- utility functions --------------------------- */ |
146 | | |
147 | | #ifndef GST_DISABLE_GST_DEBUG |
148 | | |
149 | | #define GST_CAT_DEFAULT ensure_debug_category() |
150 | | |
151 | | static GstDebugCategory * |
152 | | ensure_debug_category (void) |
153 | 0 | { |
154 | 0 | static gsize cat_gonce = 0; |
155 | |
|
156 | 0 | if (g_once_init_enter (&cat_gonce)) { |
157 | 0 | gsize cat_done; |
158 | |
|
159 | 0 | cat_done = (gsize) _gst_debug_category_new ("video-composition", 0, |
160 | 0 | "video overlay composition"); |
161 | |
|
162 | 0 | g_once_init_leave (&cat_gonce, cat_done); |
163 | 0 | } |
164 | |
|
165 | 0 | return (GstDebugCategory *) cat_gonce; |
166 | 0 | } |
167 | | |
168 | | #else |
169 | | |
170 | | #define ensure_debug_category() /* NOOP */ |
171 | | |
172 | | #endif /* GST_DISABLE_GST_DEBUG */ |
173 | | |
174 | | static guint |
175 | | gst_video_overlay_get_seqnum (void) |
176 | 0 | { |
177 | 0 | static gint seqnum; /* 0 */ |
178 | |
|
179 | 0 | return (guint) g_atomic_int_add (&seqnum, 1); |
180 | 0 | } |
181 | | |
182 | | static gboolean |
183 | | gst_video_overlay_composition_meta_init (GstMeta * meta, gpointer params, |
184 | | GstBuffer * buf) |
185 | 0 | { |
186 | 0 | GstVideoOverlayCompositionMeta *ometa; |
187 | |
|
188 | 0 | ometa = (GstVideoOverlayCompositionMeta *) meta; |
189 | |
|
190 | 0 | ometa->overlay = NULL; |
191 | |
|
192 | 0 | return TRUE; |
193 | 0 | } |
194 | | |
195 | | static void |
196 | | gst_video_overlay_composition_meta_free (GstMeta * meta, GstBuffer * buf) |
197 | 0 | { |
198 | 0 | GstVideoOverlayCompositionMeta *ometa; |
199 | |
|
200 | 0 | ometa = (GstVideoOverlayCompositionMeta *) meta; |
201 | |
|
202 | 0 | if (ometa->overlay) |
203 | 0 | gst_video_overlay_composition_unref (ometa->overlay); |
204 | 0 | } |
205 | | |
206 | | static gboolean |
207 | | gst_video_overlay_composition_meta_transform (GstBuffer * dest, GstMeta * meta, |
208 | | GstBuffer * buffer, GQuark type, gpointer data) |
209 | 0 | { |
210 | 0 | GstVideoOverlayCompositionMeta *dmeta = NULL, *smeta; |
211 | |
|
212 | 0 | smeta = (GstVideoOverlayCompositionMeta *) meta; |
213 | |
|
214 | 0 | if (GST_META_TRANSFORM_IS_COPY (type)) { |
215 | 0 | GST_DEBUG ("copy video overlay composition metadata"); |
216 | |
|
217 | 0 | dmeta = |
218 | 0 | (GstVideoOverlayCompositionMeta *) gst_buffer_add_meta (dest, |
219 | 0 | GST_VIDEO_OVERLAY_COMPOSITION_META_INFO, NULL); |
220 | 0 | if (!dmeta) |
221 | 0 | return FALSE; |
222 | | |
223 | 0 | dmeta->overlay = gst_video_overlay_composition_ref (smeta->overlay); |
224 | 0 | } else if (GST_VIDEO_META_TRANSFORM_IS_MATRIX (type)) { |
225 | 0 | GstVideoMetaTransformMatrix *trans = data; |
226 | 0 | GstVideoOverlayComposition *new_comp; |
227 | 0 | guint n_rectangles; |
228 | 0 | gboolean have_rect = FALSE; |
229 | |
|
230 | 0 | new_comp = gst_video_overlay_composition_new (NULL); |
231 | 0 | n_rectangles = gst_video_overlay_composition_n_rectangles (smeta->overlay); |
232 | 0 | for (guint i = 0; i < n_rectangles; i++) { |
233 | 0 | GstVideoOverlayRectangle *rect = |
234 | 0 | gst_video_overlay_composition_get_rectangle (smeta->overlay, i); |
235 | 0 | GstVideoOverlayRectangle *new_rect = |
236 | 0 | gst_video_overlay_rectangle_copy (rect); |
237 | 0 | GstVideoRectangle render_rect; |
238 | 0 | guint width = G_MAXINT; |
239 | 0 | guint height = G_MAXINT; |
240 | |
|
241 | 0 | gst_video_overlay_rectangle_get_render_rectangle (rect, &render_rect.x, |
242 | 0 | &render_rect.y, &width, &height); |
243 | 0 | render_rect.w = MIN (width, G_MAXINT); |
244 | 0 | render_rect.h = MIN (height, G_MAXINT); |
245 | |
|
246 | 0 | GstVideoRectangle orig_render_rect = render_rect; |
247 | |
|
248 | 0 | if (gst_video_meta_transform_matrix_rectangle (trans, &render_rect)) { |
249 | |
|
250 | 0 | GST_DEBUG ("overlay rectangle %u (seqnum: %d) (%dx%d)x(%dx%d)" |
251 | 0 | " -> (%dx%d)->(%dx%d)", i, |
252 | 0 | gst_video_overlay_rectangle_get_seqnum (rect), orig_render_rect.x, |
253 | 0 | orig_render_rect.y, orig_render_rect.w, orig_render_rect.h, |
254 | 0 | render_rect.x, render_rect.y, render_rect.w, render_rect.h); |
255 | |
|
256 | 0 | gst_video_overlay_rectangle_set_render_rectangle (new_rect, |
257 | 0 | render_rect.x, render_rect.y, render_rect.w, render_rect.h); |
258 | 0 | gst_video_overlay_composition_add_rectangle (new_comp, new_rect); |
259 | 0 | have_rect = TRUE; |
260 | 0 | } |
261 | 0 | gst_video_overlay_rectangle_unref (new_rect); |
262 | 0 | } |
263 | |
|
264 | 0 | if (have_rect) |
265 | 0 | dmeta = gst_buffer_add_video_overlay_composition_meta (dest, new_comp); |
266 | 0 | gst_video_overlay_composition_unref (new_comp); |
267 | 0 | if (!have_rect || !dmeta) |
268 | 0 | return FALSE; |
269 | 0 | } else if (GST_VIDEO_META_TRANSFORM_IS_SCALE (type)) { |
270 | 0 | GstVideoMetaTransform *trans = data; |
271 | 0 | GstVideoOverlayComposition *new_comp; |
272 | 0 | gint ow, oh, nw, nh; |
273 | 0 | guint n_rectangles; |
274 | |
|
275 | 0 | ow = GST_VIDEO_INFO_WIDTH (trans->in_info); |
276 | 0 | nw = GST_VIDEO_INFO_WIDTH (trans->out_info); |
277 | 0 | oh = GST_VIDEO_INFO_HEIGHT (trans->in_info); |
278 | 0 | nh = GST_VIDEO_INFO_HEIGHT (trans->out_info); |
279 | |
|
280 | 0 | GST_DEBUG ("scaling video overlay composition metadata %dx%d -> %dx%d", ow, |
281 | 0 | oh, nw, nh); |
282 | |
|
283 | 0 | new_comp = gst_video_overlay_composition_new (NULL); |
284 | 0 | n_rectangles = gst_video_overlay_composition_n_rectangles (smeta->overlay); |
285 | 0 | for (guint i = 0; i < n_rectangles; i++) { |
286 | 0 | GstVideoOverlayRectangle *rect = |
287 | 0 | gst_video_overlay_composition_get_rectangle (smeta->overlay, i); |
288 | 0 | GstVideoOverlayRectangle *new_rect = |
289 | 0 | gst_video_overlay_rectangle_copy (rect); |
290 | 0 | gint render_x = 0, render_y = 0; |
291 | 0 | gint new_render_x, new_render_y; |
292 | 0 | guint render_width = 0, render_height = 0; |
293 | 0 | guint new_render_width, new_render_height; |
294 | |
|
295 | 0 | gst_video_overlay_rectangle_get_render_rectangle (rect, &render_x, |
296 | 0 | &render_y, &render_width, &render_height); |
297 | |
|
298 | 0 | new_render_x = (render_x * nw) / ow; |
299 | 0 | new_render_y = (render_y * nh) / oh; |
300 | 0 | new_render_width = (render_width * nw) / ow; |
301 | 0 | new_render_height = (render_height * nh) / oh; |
302 | |
|
303 | 0 | GST_DEBUG |
304 | 0 | ("overlay rectangle %u (seqnum: %d) (%dx%d)x(%ux%u) -> (%dx%d)->(%ux%u)", |
305 | 0 | i, gst_video_overlay_rectangle_get_seqnum (rect), render_x, render_y, |
306 | 0 | render_width, render_height, new_render_x, new_render_y, |
307 | 0 | new_render_width, new_render_height); |
308 | |
|
309 | 0 | gst_video_overlay_rectangle_set_render_rectangle (new_rect, |
310 | 0 | new_render_x, new_render_y, new_render_width, new_render_height); |
311 | 0 | gst_video_overlay_composition_add_rectangle (new_comp, new_rect); |
312 | 0 | gst_video_overlay_rectangle_unref (new_rect); |
313 | 0 | } |
314 | |
|
315 | 0 | dmeta = gst_buffer_add_video_overlay_composition_meta (dest, new_comp); |
316 | 0 | gst_video_overlay_composition_unref (new_comp); |
317 | 0 | if (!dmeta) |
318 | 0 | return FALSE; |
319 | 0 | } else { |
320 | | /* return FALSE, if transform type is not supported */ |
321 | 0 | return FALSE; |
322 | 0 | } |
323 | 0 | return TRUE; |
324 | 0 | } |
325 | | |
326 | | GType |
327 | | gst_video_overlay_composition_meta_api_get_type (void) |
328 | 0 | { |
329 | 0 | static GType type = 0; |
330 | 0 | static const gchar *tags[] = |
331 | 0 | { GST_META_TAG_VIDEO_STR, GST_META_TAG_VIDEO_ORIENTATION_STR, |
332 | 0 | GST_META_TAG_VIDEO_SIZE_STR, NULL |
333 | 0 | }; |
334 | |
|
335 | 0 | if (g_once_init_enter (&type)) { |
336 | 0 | GType _type = |
337 | 0 | gst_meta_api_type_register ("GstVideoOverlayCompositionMetaAPI", tags); |
338 | 0 | g_once_init_leave (&type, _type); |
339 | 0 | } |
340 | 0 | return type; |
341 | 0 | } |
342 | | |
343 | | /* video overlay composition metadata */ |
344 | | const GstMetaInfo * |
345 | | gst_video_overlay_composition_meta_get_info (void) |
346 | 0 | { |
347 | 0 | static const GstMetaInfo *video_overlay_composition_meta_info = NULL; |
348 | |
|
349 | 0 | if (g_once_init_enter ((GstMetaInfo **) & |
350 | 0 | video_overlay_composition_meta_info)) { |
351 | 0 | const GstMetaInfo *meta = |
352 | 0 | gst_meta_register (GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, |
353 | 0 | "GstVideoOverlayCompositionMeta", |
354 | 0 | sizeof (GstVideoOverlayCompositionMeta), |
355 | 0 | (GstMetaInitFunction) gst_video_overlay_composition_meta_init, |
356 | 0 | (GstMetaFreeFunction) gst_video_overlay_composition_meta_free, |
357 | 0 | (GstMetaTransformFunction) |
358 | 0 | gst_video_overlay_composition_meta_transform); |
359 | 0 | g_once_init_leave ((GstMetaInfo **) & video_overlay_composition_meta_info, |
360 | 0 | (GstMetaInfo *) meta); |
361 | 0 | } |
362 | 0 | return video_overlay_composition_meta_info; |
363 | 0 | } |
364 | | |
365 | | /** |
366 | | * gst_buffer_add_video_overlay_composition_meta: |
367 | | * @buf: a #GstBuffer |
368 | | * @comp: (allow-none): a #GstVideoOverlayComposition |
369 | | * |
370 | | * Sets an overlay composition on a buffer. The buffer will obtain its own |
371 | | * reference to the composition, meaning this function does not take ownership |
372 | | * of @comp. |
373 | | * |
374 | | * Returns: (transfer none): a #GstVideoOverlayCompositionMeta |
375 | | */ |
376 | | GstVideoOverlayCompositionMeta * |
377 | | gst_buffer_add_video_overlay_composition_meta (GstBuffer * buf, |
378 | | GstVideoOverlayComposition * comp) |
379 | 0 | { |
380 | 0 | GstVideoOverlayCompositionMeta *ometa; |
381 | |
|
382 | 0 | g_return_val_if_fail (gst_buffer_is_writable (buf), NULL); |
383 | | |
384 | 0 | ometa = (GstVideoOverlayCompositionMeta *) |
385 | 0 | gst_buffer_add_meta (buf, GST_VIDEO_OVERLAY_COMPOSITION_META_INFO, NULL); |
386 | |
|
387 | 0 | ometa->overlay = gst_video_overlay_composition_ref (comp); |
388 | |
|
389 | 0 | return ometa; |
390 | 0 | } |
391 | | |
392 | | /* ------------------------------ composition ------------------------------ */ |
393 | | |
394 | 0 | #define RECTANGLE_ARRAY_STEP 4 /* premature optimization */ |
395 | | |
396 | 2 | GST_DEFINE_MINI_OBJECT_TYPE (GstVideoOverlayComposition, |
397 | 2 | gst_video_overlay_composition); |
398 | 2 | |
399 | 2 | static void |
400 | 2 | gst_video_overlay_composition_free (GstMiniObject * mini_obj) |
401 | 2 | { |
402 | 0 | GstVideoOverlayComposition *comp = (GstVideoOverlayComposition *) mini_obj; |
403 | 0 | guint num; |
404 | |
|
405 | 0 | num = comp->num_rectangles; |
406 | |
|
407 | 0 | while (num > 0) { |
408 | 0 | gst_mini_object_remove_parent (GST_MINI_OBJECT_CAST (comp->rectangles[num - |
409 | 0 | 1]), GST_MINI_OBJECT_CAST (comp)); |
410 | 0 | gst_video_overlay_rectangle_unref (comp->rectangles[num - 1]); |
411 | 0 | --num; |
412 | 0 | } |
413 | |
|
414 | 0 | g_free (comp->rectangles); |
415 | 0 | comp->rectangles = NULL; |
416 | 0 | comp->num_rectangles = 0; |
417 | |
|
418 | 0 | g_free (comp); |
419 | 0 | } |
420 | | |
421 | | /** |
422 | | * gst_video_overlay_composition_new: |
423 | | * @rectangle: (transfer none) (nullable): a #GstVideoOverlayRectangle to add to the |
424 | | * composition |
425 | | * |
426 | | * Creates a new video overlay composition object to hold one or more |
427 | | * overlay rectangles. |
428 | | * |
429 | | * Note that since 1.20 this allows to pass %NULL for @rectangle. |
430 | | * |
431 | | * Returns: (transfer full): a new #GstVideoOverlayComposition. Unref with |
432 | | * gst_video_overlay_composition_unref() when no longer needed. |
433 | | */ |
434 | | GstVideoOverlayComposition * |
435 | | gst_video_overlay_composition_new (GstVideoOverlayRectangle * rectangle) |
436 | 0 | { |
437 | 0 | GstVideoOverlayComposition *comp; |
438 | |
|
439 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle) |
440 | 0 | || rectangle == NULL, NULL); |
441 | | |
442 | 0 | comp = g_new0 (GstVideoOverlayComposition, 1); |
443 | |
|
444 | 0 | gst_mini_object_init (GST_MINI_OBJECT_CAST (comp), 0, |
445 | 0 | GST_TYPE_VIDEO_OVERLAY_COMPOSITION, |
446 | 0 | (GstMiniObjectCopyFunction) gst_video_overlay_composition_copy, |
447 | 0 | NULL, (GstMiniObjectFreeFunction) gst_video_overlay_composition_free); |
448 | |
|
449 | 0 | comp->rectangles = g_new0 (GstVideoOverlayRectangle *, RECTANGLE_ARRAY_STEP); |
450 | |
|
451 | 0 | comp->seq_num = gst_video_overlay_get_seqnum (); |
452 | 0 | comp->min_seq_num_used = comp->seq_num; |
453 | |
|
454 | 0 | GST_LOG ("new composition %p: seq_num %u", comp, comp->seq_num); |
455 | |
|
456 | 0 | if (rectangle) { |
457 | | /* since the rectangle was created earlier, its seqnum is smaller than ours */ |
458 | 0 | comp->min_seq_num_used = rectangle->seq_num; |
459 | 0 | gst_video_overlay_composition_add_rectangle (comp, rectangle); |
460 | 0 | } |
461 | |
|
462 | 0 | return comp; |
463 | 0 | } |
464 | | |
465 | | /** |
466 | | * gst_video_overlay_composition_add_rectangle: |
467 | | * @comp: a #GstVideoOverlayComposition |
468 | | * @rectangle: (transfer none): a #GstVideoOverlayRectangle to add to the |
469 | | * composition |
470 | | * |
471 | | * Adds an overlay rectangle to an existing overlay composition object. This |
472 | | * must be done right after creating the overlay composition. |
473 | | */ |
474 | | void |
475 | | gst_video_overlay_composition_add_rectangle (GstVideoOverlayComposition * comp, |
476 | | GstVideoOverlayRectangle * rectangle) |
477 | 0 | { |
478 | 0 | g_return_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp)); |
479 | 0 | g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); |
480 | 0 | g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (comp))); |
481 | | |
482 | 0 | if (comp->num_rectangles % RECTANGLE_ARRAY_STEP == 0) { |
483 | 0 | comp->rectangles = |
484 | 0 | g_renew (GstVideoOverlayRectangle *, comp->rectangles, |
485 | 0 | comp->num_rectangles + RECTANGLE_ARRAY_STEP); |
486 | 0 | } |
487 | |
|
488 | 0 | comp->rectangles[comp->num_rectangles] = |
489 | 0 | gst_video_overlay_rectangle_ref (rectangle); |
490 | 0 | gst_mini_object_add_parent (GST_MINI_OBJECT_CAST (rectangle), |
491 | 0 | GST_MINI_OBJECT_CAST (comp)); |
492 | 0 | comp->num_rectangles += 1; |
493 | |
|
494 | 0 | comp->min_seq_num_used = MIN (comp->min_seq_num_used, rectangle->seq_num); |
495 | |
|
496 | 0 | GST_LOG ("composition %p: added rectangle %p", comp, rectangle); |
497 | 0 | } |
498 | | |
499 | | /** |
500 | | * gst_video_overlay_composition_n_rectangles: |
501 | | * @comp: a #GstVideoOverlayComposition |
502 | | * |
503 | | * Returns the number of #GstVideoOverlayRectangle<!-- -->s contained in @comp. |
504 | | * |
505 | | * Returns: the number of rectangles |
506 | | */ |
507 | | guint |
508 | | gst_video_overlay_composition_n_rectangles (GstVideoOverlayComposition * comp) |
509 | 0 | { |
510 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), 0); |
511 | | |
512 | 0 | return comp->num_rectangles; |
513 | 0 | } |
514 | | |
515 | | /** |
516 | | * gst_video_overlay_composition_get_rectangle: |
517 | | * @comp: a #GstVideoOverlayComposition |
518 | | * @n: number of the rectangle to get |
519 | | * |
520 | | * Returns the @n-th #GstVideoOverlayRectangle contained in @comp. |
521 | | * |
522 | | * Returns: (transfer none) (nullable): the @n-th rectangle, or NULL if @n is out of |
523 | | * bounds. Will not return a new reference, the caller will need to |
524 | | * obtain her own reference using gst_video_overlay_rectangle_ref() |
525 | | * if needed. |
526 | | */ |
527 | | GstVideoOverlayRectangle * |
528 | | gst_video_overlay_composition_get_rectangle (GstVideoOverlayComposition * comp, |
529 | | guint n) |
530 | 0 | { |
531 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), NULL); |
532 | | |
533 | 0 | if (n >= comp->num_rectangles) |
534 | 0 | return NULL; |
535 | | |
536 | 0 | return comp->rectangles[n]; |
537 | 0 | } |
538 | | |
539 | | static gboolean |
540 | | gst_video_overlay_rectangle_needs_scaling (GstVideoOverlayRectangle * r) |
541 | 0 | { |
542 | 0 | return (GST_VIDEO_INFO_WIDTH (&r->info) != r->render_width || |
543 | 0 | GST_VIDEO_INFO_HEIGHT (&r->info) != r->render_height); |
544 | 0 | } |
545 | | |
546 | | /** |
547 | | * gst_video_overlay_composition_blend: |
548 | | * @comp: a #GstVideoOverlayComposition |
549 | | * @video_buf: a #GstVideoFrame containing raw video data in a |
550 | | * supported format. It should be mapped using GST_MAP_READWRITE |
551 | | * |
552 | | * Blends the overlay rectangles in @comp on top of the raw video data |
553 | | * contained in @video_buf. The data in @video_buf must be writable and |
554 | | * mapped appropriately. |
555 | | * |
556 | | * Since @video_buf data is read and will be modified, it ought be |
557 | | * mapped with flag GST_MAP_READWRITE. |
558 | | */ |
559 | | /* FIXME: formats with more than 8 bit per component which get unpacked into |
560 | | * ARGB64 or AYUV64 (such as v210, v216, UYVP, GRAY16_LE and GRAY16_BE) |
561 | | * are not supported yet by the code in video-blend.c. |
562 | | */ |
563 | | gboolean |
564 | | gst_video_overlay_composition_blend (GstVideoOverlayComposition * comp, |
565 | | GstVideoFrame * video_buf) |
566 | 0 | { |
567 | 0 | GstVideoInfo scaled_info; |
568 | 0 | GstVideoInfo *vinfo; |
569 | 0 | GstVideoFrame rectangle_frame; |
570 | 0 | GstVideoFormat fmt; |
571 | 0 | GstBuffer *pixels = NULL; |
572 | 0 | gboolean ret = TRUE; |
573 | 0 | guint n, num; |
574 | 0 | int w, h; |
575 | |
|
576 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), FALSE); |
577 | 0 | g_return_val_if_fail (video_buf != NULL, FALSE); |
578 | | |
579 | 0 | w = GST_VIDEO_FRAME_WIDTH (video_buf); |
580 | 0 | h = GST_VIDEO_FRAME_HEIGHT (video_buf); |
581 | 0 | fmt = GST_VIDEO_FRAME_FORMAT (video_buf); |
582 | |
|
583 | 0 | num = comp->num_rectangles; |
584 | 0 | GST_LOG ("Blending composition %p with %u rectangles onto video buffer %p " |
585 | 0 | "(%ux%u, format %u)", comp, num, video_buf, w, h, fmt); |
586 | |
|
587 | 0 | for (n = 0; n < num; ++n) { |
588 | 0 | GstVideoOverlayRectangle *rect; |
589 | 0 | gboolean needs_scaling; |
590 | |
|
591 | 0 | rect = comp->rectangles[n]; |
592 | |
|
593 | 0 | GST_LOG (" rectangle %u %p: %ux%u, format %u", n, rect, |
594 | 0 | GST_VIDEO_INFO_WIDTH (&rect->info), GST_VIDEO_INFO_HEIGHT (&rect->info), |
595 | 0 | GST_VIDEO_INFO_FORMAT (&rect->info)); |
596 | |
|
597 | 0 | needs_scaling = gst_video_overlay_rectangle_needs_scaling (rect); |
598 | 0 | if (needs_scaling) { |
599 | 0 | gst_video_blend_scale_linear_RGBA (&rect->info, rect->pixels, |
600 | 0 | rect->render_height, rect->render_width, &scaled_info, &pixels); |
601 | 0 | vinfo = &scaled_info; |
602 | 0 | } else { |
603 | 0 | pixels = gst_buffer_ref (rect->pixels); |
604 | 0 | vinfo = &rect->info; |
605 | 0 | } |
606 | |
|
607 | 0 | gst_video_frame_map (&rectangle_frame, vinfo, pixels, GST_MAP_READ); |
608 | |
|
609 | 0 | ret = gst_video_blend (video_buf, &rectangle_frame, rect->x, rect->y, |
610 | 0 | rect->global_alpha); |
611 | 0 | gst_video_frame_unmap (&rectangle_frame); |
612 | 0 | if (!ret) { |
613 | 0 | GST_WARNING ("Could not blend overlay rectangle onto video buffer"); |
614 | 0 | } |
615 | | |
616 | | /* FIXME: should cache scaled pixels in the rectangle struct */ |
617 | 0 | gst_buffer_unref (pixels); |
618 | 0 | } |
619 | |
|
620 | 0 | return ret; |
621 | 0 | } |
622 | | |
623 | | /** |
624 | | * gst_video_overlay_composition_copy: |
625 | | * @comp: (transfer none): a #GstVideoOverlayComposition to copy |
626 | | * |
627 | | * Makes a copy of @comp and all contained rectangles, so that it is possible |
628 | | * to modify the composition and contained rectangles (e.g. add additional |
629 | | * rectangles or change the render co-ordinates or render dimension). The |
630 | | * actual overlay pixel data buffers contained in the rectangles are not |
631 | | * copied. |
632 | | * |
633 | | * This should be avoided unless rectangles need to be modified because it |
634 | | * invalidates caching in sinks and compositor elements. To add extra rectangles |
635 | | * it is rather recommended to add an extra composition meta using |
636 | | * gst_buffer_add_video_overlay_composition_meta(). |
637 | | * |
638 | | * Returns: (transfer full): a new #GstVideoOverlayComposition equivalent |
639 | | * to @comp. |
640 | | */ |
641 | | GstVideoOverlayComposition * |
642 | | gst_video_overlay_composition_copy (GstVideoOverlayComposition * comp) |
643 | 0 | { |
644 | 0 | GstVideoOverlayComposition *copy; |
645 | 0 | GstVideoOverlayRectangle *rect; |
646 | 0 | guint n; |
647 | |
|
648 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), NULL); |
649 | | |
650 | 0 | if (G_LIKELY (comp->num_rectangles == 0)) |
651 | 0 | return gst_video_overlay_composition_new (NULL); |
652 | | |
653 | 0 | rect = gst_video_overlay_rectangle_copy (comp->rectangles[0]); |
654 | 0 | copy = gst_video_overlay_composition_new (rect); |
655 | 0 | gst_video_overlay_rectangle_unref (rect); |
656 | |
|
657 | 0 | for (n = 1; n < comp->num_rectangles; ++n) { |
658 | 0 | rect = gst_video_overlay_rectangle_copy (comp->rectangles[n]); |
659 | 0 | gst_video_overlay_composition_add_rectangle (copy, rect); |
660 | 0 | gst_video_overlay_rectangle_unref (rect); |
661 | 0 | } |
662 | |
|
663 | 0 | return copy; |
664 | 0 | } |
665 | | |
666 | | /** |
667 | | * gst_video_overlay_composition_make_writable: |
668 | | * @comp: (transfer full): a #GstVideoOverlayComposition to copy |
669 | | * |
670 | | * Takes ownership of @comp and returns a version of @comp that is writable |
671 | | * (i.e. can be modified). Will either return @comp right away, or create a |
672 | | * new writable copy of @comp and unref @comp itself. All the contained |
673 | | * rectangles will also be copied, but the actual overlay pixel data buffers |
674 | | * contained in the rectangles are not copied. |
675 | | * |
676 | | * This should be avoided unless rectangles need to be modified because it |
677 | | * invalidates caching in sinks and compositor elements. To add extra rectangles |
678 | | * it is rather recommended to add an extra composition meta using |
679 | | * gst_buffer_add_video_overlay_composition_meta(). |
680 | | * |
681 | | * Returns: (transfer full): a writable #GstVideoOverlayComposition |
682 | | * equivalent to @comp. |
683 | | */ |
684 | | GstVideoOverlayComposition * |
685 | | gst_video_overlay_composition_make_writable (GstVideoOverlayComposition * comp) |
686 | 0 | { |
687 | 0 | GstVideoOverlayComposition *writable_comp; |
688 | |
|
689 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), NULL); |
690 | | |
691 | 0 | if (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (comp))) { |
692 | 0 | guint n; |
693 | |
|
694 | 0 | for (n = 0; n < comp->num_rectangles; ++n) { |
695 | 0 | if (!gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (comp->rectangles |
696 | 0 | [n]))) |
697 | 0 | goto copy; |
698 | 0 | } |
699 | 0 | return comp; |
700 | 0 | } |
701 | | |
702 | 0 | copy: |
703 | |
|
704 | 0 | writable_comp = gst_video_overlay_composition_copy (comp); |
705 | 0 | gst_video_overlay_composition_unref (comp); |
706 | |
|
707 | 0 | return writable_comp; |
708 | 0 | } |
709 | | |
710 | | /** |
711 | | * gst_video_overlay_composition_get_seqnum: |
712 | | * @comp: a #GstVideoOverlayComposition |
713 | | * |
714 | | * Returns the sequence number of this composition. Sequence numbers are |
715 | | * monotonically increasing and unique for overlay compositions and rectangles |
716 | | * (meaning there will never be a rectangle with the same sequence number as |
717 | | * a composition). |
718 | | * |
719 | | * Returns: the sequence number of @comp |
720 | | */ |
721 | | guint |
722 | | gst_video_overlay_composition_get_seqnum (GstVideoOverlayComposition * comp) |
723 | 0 | { |
724 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), 0); |
725 | | |
726 | 0 | return comp->seq_num; |
727 | 0 | } |
728 | | |
729 | | /* ------------------------------ rectangles ------------------------------ -*/ |
730 | | |
731 | 0 | GST_DEFINE_MINI_OBJECT_TYPE (GstVideoOverlayRectangle, |
732 | 0 | gst_video_overlay_rectangle); |
733 | 0 |
|
734 | 0 | static void |
735 | 0 | gst_video_overlay_rectangle_free (GstMiniObject * mini_obj) |
736 | 0 | { |
737 | 0 | GstVideoOverlayRectangle *rect = (GstVideoOverlayRectangle *) mini_obj; |
738 | |
|
739 | 0 | gst_mini_object_remove_parent (GST_MINI_OBJECT_CAST (rect->pixels), |
740 | 0 | GST_MINI_OBJECT_CAST (rect)); |
741 | 0 | gst_buffer_replace (&rect->pixels, NULL); |
742 | |
|
743 | 0 | while (rect->scaled_rectangles != NULL) { |
744 | 0 | GstVideoOverlayRectangle *scaled_rect = rect->scaled_rectangles->data; |
745 | |
|
746 | 0 | gst_video_overlay_rectangle_unref (scaled_rect); |
747 | |
|
748 | 0 | rect->scaled_rectangles = |
749 | 0 | g_list_delete_link (rect->scaled_rectangles, rect->scaled_rectangles); |
750 | 0 | } |
751 | |
|
752 | 0 | g_free (rect->initial_alpha); |
753 | 0 | g_mutex_clear (&rect->lock); |
754 | |
|
755 | 0 | g_free (rect); |
756 | 0 | } |
757 | | |
758 | | #ifndef G_DISABLE_CHECKS |
759 | | static inline gboolean |
760 | | gst_video_overlay_rectangle_check_flags (GstVideoOverlayFormatFlags flags) |
761 | 0 | { |
762 | | /* Check flags only contains flags we know about */ |
763 | 0 | return (flags & ~(GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA | |
764 | 0 | GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)) == 0; |
765 | 0 | } |
766 | | #endif |
767 | | |
768 | | static gboolean |
769 | | gst_video_overlay_rectangle_is_same_alpha_type (GstVideoOverlayFormatFlags |
770 | | flags1, GstVideoOverlayFormatFlags flags2) |
771 | 0 | { |
772 | 0 | return ((flags1 ^ flags2) & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) |
773 | 0 | == 0; |
774 | 0 | } |
775 | | |
776 | | |
777 | | /** |
778 | | * gst_video_overlay_rectangle_new_raw: |
779 | | * @pixels: (transfer none): a #GstBuffer pointing to the pixel memory |
780 | | * @render_x: the X co-ordinate on the video where the top-left corner of this |
781 | | * overlay rectangle should be rendered to |
782 | | * @render_y: the Y co-ordinate on the video where the top-left corner of this |
783 | | * overlay rectangle should be rendered to |
784 | | * @render_width: the render width of this rectangle on the video |
785 | | * @render_height: the render height of this rectangle on the video |
786 | | * @flags: flags |
787 | | * |
788 | | * Creates a new video overlay rectangle with ARGB or AYUV pixel data. |
789 | | * The layout in case of ARGB of the components in memory is B-G-R-A |
790 | | * on little-endian platforms |
791 | | * (corresponding to #GST_VIDEO_FORMAT_BGRA) and A-R-G-B on big-endian |
792 | | * platforms (corresponding to #GST_VIDEO_FORMAT_ARGB). In other words, |
793 | | * pixels are treated as 32-bit words and the lowest 8 bits then contain |
794 | | * the blue component value and the highest 8 bits contain the alpha |
795 | | * component value. Unless specified in the flags, the RGB values are |
796 | | * non-premultiplied. This is the format that is used by most hardware, |
797 | | * and also many rendering libraries such as Cairo, for example. |
798 | | * The pixel data buffer must have #GstVideoMeta set. |
799 | | * |
800 | | * Returns: (transfer full): a new #GstVideoOverlayRectangle. Unref with |
801 | | * gst_video_overlay_rectangle_unref() when no longer needed. |
802 | | */ |
803 | | GstVideoOverlayRectangle * |
804 | | gst_video_overlay_rectangle_new_raw (GstBuffer * pixels, |
805 | | gint render_x, gint render_y, guint render_width, guint render_height, |
806 | | GstVideoOverlayFormatFlags flags) |
807 | 0 | { |
808 | 0 | GstVideoOverlayRectangle *rect; |
809 | 0 | GstVideoMeta *vmeta; |
810 | 0 | GstVideoFormat format; |
811 | 0 | guint width, height; |
812 | |
|
813 | 0 | g_return_val_if_fail (GST_IS_BUFFER (pixels), NULL); |
814 | 0 | g_return_val_if_fail (render_height > 0 && render_width > 0, NULL); |
815 | 0 | g_return_val_if_fail (gst_video_overlay_rectangle_check_flags (flags), NULL); |
816 | | |
817 | | /* buffer must have video meta with some expected settings */ |
818 | 0 | vmeta = gst_buffer_get_video_meta (pixels); |
819 | 0 | g_return_val_if_fail (vmeta, NULL); |
820 | 0 | g_return_val_if_fail (vmeta->format == |
821 | 0 | GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB || |
822 | 0 | vmeta->format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV, NULL); |
823 | 0 | g_return_val_if_fail (vmeta->flags == GST_VIDEO_FRAME_FLAG_NONE, NULL); |
824 | | |
825 | 0 | format = vmeta->format; |
826 | 0 | width = vmeta->width; |
827 | 0 | height = vmeta->height; |
828 | | |
829 | | /* technically ((height-1)*stride)+width might be okay too */ |
830 | 0 | g_return_val_if_fail (gst_buffer_get_size (pixels) >= height * width * 4, |
831 | 0 | NULL); |
832 | 0 | g_return_val_if_fail (height > 0 && width > 0, NULL); |
833 | | |
834 | 0 | rect = g_new0 (GstVideoOverlayRectangle, 1); |
835 | |
|
836 | 0 | gst_mini_object_init (GST_MINI_OBJECT_CAST (rect), 0, |
837 | 0 | GST_TYPE_VIDEO_OVERLAY_RECTANGLE, |
838 | 0 | (GstMiniObjectCopyFunction) gst_video_overlay_rectangle_copy, |
839 | 0 | NULL, (GstMiniObjectFreeFunction) gst_video_overlay_rectangle_free); |
840 | |
|
841 | 0 | g_mutex_init (&rect->lock); |
842 | |
|
843 | 0 | rect->pixels = gst_buffer_ref (pixels); |
844 | 0 | gst_mini_object_add_parent (GST_MINI_OBJECT_CAST (pixels), |
845 | 0 | GST_MINI_OBJECT_CAST (rect)); |
846 | 0 | rect->scaled_rectangles = NULL; |
847 | |
|
848 | 0 | gst_video_info_init (&rect->info); |
849 | 0 | if (!gst_video_info_set_format (&rect->info, format, width, height)) { |
850 | 0 | gst_mini_object_unref (GST_MINI_OBJECT_CAST (rect)); |
851 | 0 | return NULL; |
852 | 0 | } |
853 | 0 | if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) |
854 | 0 | rect->info.flags |= GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA; |
855 | |
|
856 | 0 | rect->x = render_x; |
857 | 0 | rect->y = render_y; |
858 | 0 | rect->render_width = render_width; |
859 | 0 | rect->render_height = render_height; |
860 | |
|
861 | 0 | rect->global_alpha = 1.0; |
862 | 0 | rect->applied_global_alpha = 1.0; |
863 | 0 | rect->initial_alpha = NULL; |
864 | |
|
865 | 0 | rect->flags = flags; |
866 | |
|
867 | 0 | rect->seq_num = gst_video_overlay_get_seqnum (); |
868 | |
|
869 | 0 | GST_LOG ("new rectangle %p: %ux%u => %ux%u @ %u,%u, seq_num %u, format %u, " |
870 | 0 | "flags %x, pixels %p, global_alpha=%f", rect, width, height, render_width, |
871 | 0 | render_height, render_x, render_y, rect->seq_num, format, |
872 | 0 | rect->flags, pixels, rect->global_alpha); |
873 | |
|
874 | 0 | return rect; |
875 | 0 | } |
876 | | |
877 | | /** |
878 | | * gst_video_overlay_rectangle_get_render_rectangle: |
879 | | * @rectangle: a #GstVideoOverlayRectangle |
880 | | * @render_x: (out) (allow-none): address where to store the X render offset |
881 | | * @render_y: (out) (allow-none): address where to store the Y render offset |
882 | | * @render_width: (out) (allow-none): address where to store the render width |
883 | | * @render_height: (out) (allow-none): address where to store the render height |
884 | | * |
885 | | * Retrieves the render position and render dimension of the overlay |
886 | | * rectangle on the video. |
887 | | * |
888 | | * Returns: TRUE if valid render dimensions were retrieved. |
889 | | */ |
890 | | gboolean |
891 | | gst_video_overlay_rectangle_get_render_rectangle (GstVideoOverlayRectangle * |
892 | | rectangle, gint * render_x, gint * render_y, guint * render_width, |
893 | | guint * render_height) |
894 | 0 | { |
895 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), FALSE); |
896 | | |
897 | 0 | if (render_x) |
898 | 0 | *render_x = rectangle->x; |
899 | 0 | if (render_y) |
900 | 0 | *render_y = rectangle->y; |
901 | 0 | if (render_width) |
902 | 0 | *render_width = rectangle->render_width; |
903 | 0 | if (render_height) |
904 | 0 | *render_height = rectangle->render_height; |
905 | |
|
906 | 0 | return TRUE; |
907 | 0 | } |
908 | | |
909 | | /** |
910 | | * gst_video_overlay_rectangle_set_render_rectangle: |
911 | | * @rectangle: a #GstVideoOverlayRectangle |
912 | | * @render_x: render X position of rectangle on video |
913 | | * @render_y: render Y position of rectangle on video |
914 | | * @render_width: render width of rectangle |
915 | | * @render_height: render height of rectangle |
916 | | * |
917 | | * Sets the render position and dimensions of the rectangle on the video. |
918 | | * This function is mainly for elements that modify the size of the video |
919 | | * in some way (e.g. through scaling or cropping) and need to adjust the |
920 | | * details of any overlays to match the operation that changed the size. |
921 | | * |
922 | | * @rectangle must be writable, meaning its refcount must be 1. You can |
923 | | * make the rectangles inside a #GstVideoOverlayComposition writable using |
924 | | * gst_video_overlay_composition_make_writable() or |
925 | | * gst_video_overlay_composition_copy(). |
926 | | */ |
927 | | void |
928 | | gst_video_overlay_rectangle_set_render_rectangle (GstVideoOverlayRectangle * |
929 | | rectangle, gint render_x, gint render_y, guint render_width, |
930 | | guint render_height) |
931 | 0 | { |
932 | 0 | g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); |
933 | 0 | g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST |
934 | 0 | (rectangle))); |
935 | | |
936 | 0 | rectangle->x = render_x; |
937 | 0 | rectangle->y = render_y; |
938 | 0 | rectangle->render_width = render_width; |
939 | 0 | rectangle->render_height = render_height; |
940 | 0 | } |
941 | | |
942 | | /* FIXME: orc-ify */ |
943 | | static void |
944 | | gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame) |
945 | 0 | { |
946 | 0 | int i, j; |
947 | 0 | int width = GST_VIDEO_FRAME_WIDTH (frame); |
948 | 0 | int height = GST_VIDEO_FRAME_HEIGHT (frame); |
949 | 0 | int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); |
950 | 0 | guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
951 | |
|
952 | 0 | for (j = 0; j < height; ++j) { |
953 | 0 | guint8 *line; |
954 | |
|
955 | 0 | line = data; |
956 | 0 | line += stride * j; |
957 | 0 | for (i = 0; i < width; ++i) { |
958 | 0 | int a = line[0]; |
959 | 0 | line[1] = line[1] * a / 255; |
960 | 0 | line[2] = line[2] * a / 255; |
961 | 0 | line[3] = line[3] * a / 255; |
962 | 0 | line += 4; |
963 | 0 | } |
964 | 0 | } |
965 | 0 | } |
966 | | |
967 | | static void |
968 | | gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame) |
969 | 0 | { |
970 | 0 | int i, j; |
971 | 0 | int width = GST_VIDEO_FRAME_WIDTH (frame); |
972 | 0 | int height = GST_VIDEO_FRAME_HEIGHT (frame); |
973 | 0 | int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); |
974 | 0 | guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
975 | |
|
976 | 0 | for (j = 0; j < height; ++j) { |
977 | 0 | guint8 *line; |
978 | |
|
979 | 0 | line = data; |
980 | 0 | line += stride * j; |
981 | 0 | for (i = 0; i < width; ++i) { |
982 | 0 | int a = line[3]; |
983 | 0 | line[0] = line[0] * a / 255; |
984 | 0 | line[1] = line[1] * a / 255; |
985 | 0 | line[2] = line[2] * a / 255; |
986 | 0 | line += 4; |
987 | 0 | } |
988 | 0 | } |
989 | 0 | } |
990 | | |
991 | | static void |
992 | | gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame) |
993 | 0 | { |
994 | 0 | gint alpha_offset; |
995 | |
|
996 | 0 | alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3); |
997 | 0 | switch (alpha_offset) { |
998 | 0 | case 0: |
999 | 0 | gst_video_overlay_rectangle_premultiply_0 (frame); |
1000 | 0 | break; |
1001 | 0 | case 3: |
1002 | 0 | gst_video_overlay_rectangle_premultiply_3 (frame); |
1003 | 0 | break; |
1004 | 0 | default: |
1005 | 0 | g_assert_not_reached (); |
1006 | 0 | break; |
1007 | 0 | } |
1008 | 0 | } |
1009 | | |
1010 | | /* FIXME: orc-ify */ |
1011 | | static void |
1012 | | gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame) |
1013 | 0 | { |
1014 | 0 | int i, j; |
1015 | 0 | int width = GST_VIDEO_FRAME_WIDTH (frame); |
1016 | 0 | int height = GST_VIDEO_FRAME_HEIGHT (frame); |
1017 | 0 | int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); |
1018 | 0 | guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
1019 | |
|
1020 | 0 | for (j = 0; j < height; ++j) { |
1021 | 0 | guint8 *line; |
1022 | |
|
1023 | 0 | line = data; |
1024 | 0 | line += stride * j; |
1025 | 0 | for (i = 0; i < width; ++i) { |
1026 | 0 | int a = line[0]; |
1027 | 0 | if (a) { |
1028 | 0 | line[1] = MIN ((line[1] * 255 + a / 2) / a, 255); |
1029 | 0 | line[2] = MIN ((line[2] * 255 + a / 2) / a, 255); |
1030 | 0 | line[3] = MIN ((line[3] * 255 + a / 2) / a, 255); |
1031 | 0 | } |
1032 | 0 | line += 4; |
1033 | 0 | } |
1034 | 0 | } |
1035 | 0 | } |
1036 | | |
1037 | | static void |
1038 | | gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame) |
1039 | 0 | { |
1040 | 0 | int i, j; |
1041 | 0 | int width = GST_VIDEO_FRAME_WIDTH (frame); |
1042 | 0 | int height = GST_VIDEO_FRAME_HEIGHT (frame); |
1043 | 0 | int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); |
1044 | 0 | guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
1045 | |
|
1046 | 0 | for (j = 0; j < height; ++j) { |
1047 | 0 | guint8 *line; |
1048 | |
|
1049 | 0 | line = data; |
1050 | 0 | line += stride * j; |
1051 | 0 | for (i = 0; i < width; ++i) { |
1052 | 0 | int a = line[3]; |
1053 | 0 | if (a) { |
1054 | 0 | line[0] = MIN ((line[0] * 255 + a / 2) / a, 255); |
1055 | 0 | line[1] = MIN ((line[1] * 255 + a / 2) / a, 255); |
1056 | 0 | line[2] = MIN ((line[2] * 255 + a / 2) / a, 255); |
1057 | 0 | } |
1058 | 0 | line += 4; |
1059 | 0 | } |
1060 | 0 | } |
1061 | 0 | } |
1062 | | |
1063 | | static void |
1064 | | gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame) |
1065 | 0 | { |
1066 | 0 | gint alpha_offset; |
1067 | |
|
1068 | 0 | alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3); |
1069 | 0 | switch (alpha_offset) { |
1070 | 0 | case 0: |
1071 | 0 | gst_video_overlay_rectangle_unpremultiply_0 (frame); |
1072 | 0 | break; |
1073 | 0 | case 3: |
1074 | 0 | gst_video_overlay_rectangle_unpremultiply_3 (frame); |
1075 | 0 | break; |
1076 | 0 | default: |
1077 | 0 | g_assert_not_reached (); |
1078 | 0 | break; |
1079 | 0 | } |
1080 | 0 | } |
1081 | | |
1082 | | |
1083 | | static void |
1084 | | gst_video_overlay_rectangle_extract_alpha (GstVideoOverlayRectangle * rect) |
1085 | 0 | { |
1086 | 0 | guint8 *src, *dst; |
1087 | 0 | GstVideoFrame frame; |
1088 | 0 | gint i, j, w, h, stride, alpha_offset; |
1089 | |
|
1090 | 0 | alpha_offset = GST_VIDEO_INFO_COMP_POFFSET (&rect->info, 3); |
1091 | 0 | g_return_if_fail (alpha_offset == 0 || alpha_offset == 3); |
1092 | | |
1093 | 0 | gst_video_frame_map (&frame, &rect->info, rect->pixels, GST_MAP_READ); |
1094 | 0 | src = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0); |
1095 | 0 | w = GST_VIDEO_INFO_WIDTH (&rect->info); |
1096 | 0 | h = GST_VIDEO_INFO_HEIGHT (&rect->info); |
1097 | 0 | stride = GST_VIDEO_INFO_PLANE_STRIDE (&rect->info, 0); |
1098 | |
|
1099 | 0 | g_free (rect->initial_alpha); |
1100 | 0 | rect->initial_alpha = g_malloc (w * h); |
1101 | 0 | dst = rect->initial_alpha; |
1102 | |
|
1103 | 0 | for (i = 0; i < h; i++) { |
1104 | 0 | for (j = 0; j < w; j++) { |
1105 | 0 | *dst = src[alpha_offset]; |
1106 | 0 | dst++; |
1107 | 0 | src += 4; |
1108 | 0 | } |
1109 | 0 | src += stride - 4 * w; |
1110 | 0 | } |
1111 | 0 | gst_video_frame_unmap (&frame); |
1112 | 0 | } |
1113 | | |
1114 | | |
1115 | | static void |
1116 | | gst_video_overlay_rectangle_apply_global_alpha (GstVideoOverlayRectangle * rect, |
1117 | | float global_alpha) |
1118 | 0 | { |
1119 | 0 | guint8 *src, *dst; |
1120 | 0 | GstVideoFrame frame; |
1121 | 0 | gint i, j, w, h, stride; |
1122 | 0 | gint argb_a, argb_r, argb_g, argb_b; |
1123 | 0 | gint alpha_offset GST_UNUSED_CHECKS; |
1124 | |
|
1125 | 0 | g_assert (!(rect->applied_global_alpha != 1.0 |
1126 | 0 | && rect->initial_alpha == NULL)); |
1127 | | |
1128 | 0 | #ifndef G_DISABLE_CHECKS |
1129 | 0 | alpha_offset = GST_VIDEO_INFO_COMP_POFFSET (&rect->info, 3); |
1130 | 0 | g_return_if_fail (alpha_offset == 0 || alpha_offset == 3); |
1131 | 0 | #endif |
1132 | | |
1133 | 0 | if (global_alpha == rect->applied_global_alpha) |
1134 | 0 | return; |
1135 | | |
1136 | 0 | if (rect->initial_alpha == NULL) |
1137 | 0 | gst_video_overlay_rectangle_extract_alpha (rect); |
1138 | |
|
1139 | 0 | src = rect->initial_alpha; |
1140 | 0 | if (!gst_buffer_is_writable (rect->pixels)) { |
1141 | 0 | gst_mini_object_remove_parent (GST_MINI_OBJECT_CAST (rect->pixels), |
1142 | 0 | GST_MINI_OBJECT_CAST (rect)); |
1143 | 0 | rect->pixels = gst_buffer_copy (rect->pixels); |
1144 | 0 | gst_mini_object_add_parent (GST_MINI_OBJECT_CAST (rect->pixels), |
1145 | 0 | GST_MINI_OBJECT_CAST (rect)); |
1146 | 0 | } |
1147 | |
|
1148 | 0 | gst_video_frame_map (&frame, &rect->info, rect->pixels, GST_MAP_READ); |
1149 | 0 | dst = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0); |
1150 | 0 | w = GST_VIDEO_INFO_WIDTH (&rect->info); |
1151 | 0 | h = GST_VIDEO_INFO_HEIGHT (&rect->info); |
1152 | 0 | stride = GST_VIDEO_INFO_PLANE_STRIDE (&rect->info, 0); |
1153 | |
|
1154 | 0 | argb_a = GST_VIDEO_INFO_COMP_POFFSET (&rect->info, 3); |
1155 | 0 | argb_r = (argb_a + 1) % 4; |
1156 | 0 | argb_g = (argb_a + 2) % 4; |
1157 | 0 | argb_b = (argb_a + 3) % 4; |
1158 | |
|
1159 | 0 | for (i = 0; i < h; i++) { |
1160 | 0 | for (j = 0; j < w; j++) { |
1161 | 0 | guint8 na = (guint8) (*src * global_alpha); |
1162 | |
|
1163 | 0 | if (!!(rect->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)) { |
1164 | 0 | dst[argb_r] = |
1165 | 0 | (guint8) ((double) (dst[argb_r] * 255) / (double) dst[argb_a]) * |
1166 | 0 | na / 255; |
1167 | 0 | dst[argb_g] = |
1168 | 0 | (guint8) ((double) (dst[argb_g] * 255) / (double) dst[argb_a]) * |
1169 | 0 | na / 255; |
1170 | 0 | dst[argb_b] = |
1171 | 0 | (guint8) ((double) (dst[argb_b] * 255) / (double) dst[argb_a]) * |
1172 | 0 | na / 255; |
1173 | 0 | } |
1174 | 0 | dst[argb_a] = na; |
1175 | 0 | src++; |
1176 | 0 | dst += 4; |
1177 | 0 | } |
1178 | 0 | dst += stride - 4 * w; |
1179 | 0 | } |
1180 | 0 | gst_video_frame_unmap (&frame); |
1181 | |
|
1182 | 0 | rect->applied_global_alpha = global_alpha; |
1183 | 0 | } |
1184 | | |
1185 | | static void |
1186 | | gst_video_overlay_rectangle_convert (const GstVideoInfo * src, |
1187 | | GstBuffer * src_buffer, GstVideoFormat dest_format, GstVideoInfo * dest, |
1188 | | GstBuffer ** dest_buffer) |
1189 | 0 | { |
1190 | 0 | gint width, height, stride; |
1191 | 0 | GstVideoFrame src_frame, dest_frame; |
1192 | 0 | GstVideoFormat format; |
1193 | 0 | gint k, l; |
1194 | 0 | guint8 *sdata, *ddata; |
1195 | |
|
1196 | 0 | format = GST_VIDEO_INFO_FORMAT (src); |
1197 | |
|
1198 | 0 | width = GST_VIDEO_INFO_WIDTH (src); |
1199 | 0 | height = GST_VIDEO_INFO_HEIGHT (src); |
1200 | |
|
1201 | 0 | gst_video_info_init (dest); |
1202 | 0 | if (!gst_video_info_set_format (dest, dest_format, width, height)) { |
1203 | 0 | g_warn_if_reached (); |
1204 | 0 | return; |
1205 | 0 | } |
1206 | | |
1207 | 0 | *dest_buffer = gst_buffer_new_and_alloc (GST_VIDEO_INFO_SIZE (dest)); |
1208 | |
|
1209 | 0 | gst_video_frame_map (&src_frame, src, src_buffer, GST_MAP_READ); |
1210 | 0 | gst_video_frame_map (&dest_frame, dest, *dest_buffer, GST_MAP_WRITE); |
1211 | |
|
1212 | 0 | sdata = GST_VIDEO_FRAME_PLANE_DATA (&src_frame, 0); |
1213 | 0 | ddata = GST_VIDEO_FRAME_PLANE_DATA (&dest_frame, 0); |
1214 | 0 | stride = GST_VIDEO_FRAME_PLANE_STRIDE (&src_frame, 0); |
1215 | |
|
1216 | 0 | if (format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV && |
1217 | 0 | dest_format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB) { |
1218 | 0 | gint ayuv; |
1219 | 0 | gint a, y, u, v, r, g, b; |
1220 | |
|
1221 | 0 | for (k = 0; k < height; k++) { |
1222 | 0 | for (l = 0; l < width; l++) { |
1223 | 0 | ayuv = GST_READ_UINT32_BE (sdata); |
1224 | 0 | a = ayuv >> 24; |
1225 | 0 | y = (ayuv >> 16) & 0xff; |
1226 | 0 | u = (ayuv >> 8) & 0xff; |
1227 | 0 | v = (ayuv & 0xff); |
1228 | |
|
1229 | 0 | r = (298 * y + 459 * v - 63514) >> 8; |
1230 | 0 | g = (298 * y - 55 * u - 136 * v + 19681) >> 8; |
1231 | 0 | b = (298 * y + 541 * u - 73988) >> 8; |
1232 | |
|
1233 | 0 | r = CLAMP (r, 0, 255); |
1234 | 0 | g = CLAMP (g, 0, 255); |
1235 | 0 | b = CLAMP (b, 0, 255); |
1236 | | |
1237 | | /* native endian ARGB */ |
1238 | 0 | *(guint32 *) ddata = ((a << 24) | (r << 16) | (g << 8) | b); |
1239 | |
|
1240 | 0 | sdata += 4; |
1241 | 0 | ddata += 4; |
1242 | 0 | } |
1243 | 0 | sdata += stride - 4 * width; |
1244 | 0 | } |
1245 | 0 | } else if (format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB && |
1246 | 0 | dest_format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV) { |
1247 | 0 | gint argb; |
1248 | 0 | gint a, y, u, v, r, g, b; |
1249 | |
|
1250 | 0 | for (k = 0; k < height; k++) { |
1251 | 0 | for (l = 0; l < width; l++) { |
1252 | | /* native endian ARGB */ |
1253 | 0 | argb = *(guint32 *) sdata; |
1254 | 0 | a = argb >> 24; |
1255 | 0 | r = (argb >> 16) & 0xff; |
1256 | 0 | g = (argb >> 8) & 0xff; |
1257 | 0 | b = (argb & 0xff); |
1258 | |
|
1259 | 0 | y = (47 * r + 157 * g + 16 * b + 4096) >> 8; |
1260 | 0 | u = (-26 * r - 87 * g + 112 * b + 32768) >> 8; |
1261 | 0 | v = (112 * r - 102 * g - 10 * b + 32768) >> 8; |
1262 | |
|
1263 | 0 | y = CLAMP (y, 0, 255); |
1264 | 0 | u = CLAMP (u, 0, 255); |
1265 | 0 | v = CLAMP (v, 0, 255); |
1266 | |
|
1267 | 0 | GST_WRITE_UINT32_BE (ddata, ((a << 24) | (y << 16) | (u << 8) | v)); |
1268 | |
|
1269 | 0 | sdata += 4; |
1270 | 0 | ddata += 4; |
1271 | 0 | } |
1272 | 0 | sdata += stride - 4 * width; |
1273 | 0 | } |
1274 | 0 | } else { |
1275 | 0 | GST_ERROR ("unsupported conversion"); |
1276 | 0 | g_assert_not_reached (); |
1277 | 0 | } |
1278 | | |
1279 | 0 | gst_video_frame_unmap (&src_frame); |
1280 | 0 | gst_video_frame_unmap (&dest_frame); |
1281 | 0 | } |
1282 | | |
1283 | | static GstBuffer * |
1284 | | gst_video_overlay_rectangle_get_pixels_raw_internal (GstVideoOverlayRectangle * |
1285 | | rectangle, GstVideoOverlayFormatFlags flags, gboolean unscaled, |
1286 | | GstVideoFormat wanted_format) |
1287 | 0 | { |
1288 | 0 | GstVideoOverlayFormatFlags new_flags; |
1289 | 0 | GstVideoOverlayRectangle *scaled_rect = NULL, *conv_rect = NULL; |
1290 | 0 | GstVideoInfo info; |
1291 | 0 | GstVideoFrame frame; |
1292 | 0 | GstBuffer *buf; |
1293 | 0 | GList *l; |
1294 | 0 | guint width, height; |
1295 | 0 | guint wanted_width; |
1296 | 0 | guint wanted_height; |
1297 | 0 | gboolean apply_global_alpha; |
1298 | 0 | gboolean revert_global_alpha; |
1299 | 0 | GstVideoFormat format; |
1300 | |
|
1301 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
1302 | 0 | g_return_val_if_fail (gst_video_overlay_rectangle_check_flags (flags), NULL); |
1303 | | |
1304 | 0 | width = GST_VIDEO_INFO_WIDTH (&rectangle->info); |
1305 | 0 | height = GST_VIDEO_INFO_HEIGHT (&rectangle->info); |
1306 | 0 | wanted_width = unscaled ? width : rectangle->render_width; |
1307 | 0 | wanted_height = unscaled ? height : rectangle->render_height; |
1308 | 0 | format = GST_VIDEO_INFO_FORMAT (&rectangle->info); |
1309 | |
|
1310 | 0 | apply_global_alpha = |
1311 | 0 | (!!(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA) |
1312 | 0 | && !(flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)); |
1313 | 0 | revert_global_alpha = |
1314 | 0 | (!!(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA) |
1315 | 0 | && !!(flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)); |
1316 | | |
1317 | | /* This assumes we don't need to adjust the format */ |
1318 | 0 | if (wanted_width == width && |
1319 | 0 | wanted_height == height && |
1320 | 0 | wanted_format == format && |
1321 | 0 | gst_video_overlay_rectangle_is_same_alpha_type (rectangle->flags, |
1322 | 0 | flags)) { |
1323 | | /* don't need to apply/revert global-alpha either: */ |
1324 | 0 | if ((!apply_global_alpha |
1325 | 0 | || rectangle->applied_global_alpha == rectangle->global_alpha) |
1326 | 0 | && (!revert_global_alpha || rectangle->applied_global_alpha == 1.0)) { |
1327 | 0 | return rectangle->pixels; |
1328 | 0 | } else { |
1329 | | /* only apply/revert global-alpha */ |
1330 | 0 | scaled_rect = rectangle; |
1331 | 0 | goto done; |
1332 | 0 | } |
1333 | 0 | } |
1334 | | |
1335 | | /* see if we've got one cached already */ |
1336 | 0 | GST_RECTANGLE_LOCK (rectangle); |
1337 | 0 | for (l = rectangle->scaled_rectangles; l != NULL; l = l->next) { |
1338 | 0 | GstVideoOverlayRectangle *r = l->data; |
1339 | |
|
1340 | 0 | if (GST_VIDEO_INFO_WIDTH (&r->info) == wanted_width && |
1341 | 0 | GST_VIDEO_INFO_HEIGHT (&r->info) == wanted_height && |
1342 | 0 | GST_VIDEO_INFO_FORMAT (&r->info) == wanted_format && |
1343 | 0 | gst_video_overlay_rectangle_is_same_alpha_type (r->flags, flags)) { |
1344 | | /* we'll keep these rectangles around until finalize, so it's ok not |
1345 | | * to take our own ref here */ |
1346 | 0 | scaled_rect = r; |
1347 | 0 | break; |
1348 | 0 | } |
1349 | 0 | } |
1350 | 0 | GST_RECTANGLE_UNLOCK (rectangle); |
1351 | |
|
1352 | 0 | if (scaled_rect != NULL) |
1353 | 0 | goto done; |
1354 | | |
1355 | | /* maybe have one in the right format though */ |
1356 | 0 | if (format != wanted_format) { |
1357 | 0 | GST_RECTANGLE_LOCK (rectangle); |
1358 | 0 | for (l = rectangle->scaled_rectangles; l != NULL; l = l->next) { |
1359 | 0 | GstVideoOverlayRectangle *r = l->data; |
1360 | |
|
1361 | 0 | if (GST_VIDEO_INFO_FORMAT (&r->info) == wanted_format && |
1362 | 0 | gst_video_overlay_rectangle_is_same_alpha_type (r->flags, flags)) { |
1363 | | /* we'll keep these rectangles around until finalize, so it's ok not |
1364 | | * to take our own ref here */ |
1365 | 0 | conv_rect = r; |
1366 | 0 | break; |
1367 | 0 | } |
1368 | 0 | } |
1369 | 0 | GST_RECTANGLE_UNLOCK (rectangle); |
1370 | 0 | } else { |
1371 | 0 | conv_rect = rectangle; |
1372 | 0 | } |
1373 | |
|
1374 | 0 | if (conv_rect == NULL) { |
1375 | 0 | GstVideoInfo conv_info; |
1376 | |
|
1377 | 0 | gst_video_overlay_rectangle_convert (&rectangle->info, rectangle->pixels, |
1378 | 0 | wanted_format, &conv_info, &buf); |
1379 | 0 | gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE, |
1380 | 0 | GST_VIDEO_INFO_FORMAT (&conv_info), width, height); |
1381 | 0 | conv_rect = gst_video_overlay_rectangle_new_raw (buf, |
1382 | 0 | 0, 0, width, height, rectangle->flags); |
1383 | 0 | if (rectangle->global_alpha != 1.0) |
1384 | 0 | gst_video_overlay_rectangle_set_global_alpha (scaled_rect, |
1385 | 0 | rectangle->global_alpha); |
1386 | 0 | gst_buffer_unref (buf); |
1387 | | /* keep this converted one around as well in any case */ |
1388 | 0 | GST_RECTANGLE_LOCK (rectangle); |
1389 | 0 | rectangle->scaled_rectangles = |
1390 | 0 | g_list_prepend (rectangle->scaled_rectangles, conv_rect); |
1391 | 0 | GST_RECTANGLE_UNLOCK (rectangle); |
1392 | 0 | } |
1393 | | |
1394 | | /* now we continue from conv_rect */ |
1395 | 0 | width = GST_VIDEO_INFO_WIDTH (&conv_rect->info); |
1396 | 0 | height = GST_VIDEO_INFO_HEIGHT (&conv_rect->info); |
1397 | 0 | format = GST_VIDEO_INFO_FORMAT (&conv_rect->info); |
1398 | | |
1399 | | /* not cached yet, do the preprocessing and put the result into our cache */ |
1400 | 0 | if (wanted_width != width || wanted_height != height) { |
1401 | 0 | GstVideoInfo scaled_info; |
1402 | | |
1403 | | /* we could check the cache for a scaled rect with global_alpha == 1 here */ |
1404 | 0 | gst_video_blend_scale_linear_RGBA (&conv_rect->info, conv_rect->pixels, |
1405 | 0 | wanted_height, wanted_width, &scaled_info, &buf); |
1406 | 0 | info = scaled_info; |
1407 | 0 | gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE, |
1408 | 0 | GST_VIDEO_INFO_FORMAT (&conv_rect->info), wanted_width, wanted_height); |
1409 | 0 | } else if (!gst_video_overlay_rectangle_is_same_alpha_type (conv_rect->flags, |
1410 | 0 | flags)) { |
1411 | | /* if we don't have to scale, we have to modify the alpha values, so we |
1412 | | * need to make a copy of the pixel memory (and we take ownership below) */ |
1413 | 0 | buf = gst_buffer_copy (conv_rect->pixels); |
1414 | 0 | info = conv_rect->info; |
1415 | 0 | } else { |
1416 | | /* do not need to scale or modify alpha values, almost done then */ |
1417 | 0 | scaled_rect = conv_rect; |
1418 | 0 | goto done; |
1419 | 0 | } |
1420 | | |
1421 | 0 | new_flags = conv_rect->flags; |
1422 | 0 | gst_video_frame_map (&frame, &info, buf, GST_MAP_READWRITE); |
1423 | 0 | if (!gst_video_overlay_rectangle_is_same_alpha_type (conv_rect->flags, flags)) { |
1424 | 0 | if (rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) { |
1425 | 0 | gst_video_overlay_rectangle_unpremultiply (&frame); |
1426 | 0 | new_flags &= ~GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA; |
1427 | 0 | } else { |
1428 | 0 | gst_video_overlay_rectangle_premultiply (&frame); |
1429 | 0 | new_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA; |
1430 | 0 | } |
1431 | 0 | } |
1432 | 0 | gst_video_frame_unmap (&frame); |
1433 | |
|
1434 | 0 | scaled_rect = gst_video_overlay_rectangle_new_raw (buf, |
1435 | 0 | 0, 0, wanted_width, wanted_height, new_flags); |
1436 | 0 | if (conv_rect->global_alpha != 1.0) |
1437 | 0 | gst_video_overlay_rectangle_set_global_alpha (scaled_rect, |
1438 | 0 | conv_rect->global_alpha); |
1439 | 0 | gst_buffer_unref (buf); |
1440 | |
|
1441 | 0 | GST_RECTANGLE_LOCK (rectangle); |
1442 | 0 | rectangle->scaled_rectangles = |
1443 | 0 | g_list_prepend (rectangle->scaled_rectangles, scaled_rect); |
1444 | 0 | GST_RECTANGLE_UNLOCK (rectangle); |
1445 | |
|
1446 | 0 | done: |
1447 | |
|
1448 | 0 | GST_RECTANGLE_LOCK (rectangle); |
1449 | 0 | if (apply_global_alpha |
1450 | 0 | && scaled_rect->applied_global_alpha != rectangle->global_alpha) { |
1451 | 0 | gst_video_overlay_rectangle_apply_global_alpha (scaled_rect, |
1452 | 0 | rectangle->global_alpha); |
1453 | 0 | gst_video_overlay_rectangle_set_global_alpha (scaled_rect, |
1454 | 0 | rectangle->global_alpha); |
1455 | 0 | } else if (revert_global_alpha && scaled_rect->applied_global_alpha != 1.0) { |
1456 | 0 | gst_video_overlay_rectangle_apply_global_alpha (scaled_rect, 1.0); |
1457 | 0 | } |
1458 | 0 | GST_RECTANGLE_UNLOCK (rectangle); |
1459 | |
|
1460 | 0 | return scaled_rect->pixels; |
1461 | 0 | } |
1462 | | |
1463 | | |
1464 | | /** |
1465 | | * gst_video_overlay_rectangle_get_pixels_raw: |
1466 | | * @rectangle: a #GstVideoOverlayRectangle |
1467 | | * @flags: flags |
1468 | | * If a global_alpha value != 1 is set for the rectangle, the caller |
1469 | | * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
1470 | | * if he wants to apply global-alpha himself. If the flag is not set |
1471 | | * global_alpha is applied internally before returning the pixel-data. |
1472 | | * |
1473 | | * Returns: (transfer none): a #GstBuffer holding the pixel data with |
1474 | | * format as originally provided and specified in video meta with |
1475 | | * width and height of the render dimensions as per |
1476 | | * gst_video_overlay_rectangle_get_render_rectangle(). This function does |
1477 | | * not return a reference, the caller should obtain a reference of her own |
1478 | | * with gst_buffer_ref() if needed. |
1479 | | */ |
1480 | | GstBuffer * |
1481 | | gst_video_overlay_rectangle_get_pixels_raw (GstVideoOverlayRectangle * |
1482 | | rectangle, GstVideoOverlayFormatFlags flags) |
1483 | 0 | { |
1484 | 0 | return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
1485 | 0 | flags, FALSE, GST_VIDEO_INFO_FORMAT (&rectangle->info)); |
1486 | 0 | } |
1487 | | |
1488 | | /** |
1489 | | * gst_video_overlay_rectangle_get_pixels_argb: |
1490 | | * @rectangle: a #GstVideoOverlayRectangle |
1491 | | * @flags: flags |
1492 | | * If a global_alpha value != 1 is set for the rectangle, the caller |
1493 | | * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
1494 | | * if he wants to apply global-alpha himself. If the flag is not set |
1495 | | * global_alpha is applied internally before returning the pixel-data. |
1496 | | * |
1497 | | * Returns: (transfer none): a #GstBuffer holding the ARGB pixel data with |
1498 | | * width and height of the render dimensions as per |
1499 | | * gst_video_overlay_rectangle_get_render_rectangle(). This function does |
1500 | | * not return a reference, the caller should obtain a reference of her own |
1501 | | * with gst_buffer_ref() if needed. |
1502 | | */ |
1503 | | GstBuffer * |
1504 | | gst_video_overlay_rectangle_get_pixels_argb (GstVideoOverlayRectangle * |
1505 | | rectangle, GstVideoOverlayFormatFlags flags) |
1506 | 0 | { |
1507 | 0 | return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
1508 | 0 | flags, FALSE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB); |
1509 | 0 | } |
1510 | | |
1511 | | /** |
1512 | | * gst_video_overlay_rectangle_get_pixels_ayuv: |
1513 | | * @rectangle: a #GstVideoOverlayRectangle |
1514 | | * @flags: flags |
1515 | | * If a global_alpha value != 1 is set for the rectangle, the caller |
1516 | | * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
1517 | | * if he wants to apply global-alpha himself. If the flag is not set |
1518 | | * global_alpha is applied internally before returning the pixel-data. |
1519 | | * |
1520 | | * Returns: (transfer none): a #GstBuffer holding the AYUV pixel data with |
1521 | | * width and height of the render dimensions as per |
1522 | | * gst_video_overlay_rectangle_get_render_rectangle(). This function does |
1523 | | * not return a reference, the caller should obtain a reference of her own |
1524 | | * with gst_buffer_ref() if needed. |
1525 | | */ |
1526 | | GstBuffer * |
1527 | | gst_video_overlay_rectangle_get_pixels_ayuv (GstVideoOverlayRectangle * |
1528 | | rectangle, GstVideoOverlayFormatFlags flags) |
1529 | 0 | { |
1530 | 0 | return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
1531 | 0 | flags, FALSE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV); |
1532 | 0 | } |
1533 | | |
1534 | | /** |
1535 | | * gst_video_overlay_rectangle_get_pixels_unscaled_raw: |
1536 | | * @rectangle: a #GstVideoOverlayRectangle |
1537 | | * @flags: flags. |
1538 | | * If a global_alpha value != 1 is set for the rectangle, the caller |
1539 | | * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
1540 | | * if he wants to apply global-alpha himself. If the flag is not set |
1541 | | * global_alpha is applied internally before returning the pixel-data. |
1542 | | * |
1543 | | * Retrieves the pixel data as it is. This is useful if the caller can |
1544 | | * do the scaling itself when handling the overlaying. The rectangle will |
1545 | | * need to be scaled to the render dimensions, which can be retrieved using |
1546 | | * gst_video_overlay_rectangle_get_render_rectangle(). |
1547 | | * |
1548 | | * Returns: (transfer none): a #GstBuffer holding the pixel data with |
1549 | | * #GstVideoMeta set. This function does not return a reference, the caller |
1550 | | * should obtain a reference of her own with gst_buffer_ref() if needed. |
1551 | | */ |
1552 | | GstBuffer * |
1553 | | gst_video_overlay_rectangle_get_pixels_unscaled_raw (GstVideoOverlayRectangle * |
1554 | | rectangle, GstVideoOverlayFormatFlags flags) |
1555 | 0 | { |
1556 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
1557 | | |
1558 | 0 | return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
1559 | 0 | flags, TRUE, GST_VIDEO_INFO_FORMAT (&rectangle->info)); |
1560 | 0 | } |
1561 | | |
1562 | | /** |
1563 | | * gst_video_overlay_rectangle_get_pixels_unscaled_argb: |
1564 | | * @rectangle: a #GstVideoOverlayRectangle |
1565 | | * @flags: flags. |
1566 | | * If a global_alpha value != 1 is set for the rectangle, the caller |
1567 | | * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
1568 | | * if he wants to apply global-alpha himself. If the flag is not set |
1569 | | * global_alpha is applied internally before returning the pixel-data. |
1570 | | * |
1571 | | * Retrieves the pixel data as it is. This is useful if the caller can |
1572 | | * do the scaling itself when handling the overlaying. The rectangle will |
1573 | | * need to be scaled to the render dimensions, which can be retrieved using |
1574 | | * gst_video_overlay_rectangle_get_render_rectangle(). |
1575 | | * |
1576 | | * Returns: (transfer none): a #GstBuffer holding the ARGB pixel data with |
1577 | | * #GstVideoMeta set. This function does not return a reference, the caller |
1578 | | * should obtain a reference of her own with gst_buffer_ref() if needed. |
1579 | | */ |
1580 | | GstBuffer * |
1581 | | gst_video_overlay_rectangle_get_pixels_unscaled_argb (GstVideoOverlayRectangle * |
1582 | | rectangle, GstVideoOverlayFormatFlags flags) |
1583 | 0 | { |
1584 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
1585 | | |
1586 | 0 | return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
1587 | 0 | flags, TRUE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB); |
1588 | 0 | } |
1589 | | |
1590 | | /** |
1591 | | * gst_video_overlay_rectangle_get_pixels_unscaled_ayuv: |
1592 | | * @rectangle: a #GstVideoOverlayRectangle |
1593 | | * @flags: flags. |
1594 | | * If a global_alpha value != 1 is set for the rectangle, the caller |
1595 | | * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
1596 | | * if he wants to apply global-alpha himself. If the flag is not set |
1597 | | * global_alpha is applied internally before returning the pixel-data. |
1598 | | * |
1599 | | * Retrieves the pixel data as it is. This is useful if the caller can |
1600 | | * do the scaling itself when handling the overlaying. The rectangle will |
1601 | | * need to be scaled to the render dimensions, which can be retrieved using |
1602 | | * gst_video_overlay_rectangle_get_render_rectangle(). |
1603 | | * |
1604 | | * Returns: (transfer none): a #GstBuffer holding the AYUV pixel data with |
1605 | | * #GstVideoMeta set. This function does not return a reference, the caller |
1606 | | * should obtain a reference of her own with gst_buffer_ref() if needed. |
1607 | | */ |
1608 | | GstBuffer * |
1609 | | gst_video_overlay_rectangle_get_pixels_unscaled_ayuv (GstVideoOverlayRectangle * |
1610 | | rectangle, GstVideoOverlayFormatFlags flags) |
1611 | 0 | { |
1612 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
1613 | | |
1614 | 0 | return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
1615 | 0 | flags, TRUE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV); |
1616 | 0 | } |
1617 | | |
1618 | | /** |
1619 | | * gst_video_overlay_rectangle_get_flags: |
1620 | | * @rectangle: a #GstVideoOverlayRectangle |
1621 | | * |
1622 | | * Retrieves the flags associated with a #GstVideoOverlayRectangle. |
1623 | | * This is useful if the caller can handle both premultiplied alpha and |
1624 | | * non premultiplied alpha, for example. By knowing whether the rectangle |
1625 | | * uses premultiplied or not, it can request the pixel data in the format |
1626 | | * it is stored in, to avoid unnecessary conversion. |
1627 | | * |
1628 | | * Returns: the #GstVideoOverlayFormatFlags associated with the rectangle. |
1629 | | */ |
1630 | | GstVideoOverlayFormatFlags |
1631 | | gst_video_overlay_rectangle_get_flags (GstVideoOverlayRectangle * rectangle) |
1632 | 0 | { |
1633 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), |
1634 | 0 | GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); |
1635 | | |
1636 | 0 | return rectangle->flags; |
1637 | 0 | } |
1638 | | |
1639 | | /** |
1640 | | * gst_video_overlay_rectangle_get_global_alpha: |
1641 | | * @rectangle: a #GstVideoOverlayRectangle |
1642 | | * |
1643 | | * Retrieves the global-alpha value associated with a #GstVideoOverlayRectangle. |
1644 | | * |
1645 | | * Returns: the global-alpha value associated with the rectangle. |
1646 | | */ |
1647 | | gfloat |
1648 | | gst_video_overlay_rectangle_get_global_alpha (GstVideoOverlayRectangle * |
1649 | | rectangle) |
1650 | 0 | { |
1651 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), -1); |
1652 | | |
1653 | 0 | return rectangle->global_alpha; |
1654 | 0 | } |
1655 | | |
1656 | | /** |
1657 | | * gst_video_overlay_rectangle_set_global_alpha: |
1658 | | * @rectangle: a #GstVideoOverlayRectangle |
1659 | | * @global_alpha: Global alpha value (0 to 1.0) |
1660 | | * |
1661 | | * Sets the global alpha value associated with a #GstVideoOverlayRectangle. Per- |
1662 | | * pixel alpha values are multiplied with this value. Valid |
1663 | | * values: 0 <= global_alpha <= 1; 1 to deactivate. |
1664 | | * |
1665 | | * @rectangle must be writable, meaning its refcount must be 1. You can |
1666 | | * make the rectangles inside a #GstVideoOverlayComposition writable using |
1667 | | * gst_video_overlay_composition_make_writable() or |
1668 | | * gst_video_overlay_composition_copy(). |
1669 | | */ |
1670 | | void |
1671 | | gst_video_overlay_rectangle_set_global_alpha (GstVideoOverlayRectangle * |
1672 | | rectangle, gfloat global_alpha) |
1673 | 0 | { |
1674 | 0 | g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); |
1675 | 0 | g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST |
1676 | 0 | (rectangle))); |
1677 | 0 | g_return_if_fail (global_alpha >= 0 && global_alpha <= 1); |
1678 | | |
1679 | 0 | if (rectangle->global_alpha != global_alpha) { |
1680 | 0 | rectangle->global_alpha = global_alpha; |
1681 | 0 | if (global_alpha != 1) |
1682 | 0 | rectangle->flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA; |
1683 | 0 | else |
1684 | 0 | rectangle->flags &= ~GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA; |
1685 | | /* update seq_num automatically to signal the consumer, that data has changed |
1686 | | * note, that this might mislead renderers, that can handle global-alpha |
1687 | | * themselves, because what they want to know is whether the actual pixel data |
1688 | | * has changed. */ |
1689 | 0 | rectangle->seq_num = gst_video_overlay_get_seqnum (); |
1690 | 0 | } |
1691 | 0 | } |
1692 | | |
1693 | | /** |
1694 | | * gst_video_overlay_rectangle_copy: |
1695 | | * @rectangle: (transfer none): a #GstVideoOverlayRectangle to copy |
1696 | | * |
1697 | | * Makes a copy of @rectangle, so that it is possible to modify it |
1698 | | * (e.g. to change the render co-ordinates or render dimension). The |
1699 | | * actual overlay pixel data buffers contained in the rectangle are not |
1700 | | * copied. |
1701 | | * |
1702 | | * Returns: (transfer full): a new #GstVideoOverlayRectangle equivalent |
1703 | | * to @rectangle. |
1704 | | */ |
1705 | | GstVideoOverlayRectangle * |
1706 | | gst_video_overlay_rectangle_copy (GstVideoOverlayRectangle * rectangle) |
1707 | 0 | { |
1708 | 0 | GstVideoOverlayRectangle *copy; |
1709 | |
|
1710 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
1711 | | |
1712 | 0 | copy = gst_video_overlay_rectangle_new_raw (rectangle->pixels, |
1713 | 0 | rectangle->x, rectangle->y, |
1714 | 0 | rectangle->render_width, rectangle->render_height, rectangle->flags); |
1715 | 0 | if (rectangle->global_alpha != 1) |
1716 | 0 | gst_video_overlay_rectangle_set_global_alpha (copy, |
1717 | 0 | rectangle->global_alpha); |
1718 | |
|
1719 | 0 | return copy; |
1720 | 0 | } |
1721 | | |
1722 | | /** |
1723 | | * gst_video_overlay_rectangle_get_seqnum: |
1724 | | * @rectangle: a #GstVideoOverlayRectangle |
1725 | | * |
1726 | | * Returns the sequence number of this rectangle. Sequence numbers are |
1727 | | * monotonically increasing and unique for overlay compositions and rectangles |
1728 | | * (meaning there will never be a rectangle with the same sequence number as |
1729 | | * a composition). |
1730 | | * |
1731 | | * Using the sequence number of a rectangle as an indicator for changed |
1732 | | * pixel-data of a rectangle is dangereous. Some API calls, like e.g. |
1733 | | * gst_video_overlay_rectangle_set_global_alpha(), automatically update |
1734 | | * the per rectangle sequence number, which is misleading for renderers/ |
1735 | | * consumers, that handle global-alpha themselves. For them the |
1736 | | * pixel-data returned by gst_video_overlay_rectangle_get_pixels_*() |
1737 | | * won't be different for different global-alpha values. In this case a |
1738 | | * renderer could also use the GstBuffer pointers as a hint for changed |
1739 | | * pixel-data. |
1740 | | * |
1741 | | * Returns: the sequence number of @rectangle |
1742 | | */ |
1743 | | guint |
1744 | | gst_video_overlay_rectangle_get_seqnum (GstVideoOverlayRectangle * rectangle) |
1745 | 0 | { |
1746 | 0 | g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), 0); |
1747 | | |
1748 | 0 | return rectangle->seq_num; |
1749 | 0 | } |