mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
fe64e68cc4
When overlay coordinates are updated, after the initial coordinates are set, the shader indices are applied to the wrong buffer, resulting in the background image appearing where the overlay should. Bind the array buffer before applying subsequent coordinate updates. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5901>
849 lines
26 KiB
C
849 lines
26 KiB
C
/*
|
|
* GStreamer
|
|
* Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-gloverlay
|
|
* @title: gloverlay
|
|
*
|
|
* Overlay GL video texture with a PNG image
|
|
*
|
|
* ## Examples
|
|
* |[
|
|
* gst-launch-1.0 videotestsrc ! gloverlay location=image.jpg ! glimagesink
|
|
* ]|
|
|
* FBO (Frame Buffer Object) is required.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <gst/base/gsttypefindhelper.h>
|
|
#include <gst/gl/gstglconfig.h>
|
|
|
|
#include "gstglelements.h"
|
|
#include "gstgloverlay.h"
|
|
#include "effects/gstgleffectssources.h"
|
|
#include "gstglutils.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#if defined(_MSC_VER) || (defined (__MINGW64_VERSION_MAJOR) && __MINGW64_VERSION_MAJOR >= 6)
|
|
#define HAVE_BOOLEAN
|
|
#endif
|
|
#include <jpeglib.h>
|
|
#include <png.h>
|
|
|
|
#if PNG_LIBPNG_VER >= 10400
|
|
#define int_p_NULL NULL
|
|
#define png_infopp_NULL NULL
|
|
#endif
|
|
|
|
#define GST_CAT_DEFAULT gst_gl_overlay_debug
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
#define DEBUG_INIT \
|
|
GST_DEBUG_CATEGORY_INIT (gst_gl_overlay_debug, "gloverlay", 0, "gloverlay element");
|
|
|
|
#define gst_gl_overlay_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstGLOverlay, gst_gl_overlay, GST_TYPE_GL_FILTER,
|
|
DEBUG_INIT);
|
|
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (gloverlay, "gloverlay",
|
|
GST_RANK_NONE, GST_TYPE_GL_OVERLAY, gl_element_init (plugin));
|
|
|
|
static gboolean gst_gl_overlay_set_caps (GstGLFilter * filter,
|
|
GstCaps * incaps, GstCaps * outcaps);
|
|
|
|
static void gst_gl_overlay_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_gl_overlay_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static void gst_gl_overlay_before_transform (GstBaseTransform * trans,
|
|
GstBuffer * outbuf);
|
|
static gboolean gst_gl_overlay_filter_texture (GstGLFilter * filter,
|
|
GstGLMemory * in_tex, GstGLMemory * out_tex);
|
|
|
|
static gboolean gst_gl_overlay_load_png (GstGLOverlay * overlay, FILE * fp);
|
|
static gboolean gst_gl_overlay_load_jpeg (GstGLOverlay * overlay, FILE * fp);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_LOCATION,
|
|
PROP_OFFSET_X,
|
|
PROP_OFFSET_Y,
|
|
PROP_RELATIVE_X,
|
|
PROP_RELATIVE_Y,
|
|
PROP_OVERLAY_WIDTH,
|
|
PROP_OVERLAY_HEIGHT,
|
|
PROP_ALPHA
|
|
};
|
|
|
|
/* *INDENT-OFF* */
|
|
/* vertex source */
|
|
static const gchar *overlay_v_src =
|
|
"attribute vec4 a_position;\n"
|
|
"attribute vec2 a_texcoord;\n"
|
|
"varying vec2 v_texcoord;\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" gl_Position = a_position;\n"
|
|
" v_texcoord = a_texcoord;\n"
|
|
"}";
|
|
|
|
/* fragment source */
|
|
static const gchar *overlay_f_src =
|
|
"uniform sampler2D texture;\n"
|
|
"uniform float alpha;\n"
|
|
"varying vec2 v_texcoord;\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec4 rgba = texture2D( texture, v_texcoord );\n"
|
|
" gl_FragColor = vec4(rgba.rgb, rgba.a * alpha);\n"
|
|
"}\n";
|
|
/* *INDENT-ON* */
|
|
|
|
/* init resources that need a gl context */
|
|
static gboolean
|
|
gst_gl_overlay_gl_start (GstGLBaseFilter * base_filter)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (base_filter);
|
|
gchar *frag_str;
|
|
gboolean ret;
|
|
|
|
if (!GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter))
|
|
return FALSE;
|
|
|
|
frag_str =
|
|
g_strdup_printf ("%s%s",
|
|
gst_gl_shader_string_get_highest_precision (base_filter->context,
|
|
GST_GLSL_VERSION_NONE,
|
|
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY), overlay_f_src);
|
|
|
|
/* blocking call, wait the opengl thread has compiled the shader */
|
|
ret = gst_gl_context_gen_shader (base_filter->context, overlay_v_src,
|
|
frag_str, &overlay->shader);
|
|
g_free (frag_str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* free resources that need a gl context */
|
|
static void
|
|
gst_gl_overlay_gl_stop (GstGLBaseFilter * base_filter)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (base_filter);
|
|
const GstGLFuncs *gl = base_filter->context->gl_vtable;
|
|
|
|
if (overlay->shader) {
|
|
gst_object_unref (overlay->shader);
|
|
overlay->shader = NULL;
|
|
}
|
|
|
|
if (overlay->image_memory) {
|
|
gst_memory_unref ((GstMemory *) overlay->image_memory);
|
|
overlay->image_memory = NULL;
|
|
}
|
|
|
|
if (overlay->vao) {
|
|
gl->DeleteVertexArrays (1, &overlay->vao);
|
|
overlay->vao = 0;
|
|
}
|
|
|
|
if (overlay->vbo) {
|
|
gl->DeleteBuffers (1, &overlay->vbo);
|
|
overlay->vbo = 0;
|
|
}
|
|
|
|
if (overlay->vbo_indices) {
|
|
gl->DeleteBuffers (1, &overlay->vbo_indices);
|
|
overlay->vbo_indices = 0;
|
|
}
|
|
|
|
if (overlay->overlay_vao) {
|
|
gl->DeleteVertexArrays (1, &overlay->overlay_vao);
|
|
overlay->overlay_vao = 0;
|
|
}
|
|
|
|
if (overlay->overlay_vbo) {
|
|
gl->DeleteBuffers (1, &overlay->overlay_vbo);
|
|
overlay->overlay_vbo = 0;
|
|
}
|
|
|
|
GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
|
|
}
|
|
|
|
static void
|
|
gst_gl_overlay_class_init (GstGLOverlayClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *element_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
element_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
|
|
|
|
gobject_class->set_property = gst_gl_overlay_set_property;
|
|
gobject_class->get_property = gst_gl_overlay_get_property;
|
|
|
|
GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_overlay_gl_start;
|
|
GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_overlay_gl_stop;
|
|
|
|
GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_overlay_set_caps;
|
|
GST_GL_FILTER_CLASS (klass)->filter_texture = gst_gl_overlay_filter_texture;
|
|
|
|
GST_BASE_TRANSFORM_CLASS (klass)->before_transform =
|
|
GST_DEBUG_FUNCPTR (gst_gl_overlay_before_transform);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_LOCATION,
|
|
g_param_spec_string ("location", "location",
|
|
"Location of image file to overlay", NULL, GST_PARAM_CONTROLLABLE
|
|
| GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OFFSET_X,
|
|
g_param_spec_int ("offset-x", "X Offset",
|
|
"For positive value, horizontal offset of overlay image in pixels from"
|
|
" left of video image. For negative value, horizontal offset of overlay"
|
|
" image in pixels from right of video image", G_MININT, G_MAXINT, 0,
|
|
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OFFSET_Y,
|
|
g_param_spec_int ("offset-y", "Y Offset",
|
|
"For positive value, vertical offset of overlay image in pixels from"
|
|
" top of video image. For negative value, vertical offset of overlay"
|
|
" image in pixels from bottom of video image", G_MININT, G_MAXINT, 0,
|
|
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_RELATIVE_X,
|
|
g_param_spec_double ("relative-x", "Relative X Offset",
|
|
"Horizontal offset of overlay image in fractions of video image "
|
|
"width, from top-left corner of video image", 0.0, 1.0, 0.0,
|
|
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_RELATIVE_Y,
|
|
g_param_spec_double ("relative-y", "Relative Y Offset",
|
|
"Vertical offset of overlay image in fractions of video image "
|
|
"height, from top-left corner of video image", 0.0, 1.0, 0.0,
|
|
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAY_WIDTH,
|
|
g_param_spec_int ("overlay-width", "Overlay Width",
|
|
"Width of overlay image in pixels (0 = same as overlay image)", 0,
|
|
G_MAXINT, 0,
|
|
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAY_HEIGHT,
|
|
g_param_spec_int ("overlay-height", "Overlay Height",
|
|
"Height of overlay image in pixels (0 = same as overlay image)", 0,
|
|
G_MAXINT, 0,
|
|
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
|
| G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_ALPHA,
|
|
g_param_spec_double ("alpha", "Alpha", "Global alpha of overlay image",
|
|
0.0, 1.0, 1.0, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING
|
|
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_set_metadata (element_class,
|
|
"Gstreamer OpenGL Overlay", "Filter/Effect/Video",
|
|
"Overlay GL video texture with a JPEG/PNG image",
|
|
"Filippo Argiolas <filippo.argiolas@gmail.com>, "
|
|
"Matthew Waters <matthew@centricular.com>");
|
|
|
|
GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
|
|
GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
|
|
}
|
|
|
|
static void
|
|
gst_gl_overlay_init (GstGLOverlay * overlay)
|
|
{
|
|
overlay->offset_x = 0;
|
|
overlay->offset_y = 0;
|
|
|
|
overlay->relative_x = 0.0;
|
|
overlay->relative_y = 0.0;
|
|
|
|
overlay->overlay_width = 0;
|
|
overlay->overlay_height = 0;
|
|
|
|
overlay->alpha = 1.0;
|
|
}
|
|
|
|
static void
|
|
gst_gl_overlay_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_LOCATION:
|
|
g_free (overlay->location);
|
|
overlay->location_has_changed = TRUE;
|
|
overlay->location = g_value_dup_string (value);
|
|
break;
|
|
case PROP_OFFSET_X:
|
|
overlay->offset_x = g_value_get_int (value);
|
|
overlay->geometry_change = TRUE;
|
|
break;
|
|
case PROP_OFFSET_Y:
|
|
overlay->offset_y = g_value_get_int (value);
|
|
overlay->geometry_change = TRUE;
|
|
break;
|
|
case PROP_RELATIVE_X:
|
|
overlay->relative_x = g_value_get_double (value);
|
|
overlay->geometry_change = TRUE;
|
|
break;
|
|
case PROP_RELATIVE_Y:
|
|
overlay->relative_y = g_value_get_double (value);
|
|
overlay->geometry_change = TRUE;
|
|
break;
|
|
case PROP_OVERLAY_WIDTH:
|
|
overlay->overlay_width = g_value_get_int (value);
|
|
overlay->geometry_change = TRUE;
|
|
break;
|
|
case PROP_OVERLAY_HEIGHT:
|
|
overlay->overlay_height = g_value_get_int (value);
|
|
overlay->geometry_change = TRUE;
|
|
break;
|
|
case PROP_ALPHA:
|
|
overlay->alpha = g_value_get_double (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_gl_overlay_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_LOCATION:
|
|
g_value_set_string (value, overlay->location);
|
|
break;
|
|
case PROP_OFFSET_X:
|
|
g_value_set_int (value, overlay->offset_x);
|
|
break;
|
|
case PROP_OFFSET_Y:
|
|
g_value_set_int (value, overlay->offset_y);
|
|
break;
|
|
case PROP_RELATIVE_X:
|
|
g_value_set_double (value, overlay->relative_x);
|
|
break;
|
|
case PROP_RELATIVE_Y:
|
|
g_value_set_double (value, overlay->relative_y);
|
|
break;
|
|
case PROP_OVERLAY_WIDTH:
|
|
g_value_set_int (value, overlay->overlay_width);
|
|
break;
|
|
case PROP_OVERLAY_HEIGHT:
|
|
g_value_set_int (value, overlay->overlay_height);
|
|
break;
|
|
case PROP_ALPHA:
|
|
g_value_set_double (value, overlay->alpha);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_overlay_set_caps (GstGLFilter * filter, GstCaps * incaps,
|
|
GstCaps * outcaps)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (filter);
|
|
GstStructure *s = gst_caps_get_structure (incaps, 0);
|
|
gint width = 0;
|
|
gint height = 0;
|
|
|
|
gst_structure_get_int (s, "width", &width);
|
|
gst_structure_get_int (s, "height", &height);
|
|
|
|
overlay->window_width = width;
|
|
overlay->window_height = height;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_unbind_buffer (GstGLOverlay * overlay)
|
|
{
|
|
GstGLFilter *filter = GST_GL_FILTER (overlay);
|
|
const GstGLFuncs *gl = GST_GL_BASE_FILTER (overlay)->context->gl_vtable;
|
|
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
|
|
|
gl->DisableVertexAttribArray (filter->draw_attr_position_loc);
|
|
gl->DisableVertexAttribArray (filter->draw_attr_texture_loc);
|
|
}
|
|
|
|
static void
|
|
_bind_buffer (GstGLOverlay * overlay, GLuint vbo)
|
|
{
|
|
GstGLFilter *filter = GST_GL_FILTER (overlay);
|
|
const GstGLFuncs *gl = GST_GL_BASE_FILTER (overlay)->context->gl_vtable;
|
|
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->vbo_indices);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, vbo);
|
|
|
|
gl->EnableVertexAttribArray (filter->draw_attr_position_loc);
|
|
gl->EnableVertexAttribArray (filter->draw_attr_texture_loc);
|
|
|
|
gl->VertexAttribPointer (filter->draw_attr_position_loc, 3, GL_FLOAT,
|
|
GL_FALSE, 5 * sizeof (GLfloat), (void *) 0);
|
|
gl->VertexAttribPointer (filter->draw_attr_texture_loc, 2, GL_FLOAT,
|
|
GL_FALSE, 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
float v_vertices[] = {
|
|
/*| Vertex | TexCoord |*/
|
|
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
|
|
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
|
|
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
|
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
|
|
};
|
|
|
|
static const GLushort indices[] = { 0, 1, 2, 0, 2, 3, };
|
|
/* *INDENT-ON* */
|
|
|
|
static gboolean
|
|
gst_gl_overlay_callback (GstGLFilter * filter, GstGLMemory * in_tex,
|
|
gpointer stuff)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (filter);
|
|
GstMapInfo map_info;
|
|
guint image_tex;
|
|
gboolean memory_mapped = FALSE;
|
|
const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
|
|
gboolean ret = FALSE;
|
|
|
|
#if GST_GL_HAVE_OPENGL
|
|
if (gst_gl_context_get_gl_api (GST_GL_BASE_FILTER (filter)->context) &
|
|
GST_GL_API_OPENGL) {
|
|
|
|
gl->MatrixMode (GL_PROJECTION);
|
|
gl->LoadIdentity ();
|
|
}
|
|
#endif
|
|
|
|
gl->ActiveTexture (GL_TEXTURE0);
|
|
gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
|
|
|
|
gst_gl_shader_use (overlay->shader);
|
|
|
|
gst_gl_shader_set_uniform_1f (overlay->shader, "alpha", 1.0f);
|
|
gst_gl_shader_set_uniform_1i (overlay->shader, "texture", 0);
|
|
|
|
filter->draw_attr_position_loc =
|
|
gst_gl_shader_get_attribute_location (overlay->shader, "a_position");
|
|
filter->draw_attr_texture_loc =
|
|
gst_gl_shader_get_attribute_location (overlay->shader, "a_texcoord");
|
|
|
|
gst_gl_filter_draw_fullscreen_quad (filter);
|
|
|
|
if (!overlay->image_memory)
|
|
goto out;
|
|
|
|
if (!gst_memory_map ((GstMemory *) overlay->image_memory, &map_info,
|
|
GST_MAP_READ | GST_MAP_GL) || map_info.data == NULL)
|
|
goto out;
|
|
|
|
memory_mapped = TRUE;
|
|
image_tex = *(guint *) map_info.data;
|
|
|
|
if (!overlay->overlay_vbo) {
|
|
if (gl->GenVertexArrays) {
|
|
gl->GenVertexArrays (1, &overlay->overlay_vao);
|
|
gl->BindVertexArray (overlay->overlay_vao);
|
|
}
|
|
|
|
gl->GenBuffers (1, &overlay->vbo_indices);
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->vbo_indices);
|
|
gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
|
|
GL_STATIC_DRAW);
|
|
|
|
gl->GenBuffers (1, &overlay->overlay_vbo);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, overlay->overlay_vbo);
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->vbo_indices);
|
|
overlay->geometry_change = TRUE;
|
|
}
|
|
|
|
if (gl->GenVertexArrays) {
|
|
gl->BindVertexArray (overlay->overlay_vao);
|
|
}
|
|
|
|
_bind_buffer (overlay, overlay->overlay_vbo);
|
|
|
|
if (overlay->geometry_change) {
|
|
gint render_width, render_height;
|
|
gfloat x, y, image_width, image_height;
|
|
|
|
/* *INDENT-OFF* */
|
|
float vertices[] = {
|
|
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
|
|
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
|
|
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
|
-1.0f, 1.0f, 0.0f, 0.0, 1.0f,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
/* scale from [0, 1] -> [-1, 1] */
|
|
x = ((gfloat) overlay->offset_x / (gfloat) overlay->window_width +
|
|
overlay->relative_x) * 2.0f - 1.0;
|
|
y = ((gfloat) overlay->offset_y / (gfloat) overlay->window_height +
|
|
overlay->relative_y) * 2.0f - 1.0;
|
|
/* scale from [0, 1] -> [0, 2] */
|
|
render_width =
|
|
overlay->overlay_width >
|
|
0 ? overlay->overlay_width : overlay->image_width;
|
|
render_height =
|
|
overlay->overlay_height >
|
|
0 ? overlay->overlay_height : overlay->image_height;
|
|
image_width =
|
|
((gfloat) render_width / (gfloat) overlay->window_width) * 2.0f;
|
|
image_height =
|
|
((gfloat) render_height / (gfloat) overlay->window_height) * 2.0f;
|
|
|
|
vertices[0] = vertices[15] = x;
|
|
vertices[5] = vertices[10] = x + image_width;
|
|
vertices[1] = vertices[6] = y;
|
|
vertices[11] = vertices[16] = y + image_height;
|
|
|
|
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
|
|
GL_STATIC_DRAW);
|
|
}
|
|
|
|
gl->BindTexture (GL_TEXTURE_2D, image_tex);
|
|
gst_gl_shader_set_uniform_1f (overlay->shader, "alpha", overlay->alpha);
|
|
|
|
gl->Enable (GL_BLEND);
|
|
if (gl->BlendFuncSeparate)
|
|
gl->BlendFuncSeparate (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
|
|
GL_ONE_MINUS_SRC_ALPHA);
|
|
else
|
|
gl->BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
gl->BlendEquation (GL_FUNC_ADD);
|
|
|
|
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
|
|
|
|
gl->Disable (GL_BLEND);
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (gl->GenVertexArrays)
|
|
gl->BindVertexArray (0);
|
|
else
|
|
_unbind_buffer (overlay);
|
|
|
|
gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context);
|
|
|
|
if (memory_mapped)
|
|
gst_memory_unmap ((GstMemory *) overlay->image_memory, &map_info);
|
|
|
|
overlay->geometry_change = FALSE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
load_file (GstGLOverlay * overlay)
|
|
{
|
|
FILE *fp;
|
|
guint8 buff[16];
|
|
gsize n_read;
|
|
GstCaps *caps;
|
|
GstStructure *structure;
|
|
gboolean success = FALSE;
|
|
|
|
if (overlay->location == NULL)
|
|
return TRUE;
|
|
|
|
if ((fp = fopen (overlay->location, "rb")) == NULL) {
|
|
GST_ELEMENT_ERROR (overlay, RESOURCE, NOT_FOUND, ("Can't open file"),
|
|
("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
n_read = fread (buff, 1, sizeof (buff), fp);
|
|
if (n_read != sizeof (buff)) {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("Can't read file header"),
|
|
("File: %s", overlay->location));
|
|
goto out;
|
|
}
|
|
|
|
caps = gst_type_find_helper_for_data (GST_OBJECT (overlay), buff,
|
|
sizeof (buff), NULL);
|
|
|
|
if (caps == NULL) {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("Can't find file type"),
|
|
("File: %s", overlay->location));
|
|
goto out;
|
|
}
|
|
|
|
fseek (fp, 0, SEEK_SET);
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
if (gst_structure_has_name (structure, "image/jpeg")) {
|
|
success = gst_gl_overlay_load_jpeg (overlay, fp);
|
|
} else if (gst_structure_has_name (structure, "image/png")) {
|
|
success = gst_gl_overlay_load_png (overlay, fp);
|
|
} else {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("Image type not supported"),
|
|
("File: %s", overlay->location));
|
|
}
|
|
|
|
out:
|
|
fclose (fp);
|
|
gst_caps_replace (&caps, NULL);
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_overlay_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
|
|
GstGLMemory * out_tex)
|
|
{
|
|
GstGLOverlay *overlay = GST_GL_OVERLAY (filter);
|
|
|
|
if (overlay->location_has_changed) {
|
|
if (overlay->image_memory) {
|
|
gst_memory_unref ((GstMemory *) overlay->image_memory);
|
|
overlay->image_memory = NULL;
|
|
}
|
|
|
|
if (!load_file (overlay))
|
|
return FALSE;
|
|
|
|
overlay->location_has_changed = FALSE;
|
|
}
|
|
|
|
gst_gl_filter_render_to_target (filter, in_tex, out_tex,
|
|
gst_gl_overlay_callback, overlay);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_gl_overlay_before_transform (GstBaseTransform * trans, GstBuffer * outbuf)
|
|
{
|
|
GstClockTime stream_time;
|
|
|
|
stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
|
|
GST_BUFFER_TIMESTAMP (outbuf));
|
|
|
|
if (GST_CLOCK_TIME_IS_VALID (stream_time))
|
|
gst_object_sync_values (GST_OBJECT (trans), stream_time);
|
|
}
|
|
|
|
static void
|
|
user_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
|
|
{
|
|
g_warning ("%s\n", warning_msg);
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_overlay_load_jpeg (GstGLOverlay * overlay, FILE * fp)
|
|
{
|
|
GstGLBaseMemoryAllocator *mem_allocator;
|
|
GstGLVideoAllocationParams *params;
|
|
GstVideoInfo v_info;
|
|
GstVideoAlignment v_align;
|
|
GstMapInfo map_info;
|
|
struct jpeg_decompress_struct cinfo;
|
|
struct jpeg_error_mgr jerr;
|
|
JSAMPROW j;
|
|
int i;
|
|
|
|
jpeg_create_decompress (&cinfo);
|
|
cinfo.err = jpeg_std_error (&jerr);
|
|
jpeg_stdio_src (&cinfo, fp);
|
|
jpeg_read_header (&cinfo, TRUE);
|
|
jpeg_start_decompress (&cinfo);
|
|
overlay->image_width = cinfo.image_width;
|
|
overlay->image_height = cinfo.image_height;
|
|
|
|
if (cinfo.num_components == 1)
|
|
gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_Y444,
|
|
overlay->image_width, overlay->image_height);
|
|
else
|
|
gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_RGB,
|
|
overlay->image_width, overlay->image_height);
|
|
|
|
gst_video_alignment_reset (&v_align);
|
|
v_align.stride_align[0] = 32 - 1;
|
|
gst_video_info_align (&v_info, &v_align);
|
|
|
|
mem_allocator =
|
|
GST_GL_BASE_MEMORY_ALLOCATOR (gst_gl_memory_allocator_get_default
|
|
(GST_GL_BASE_FILTER (overlay)->context));
|
|
params =
|
|
gst_gl_video_allocation_params_new (GST_GL_BASE_FILTER (overlay)->context,
|
|
NULL, &v_info, 0, &v_align, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
|
|
overlay->image_memory = (GstGLMemory *)
|
|
gst_gl_base_memory_alloc (mem_allocator,
|
|
(GstGLAllocationParams *) params);
|
|
gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
|
|
gst_object_unref (mem_allocator);
|
|
|
|
if (!gst_memory_map ((GstMemory *) overlay->image_memory, &map_info,
|
|
GST_MAP_WRITE)) {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("failed to map memory"),
|
|
("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < overlay->image_height; ++i) {
|
|
j = map_info.data + v_info.stride[0] * i;
|
|
jpeg_read_scanlines (&cinfo, &j, 1);
|
|
}
|
|
jpeg_finish_decompress (&cinfo);
|
|
jpeg_destroy_decompress (&cinfo);
|
|
gst_memory_unmap ((GstMemory *) overlay->image_memory, &map_info);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_overlay_load_png (GstGLOverlay * overlay, FILE * fp)
|
|
{
|
|
GstGLBaseMemoryAllocator *mem_allocator;
|
|
GstGLVideoAllocationParams *params;
|
|
GstVideoInfo v_info;
|
|
GstMapInfo map_info;
|
|
|
|
png_structp png_ptr;
|
|
png_infop info_ptr;
|
|
png_uint_32 width = 0;
|
|
png_uint_32 height = 0;
|
|
gint bit_depth = 0;
|
|
gint color_type = 0;
|
|
gint interlace_type = 0;
|
|
guint y = 0;
|
|
guchar **rows = NULL;
|
|
gint filler;
|
|
png_byte magic[8];
|
|
gint n_read;
|
|
|
|
if (!GST_GL_BASE_FILTER (overlay)->context)
|
|
return FALSE;
|
|
|
|
/* Read magic number */
|
|
n_read = fread (magic, 1, sizeof (magic), fp);
|
|
if (n_read != sizeof (magic)) {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
|
|
("can't read PNG magic number"), ("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Check for valid magic number */
|
|
if (png_sig_cmp (magic, 0, sizeof (magic))) {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
|
|
("not a valid PNG image"), ("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
|
|
|
if (png_ptr == NULL) {
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
|
|
("failed to initialize the png_struct"), ("File: %s",
|
|
overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
png_set_error_fn (png_ptr, NULL, NULL, user_warning_fn);
|
|
|
|
info_ptr = png_create_info_struct (png_ptr);
|
|
if (info_ptr == NULL) {
|
|
png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
|
|
("failed to initialize the memory for image information"),
|
|
("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
png_init_io (png_ptr, fp);
|
|
|
|
png_set_sig_bytes (png_ptr, sizeof (magic));
|
|
|
|
png_read_info (png_ptr, info_ptr);
|
|
|
|
png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
|
|
&interlace_type, int_p_NULL, int_p_NULL);
|
|
|
|
if (color_type == PNG_COLOR_TYPE_RGB) {
|
|
filler = 0xff;
|
|
png_set_filler (png_ptr, filler, PNG_FILLER_AFTER);
|
|
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
|
}
|
|
|
|
if (color_type != PNG_COLOR_TYPE_RGB_ALPHA) {
|
|
png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
|
|
("color type is not rgb"), ("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
|
|
overlay->image_width = width;
|
|
overlay->image_height = height;
|
|
|
|
gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_RGBA, width, height);
|
|
mem_allocator =
|
|
GST_GL_BASE_MEMORY_ALLOCATOR (gst_gl_memory_allocator_get_default
|
|
(GST_GL_BASE_FILTER (overlay)->context));
|
|
params =
|
|
gst_gl_video_allocation_params_new (GST_GL_BASE_FILTER (overlay)->context,
|
|
NULL, &v_info, 0, NULL, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
|
|
overlay->image_memory = (GstGLMemory *)
|
|
gst_gl_base_memory_alloc (mem_allocator,
|
|
(GstGLAllocationParams *) params);
|
|
gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
|
|
gst_object_unref (mem_allocator);
|
|
|
|
if (!gst_memory_map ((GstMemory *) overlay->image_memory, &map_info,
|
|
GST_MAP_WRITE)) {
|
|
png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
|
|
GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
|
|
("failed to map memory"), ("File: %s", overlay->location));
|
|
return FALSE;
|
|
}
|
|
rows = (guchar **) malloc (sizeof (guchar *) * height);
|
|
|
|
for (y = 0; y < height; ++y)
|
|
rows[y] = (guchar *) (map_info.data + y * width * 4);
|
|
|
|
png_read_image (png_ptr, rows);
|
|
|
|
free (rows);
|
|
gst_memory_unmap ((GstMemory *) overlay->image_memory, &map_info);
|
|
|
|
png_read_end (png_ptr, info_ptr);
|
|
png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
|
|
|
|
return TRUE;
|
|
}
|