mirror of
https://github.com/brl/mutter.git
synced 2024-11-23 08:30:42 -05:00
1774 lines
51 KiB
C
1774 lines
51 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
*
|
|
* Copyright (C) 2006 OpenedHand
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:clutter-texture
|
|
* @short_description: An actor for displaying and manipulating images.
|
|
*
|
|
* #ClutterTexture is a base class for displaying and manipulating pixel
|
|
* buffer type data.
|
|
*
|
|
* The clutter_texture_set_from_rgb_data() and
|
|
* clutter_texture_set_from_file() functions are used to copy image
|
|
* data into texture memory and subsequently realize the texture.
|
|
*
|
|
* If texture reads are supported by underlying GL implementation,
|
|
* unrealizing frees image data from texture memory moving to main
|
|
* system memory. Re-realizing then performs the opposite operation.
|
|
* This process allows basic management of commonly limited available
|
|
* texture memory.
|
|
*
|
|
* Note: a ClutterTexture will scale its contents to fit the bounding
|
|
* box requested using clutter_actor_set_size(). To display an area of
|
|
* a texture without scaling, you should set the clip area using
|
|
* clutter_actor_set_clip().
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "clutter-texture.h"
|
|
#include "clutter-main.h"
|
|
#include "clutter-marshal.h"
|
|
#include "clutter-feature.h"
|
|
#include "clutter-util.h"
|
|
#include "clutter-private.h"
|
|
#include "clutter-scriptable.h"
|
|
#include "clutter-debug.h"
|
|
#include "clutter-fixed.h"
|
|
#include "clutter-enum-types.h"
|
|
|
|
#include "cogl/cogl.h"
|
|
|
|
static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (ClutterTexture,
|
|
clutter_texture,
|
|
CLUTTER_TYPE_ACTOR,
|
|
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
|
|
clutter_scriptable_iface_init));
|
|
|
|
#define CLUTTER_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXTURE, ClutterTexturePrivate))
|
|
|
|
struct _ClutterTexturePrivate
|
|
{
|
|
gint width;
|
|
gint height;
|
|
guint sync_actor_size : 1;
|
|
gint max_tile_waste;
|
|
ClutterTextureQuality filter_quality;
|
|
guint repeat_x : 1;
|
|
guint repeat_y : 1;
|
|
CoglHandle texture;
|
|
gboolean no_slice;
|
|
|
|
ClutterActor *fbo_source;
|
|
CoglHandle fbo_handle;
|
|
|
|
/* Non video memory copy of image data */
|
|
guint local_data_width, local_data_height;
|
|
guint local_data_rowstride;
|
|
guint local_data_has_alpha;
|
|
guchar *local_data;
|
|
|
|
guint in_dispose : 1;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_NO_SLICE,
|
|
PROP_MAX_TILE_WASTE,
|
|
PROP_PIXEL_FORMAT, /* Texture format */
|
|
PROP_SYNC_SIZE,
|
|
PROP_REPEAT_Y,
|
|
PROP_REPEAT_X,
|
|
PROP_FILTER_QUALITY,
|
|
PROP_COGL_TEXTURE,
|
|
PROP_FILENAME
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIZE_CHANGE,
|
|
PIXBUF_CHANGE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static int texture_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void
|
|
texture_fbo_free_resources (ClutterTexture *texture);
|
|
|
|
static void
|
|
clutter_texture_save_to_local_data (ClutterTexture *texture);
|
|
|
|
static void
|
|
clutter_texture_load_from_local_data (ClutterTexture *texture);
|
|
|
|
GQuark
|
|
clutter_texture_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("clutter-texture-error-quark");
|
|
}
|
|
|
|
static COGLenum
|
|
clutter_texture_quality_to_cogl_min_filter (ClutterTextureQuality buf_filter)
|
|
{
|
|
switch (buf_filter)
|
|
{
|
|
case CLUTTER_TEXTURE_QUALITY_LOW:
|
|
return CGL_NEAREST;
|
|
case CLUTTER_TEXTURE_QUALITY_MEDIUM:
|
|
return CGL_LINEAR;
|
|
case CLUTTER_TEXTURE_QUALITY_HIGH:
|
|
return CGL_LINEAR_MIPMAP_LINEAR;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static COGLenum
|
|
clutter_texture_quality_to_cogl_mag_filter (ClutterTextureQuality buf_filter)
|
|
{
|
|
switch (buf_filter)
|
|
{
|
|
case CLUTTER_TEXTURE_QUALITY_LOW:
|
|
return CGL_NEAREST;
|
|
case CLUTTER_TEXTURE_QUALITY_MEDIUM:
|
|
case CLUTTER_TEXTURE_QUALITY_HIGH:
|
|
return CGL_LINEAR;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ClutterTextureQuality
|
|
cogl_filters_to_clutter_texture_quality (COGLenum min,
|
|
COGLenum mag)
|
|
{
|
|
switch (min)
|
|
{
|
|
case CGL_NEAREST:
|
|
g_assert (mag == min); /* just for sanity */
|
|
return CLUTTER_TEXTURE_QUALITY_LOW;
|
|
case CGL_LINEAR:
|
|
g_assert (mag == min); /* just for sanity */
|
|
return CLUTTER_TEXTURE_QUALITY_MEDIUM;
|
|
case CGL_LINEAR_MIPMAP_LINEAR:
|
|
g_assert (mag == CGL_LINEAR); /* just for sanity */
|
|
return CLUTTER_TEXTURE_QUALITY_HIGH;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
texture_free_gl_resources (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->texture != COGL_INVALID_HANDLE)
|
|
{
|
|
cogl_texture_unref (priv->texture);
|
|
priv->texture = COGL_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_unrealize (ClutterActor *actor)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(actor);
|
|
priv = texture->priv;
|
|
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
return;
|
|
|
|
/* there's no need to read the pixels back when unrealizing inside
|
|
* a dispose run, and the dispose() call will release the GL
|
|
* texture data as well, so we can safely bail out now
|
|
*/
|
|
if ((CLUTTER_PRIVATE_FLAGS (actor) & CLUTTER_ACTOR_IN_DESTRUCTION) ||
|
|
priv->in_dispose)
|
|
return;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->fbo_source != COGL_INVALID_HANDLE)
|
|
{
|
|
/* Free up our fbo handle and texture resources, realize will recreate */
|
|
cogl_offscreen_unref (priv->fbo_handle);
|
|
priv->fbo_handle = COGL_INVALID_HANDLE;
|
|
texture_free_gl_resources (texture);
|
|
return;
|
|
}
|
|
|
|
if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
|
|
{
|
|
/* Move image data from video to main memory.
|
|
* GL/ES cant do this - it probably makes sense
|
|
* to move this kind of thing into a ClutterProxyTexture
|
|
* where this behaviour can be better controlled.
|
|
*
|
|
* Or make it controllable via a property.
|
|
*/
|
|
if (priv->local_data == NULL)
|
|
{
|
|
clutter_texture_save_to_local_data (texture);
|
|
CLUTTER_NOTE (TEXTURE, "moved pixels into system mem");
|
|
}
|
|
|
|
texture_free_gl_resources (texture);
|
|
}
|
|
|
|
CLUTTER_NOTE (TEXTURE, "Texture unrealized");
|
|
}
|
|
|
|
static void
|
|
clutter_texture_realize (ClutterActor *actor)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(actor);
|
|
priv = texture->priv;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->fbo_source)
|
|
{
|
|
/* Handle FBO's */
|
|
|
|
if (priv->texture != COGL_INVALID_HANDLE)
|
|
cogl_texture_unref (priv->texture);
|
|
|
|
priv->texture
|
|
= cogl_texture_new_with_size
|
|
(priv->width,
|
|
priv->height,
|
|
priv->no_slice ? -1 : priv->max_tile_waste,
|
|
priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH,
|
|
COGL_PIXEL_FORMAT_RGBA_8888);
|
|
|
|
cogl_texture_set_filters (priv->texture,
|
|
clutter_texture_quality_to_cogl_min_filter (priv->filter_quality),
|
|
clutter_texture_quality_to_cogl_mag_filter (priv->filter_quality));
|
|
|
|
priv->fbo_handle = cogl_offscreen_new_to_texture (priv->texture);
|
|
|
|
if (priv->fbo_handle == COGL_INVALID_HANDLE)
|
|
{
|
|
g_warning ("%s: Offscreen texture creation failed", G_STRLOC);
|
|
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
|
|
return;
|
|
}
|
|
|
|
clutter_actor_set_size (actor, priv->width, priv->height);
|
|
return;
|
|
}
|
|
|
|
if (priv->local_data != NULL)
|
|
{
|
|
/* Move any local image data we have from unrealization
|
|
* back into video memory.
|
|
*/
|
|
clutter_texture_load_from_local_data (texture);
|
|
}
|
|
else
|
|
{
|
|
if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
|
|
{
|
|
/* Dont allow realization with no data - note set_data
|
|
* will set realize flags.
|
|
*/
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"Texture has no image data cannot realize");
|
|
|
|
CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
|
|
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
|
|
CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
|
|
return;
|
|
}
|
|
}
|
|
|
|
CLUTTER_NOTE (TEXTURE, "Texture realized");
|
|
}
|
|
|
|
static void
|
|
clutter_texture_get_preferred_width (ClutterActor *self,
|
|
ClutterUnit for_height,
|
|
ClutterUnit *min_width_p,
|
|
ClutterUnit *natural_width_p)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (self);
|
|
ClutterTexturePrivate *priv = texture->priv;
|
|
|
|
/* FIXME If we wanted to be clever here, we could set the natural
|
|
* width to preserve aspect ratio considering for_height
|
|
*/
|
|
|
|
/* Min request is always 0 since we can scale down or clip */
|
|
if (min_width_p)
|
|
*min_width_p = 0;
|
|
|
|
if (priv->sync_actor_size)
|
|
{
|
|
if (natural_width_p)
|
|
*natural_width_p = CLUTTER_UNITS_FROM_DEVICE (priv->width);
|
|
}
|
|
else
|
|
{
|
|
if (natural_width_p)
|
|
*natural_width_p = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_get_preferred_height (ClutterActor *self,
|
|
ClutterUnit for_width,
|
|
ClutterUnit *min_height_p,
|
|
ClutterUnit *natural_height_p)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (self);
|
|
ClutterTexturePrivate *priv = texture->priv;
|
|
|
|
/* FIXME If we wanted to be clever here, we could set the natural
|
|
* height to preserve aspect ratio considering for_width
|
|
*/
|
|
|
|
/* Min request is always 0 since we can scale down or clip */
|
|
if (min_height_p)
|
|
*min_height_p = 0;
|
|
|
|
if (priv->sync_actor_size)
|
|
{
|
|
if (natural_height_p)
|
|
*natural_height_p = CLUTTER_UNITS_FROM_DEVICE (priv->height);
|
|
}
|
|
else
|
|
{
|
|
if (natural_height_p)
|
|
*natural_height_p = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_paint (ClutterActor *self)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (self);
|
|
ClutterTexturePrivate *priv = texture->priv;
|
|
gint x_1, y_1, x_2, y_2;
|
|
ClutterColor col = { 0xff, 0xff, 0xff, 0xff };
|
|
ClutterColor transparent_col = { 0, 0, 0, 0 };
|
|
ClutterFixed t_w, t_h;
|
|
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR(texture)))
|
|
clutter_actor_realize (CLUTTER_ACTOR(texture));
|
|
|
|
if (priv->fbo_handle != COGL_INVALID_HANDLE)
|
|
{
|
|
ClutterMainContext *context;
|
|
ClutterShader *shader = NULL;
|
|
|
|
context = clutter_context_get_default ();
|
|
|
|
if (context->shaders)
|
|
shader = clutter_actor_get_shader (context->shaders->data);
|
|
|
|
/* Temporarily turn of the shader on the top of the context's
|
|
* shader stack, to restore the GL pipeline to it's natural state.
|
|
*/
|
|
if (shader)
|
|
clutter_shader_set_is_enabled (shader, FALSE);
|
|
|
|
/* Redirect drawing to the fbo */
|
|
cogl_draw_buffer (COGL_OFFSCREEN_BUFFER, priv->fbo_handle);
|
|
|
|
/* cogl_paint_init is called to clear the buffers */
|
|
cogl_paint_init (&transparent_col);
|
|
|
|
/* Render out actor scene to fbo */
|
|
clutter_actor_paint (priv->fbo_source);
|
|
|
|
/* Restore drawing to the frame buffer */
|
|
cogl_draw_buffer (COGL_WINDOW_BUFFER, COGL_INVALID_HANDLE);
|
|
|
|
/* If there is a shader on top of the shader stack, turn it back on. */
|
|
if (shader)
|
|
clutter_shader_set_is_enabled (shader, TRUE);
|
|
}
|
|
|
|
CLUTTER_NOTE (PAINT,
|
|
"painting texture '%s'",
|
|
clutter_actor_get_name (self) ? clutter_actor_get_name (self)
|
|
: "unknown");
|
|
cogl_push_matrix ();
|
|
|
|
col.alpha = clutter_actor_get_paint_opacity (self);
|
|
cogl_color (&col);
|
|
|
|
clutter_actor_get_allocation_coords (self, &x_1, &y_1, &x_2, &y_2);
|
|
|
|
CLUTTER_NOTE (PAINT, "paint to x1: %i, y1: %i x2: %i, y2: %i "
|
|
"opacity: %i",
|
|
x_1, y_1, x_2, y_2,
|
|
clutter_actor_get_opacity (self));
|
|
|
|
if (priv->repeat_x && priv->width > 0)
|
|
t_w = CFX_QDIV (CLUTTER_INT_TO_FIXED (x_2 - x_1),
|
|
CLUTTER_INT_TO_FIXED (priv->width));
|
|
else
|
|
t_w = CFX_ONE;
|
|
if (priv->repeat_y && priv->height > 0)
|
|
t_h = CFX_QDIV (CLUTTER_INT_TO_FIXED (y_2 - y_1),
|
|
CLUTTER_INT_TO_FIXED (priv->height));
|
|
else
|
|
t_h = CFX_ONE;
|
|
|
|
/* Paint will have translated us */
|
|
cogl_texture_rectangle (priv->texture, 0, 0,
|
|
CLUTTER_INT_TO_FIXED (x_2 - x_1),
|
|
CLUTTER_INT_TO_FIXED (y_2 - y_1),
|
|
0, 0, t_w, t_h);
|
|
|
|
cogl_pop_matrix ();
|
|
}
|
|
|
|
static void
|
|
clutter_texture_dispose (GObject *object)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (object);
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
/* mark that we are in dispose, so when the parent class'
|
|
* dispose implementation will call unrealize on us we'll
|
|
* not try to copy back the resources from video memory
|
|
* to system memory
|
|
*/
|
|
if (!priv->in_dispose)
|
|
priv->in_dispose = TRUE;
|
|
|
|
texture_free_gl_resources (texture);
|
|
texture_fbo_free_resources (texture);
|
|
|
|
if (priv->local_data != NULL)
|
|
{
|
|
g_free (priv->local_data);
|
|
priv->local_data = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(object);
|
|
priv = texture->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_MAX_TILE_WASTE:
|
|
clutter_texture_set_max_tile_waste (texture,
|
|
g_value_get_int (value));
|
|
break;
|
|
case PROP_SYNC_SIZE:
|
|
priv->sync_actor_size = g_value_get_boolean (value);
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (texture));
|
|
break;
|
|
case PROP_REPEAT_X:
|
|
if (priv->repeat_x != g_value_get_boolean (value))
|
|
{
|
|
priv->repeat_x = !priv->repeat_x;
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (texture))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
|
|
}
|
|
break;
|
|
case PROP_REPEAT_Y:
|
|
if (priv->repeat_y != g_value_get_boolean (value))
|
|
{
|
|
priv->repeat_y = !priv->repeat_y;
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (texture))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
|
|
}
|
|
break;
|
|
case PROP_FILTER_QUALITY:
|
|
clutter_texture_set_filter_quality (texture,
|
|
g_value_get_enum (value));
|
|
break;
|
|
case PROP_COGL_TEXTURE:
|
|
clutter_texture_set_cogl_texture
|
|
(texture, (CoglHandle) g_value_get_boxed (value));
|
|
break;
|
|
case PROP_FILENAME:
|
|
clutter_texture_set_from_file (texture,
|
|
g_value_get_string (value),
|
|
NULL);
|
|
break;
|
|
case PROP_NO_SLICE:
|
|
priv->no_slice = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(object);
|
|
priv = texture->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_MAX_TILE_WASTE:
|
|
g_value_set_int (value, clutter_texture_get_max_tile_waste (texture));
|
|
break;
|
|
case PROP_PIXEL_FORMAT:
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
g_value_set_int (value, COGL_PIXEL_FORMAT_ANY);
|
|
else
|
|
g_value_set_int (value, cogl_texture_get_format (priv->texture));
|
|
break;
|
|
case PROP_SYNC_SIZE:
|
|
g_value_set_boolean (value, priv->sync_actor_size);
|
|
break;
|
|
case PROP_REPEAT_X:
|
|
g_value_set_boolean (value, priv->repeat_x);
|
|
break;
|
|
case PROP_REPEAT_Y:
|
|
g_value_set_boolean (value, priv->repeat_y);
|
|
break;
|
|
case PROP_FILTER_QUALITY:
|
|
g_value_set_enum (value, clutter_texture_get_filter_quality (texture));
|
|
break;
|
|
case PROP_COGL_TEXTURE:
|
|
g_value_set_boxed (value, clutter_texture_get_cogl_texture (texture));
|
|
break;
|
|
case PROP_NO_SLICE:
|
|
g_value_set_boolean (value, priv->no_slice);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_class_init (ClutterTextureClass *klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
ClutterActorClass *actor_class;
|
|
|
|
gobject_class = (GObjectClass*) klass;
|
|
actor_class = (ClutterActorClass*) klass;
|
|
|
|
g_type_class_add_private (klass, sizeof (ClutterTexturePrivate));
|
|
|
|
actor_class->paint = clutter_texture_paint;
|
|
actor_class->realize = clutter_texture_realize;
|
|
actor_class->unrealize = clutter_texture_unrealize;
|
|
|
|
actor_class->get_preferred_width = clutter_texture_get_preferred_width;
|
|
actor_class->get_preferred_height = clutter_texture_get_preferred_height;
|
|
|
|
gobject_class->dispose = clutter_texture_dispose;
|
|
gobject_class->set_property = clutter_texture_set_property;
|
|
gobject_class->get_property = clutter_texture_get_property;
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_SYNC_SIZE,
|
|
g_param_spec_boolean ("sync-size",
|
|
"Sync size of actor",
|
|
"Auto sync size of actor to underlying pixbuf "
|
|
"dimensions",
|
|
TRUE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_NO_SLICE,
|
|
g_param_spec_boolean ("disable-slicing",
|
|
"Disable Slicing",
|
|
"Force the underlying texture to be singlular"
|
|
"and not made of of smaller space saving "
|
|
"inidivual textures.",
|
|
FALSE,
|
|
G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));
|
|
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_REPEAT_X,
|
|
g_param_spec_boolean ("repeat-x",
|
|
"Tile underlying pixbuf in x direction",
|
|
"Repeat underlying pixbuf rather than scale "
|
|
"in x direction.",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_REPEAT_Y,
|
|
g_param_spec_boolean ("repeat-y",
|
|
"Tile underlying pixbuf in y direction",
|
|
"Repeat underlying pixbuf rather than scale "
|
|
"in y direction.",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_FILTER_QUALITY,
|
|
g_param_spec_enum ("filter-quality",
|
|
"Filter Quality",
|
|
"Rendering quality used when drawing the texture.",
|
|
CLUTTER_TYPE_TEXTURE_QUALITY,
|
|
CLUTTER_TEXTURE_QUALITY_MEDIUM,
|
|
G_PARAM_CONSTRUCT | CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_MAX_TILE_WASTE,
|
|
g_param_spec_int ("tile-waste",
|
|
"Tile dimension to waste",
|
|
"Max wastage dimension of a texture when using "
|
|
"sliced textures or -1 to disable slicing. "
|
|
"Bigger values use less textures, "
|
|
"smaller values less texture memory.",
|
|
-1,
|
|
G_MAXINT,
|
|
64,
|
|
G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_PIXEL_FORMAT,
|
|
g_param_spec_int ("pixel-format",
|
|
"Texture pixel format",
|
|
"CoglPixelFormat to use.",
|
|
0,
|
|
G_MAXINT,
|
|
COGL_PIXEL_FORMAT_RGBA_8888,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_COGL_TEXTURE,
|
|
g_param_spec_boxed ("cogl-texture",
|
|
"COGL Texture",
|
|
"The underlying COGL texture handle used to draw "
|
|
"this actor",
|
|
CLUTTER_TYPE_TEXTURE_HANDLE,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_FILENAME,
|
|
g_param_spec_string ("filename",
|
|
"Filename",
|
|
"The full path of the file containing the texture",
|
|
NULL,
|
|
G_PARAM_WRITABLE));
|
|
|
|
/**
|
|
* ClutterTexture::size-change:
|
|
* @texture: the texture which received the signal
|
|
* @width: the width of the new texture
|
|
* @height: the height of the new texture
|
|
*
|
|
* The ::size-change signal is emitted each time the size of the
|
|
* pixbuf used by @texture changes. The new size is given as
|
|
* argument to the callback.
|
|
*/
|
|
texture_signals[SIZE_CHANGE] =
|
|
g_signal_new ("size-change",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextureClass, size_change),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__INT_INT,
|
|
G_TYPE_NONE,
|
|
2, G_TYPE_INT, G_TYPE_INT);
|
|
/**
|
|
* ClutterTexture::pixbuf-change:
|
|
* @texture: the texture which received the signal
|
|
*
|
|
* The ::pixbuf-change signal is emitted each time the pixbuf
|
|
* used by @texture changes.
|
|
*/
|
|
texture_signals[PIXBUF_CHANGE] =
|
|
g_signal_new ("pixbuf-change",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextureClass, pixbuf_change),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
}
|
|
|
|
static ClutterScriptableIface *parent_scriptable_iface = NULL;
|
|
|
|
static void
|
|
clutter_texture_set_custom_property (ClutterScriptable *scriptable,
|
|
ClutterScript *script,
|
|
const gchar *name,
|
|
const GValue *value)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (scriptable);
|
|
|
|
if (strcmp ("filename", name) == 0)
|
|
{
|
|
const gchar *str = g_value_get_string (value);
|
|
gchar *path;
|
|
GError *error;
|
|
|
|
if (g_path_is_absolute (str))
|
|
path = g_strdup (str);
|
|
else
|
|
{
|
|
gchar *dirname = NULL;
|
|
gboolean is_filename = FALSE;
|
|
|
|
g_object_get (script, "filename-set", &is_filename, NULL);
|
|
if (is_filename)
|
|
{
|
|
gchar *filename = NULL;
|
|
|
|
g_object_get (script, "filename", &filename, NULL);
|
|
dirname = g_path_get_dirname (filename);
|
|
|
|
g_free (filename);
|
|
}
|
|
else
|
|
dirname = g_get_current_dir ();
|
|
|
|
path = g_build_filename (dirname, str, NULL);
|
|
g_free (dirname);
|
|
}
|
|
|
|
error = NULL;
|
|
clutter_texture_set_from_file (texture, path, &error);
|
|
if (error)
|
|
{
|
|
g_warning ("Unable to open image path at `%s': %s",
|
|
path,
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_free (path);
|
|
}
|
|
else
|
|
{
|
|
/* chain up */
|
|
if (parent_scriptable_iface->set_custom_property)
|
|
parent_scriptable_iface->set_custom_property (scriptable, script,
|
|
name,
|
|
value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_scriptable_iface_init (ClutterScriptableIface *iface)
|
|
{
|
|
parent_scriptable_iface = g_type_interface_peek_parent (iface);
|
|
|
|
if (!parent_scriptable_iface)
|
|
parent_scriptable_iface = g_type_default_interface_peek
|
|
(CLUTTER_TYPE_SCRIPTABLE);
|
|
|
|
iface->set_custom_property = clutter_texture_set_custom_property;
|
|
}
|
|
|
|
static void
|
|
clutter_texture_init (ClutterTexture *self)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
self->priv = priv = CLUTTER_TEXTURE_GET_PRIVATE (self);
|
|
|
|
priv->max_tile_waste = 64;
|
|
priv->filter_quality = CLUTTER_TEXTURE_QUALITY_MEDIUM;
|
|
priv->repeat_x = FALSE;
|
|
priv->repeat_y = FALSE;
|
|
priv->sync_actor_size = TRUE;
|
|
priv->texture = COGL_INVALID_HANDLE;
|
|
priv->fbo_handle = COGL_INVALID_HANDLE;
|
|
priv->local_data = NULL;
|
|
}
|
|
|
|
static void
|
|
clutter_texture_save_to_local_data (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
int bpp;
|
|
CoglPixelFormat pixel_format;
|
|
|
|
priv = texture->priv;
|
|
|
|
if (priv->local_data)
|
|
{
|
|
g_free (priv->local_data);
|
|
priv->local_data = NULL;
|
|
}
|
|
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
return;
|
|
|
|
priv->local_data_width = cogl_texture_get_width (priv->texture);
|
|
priv->local_data_height = cogl_texture_get_height (priv->texture);
|
|
pixel_format = cogl_texture_get_format (priv->texture);
|
|
priv->local_data_has_alpha = pixel_format & COGL_A_BIT;
|
|
bpp = priv->local_data_has_alpha ? 4 : 3;
|
|
|
|
/* Align to 4 bytes */
|
|
priv->local_data_rowstride = (priv->local_data_width * bpp + 3) & ~3;
|
|
|
|
/* Store the filter quality and max_tile_waste from the texture
|
|
properties so that they will be restored the data is loaded
|
|
again */
|
|
priv->max_tile_waste = clutter_texture_get_max_tile_waste (texture);
|
|
priv->filter_quality = clutter_texture_get_filter_quality (texture);
|
|
|
|
priv->local_data = g_malloc (priv->local_data_rowstride
|
|
* priv->local_data_height);
|
|
|
|
if (cogl_texture_get_data (priv->texture,
|
|
priv->local_data_has_alpha
|
|
? COGL_PIXEL_FORMAT_RGBA_8888
|
|
: COGL_PIXEL_FORMAT_RGB_888,
|
|
priv->local_data_rowstride,
|
|
priv->local_data) == 0)
|
|
{
|
|
g_free (priv->local_data);
|
|
priv->local_data = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_load_from_local_data (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
if (priv->local_data == NULL)
|
|
return;
|
|
|
|
clutter_texture_set_from_rgb_data (texture,
|
|
priv->local_data,
|
|
priv->local_data_has_alpha,
|
|
priv->local_data_width,
|
|
priv->local_data_height,
|
|
priv->local_data_rowstride,
|
|
priv->local_data_has_alpha ? 4: 3,
|
|
0, NULL);
|
|
|
|
g_free (priv->local_data);
|
|
priv->local_data = NULL;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_cogl_texture
|
|
* @texture: A #ClutterTexture
|
|
*
|
|
* Returns a handle to the underlying COGL texture used for drawing
|
|
* the actor. No extra reference is taken so if you need to keep the
|
|
* handle then you should call cogl_texture_ref on it.
|
|
*
|
|
* Since: 0.8
|
|
*
|
|
* Return value: COGL texture handle
|
|
**/
|
|
CoglHandle
|
|
clutter_texture_get_cogl_texture (ClutterTexture *texture)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), COGL_INVALID_HANDLE);
|
|
|
|
return texture->priv->texture;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_cogl_texture
|
|
* @texture: A #ClutterTexture
|
|
* @cogl_tex: A CoglHandle for a texture
|
|
*
|
|
* Replaces the underlying COGL texture drawn by this actor with
|
|
* @cogl_tex. A reference to the texture is taken so if the handle is
|
|
* no longer needed it should be deref'd with cogl_texture_unref.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
void
|
|
clutter_texture_set_cogl_texture (ClutterTexture *texture,
|
|
CoglHandle cogl_tex)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
gboolean size_change;
|
|
guint width, height;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
|
|
g_return_if_fail (cogl_is_texture (cogl_tex));
|
|
|
|
priv = texture->priv;
|
|
|
|
width = cogl_texture_get_width (cogl_tex);
|
|
height = cogl_texture_get_height (cogl_tex);
|
|
|
|
/* Reference the new texture now in case it is the same one we are
|
|
already using */
|
|
cogl_texture_ref (cogl_tex);
|
|
|
|
/* Remove FBO if exisiting */
|
|
if (priv->fbo_source)
|
|
texture_fbo_free_resources (texture);
|
|
/* Remove old texture */
|
|
texture_free_gl_resources (texture);
|
|
/* Use the new texture */
|
|
priv->texture = cogl_tex;
|
|
|
|
size_change = width != priv->width || height != priv->height;
|
|
priv->width = width;
|
|
priv->height = height;
|
|
|
|
CLUTTER_NOTE (TEXTURE, "set size %ix%i\n",
|
|
priv->width,
|
|
priv->height);
|
|
|
|
CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);
|
|
|
|
if (size_change)
|
|
{
|
|
g_signal_emit (texture, texture_signals[SIZE_CHANGE], 0,
|
|
priv->width,
|
|
priv->height);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (texture));
|
|
}
|
|
|
|
/* rename signal */
|
|
g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);
|
|
|
|
g_object_notify (G_OBJECT (texture), "cogl-texture");
|
|
|
|
/* If resized actor may need resizing but paint() will do this */
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (texture))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
|
|
}
|
|
|
|
static gboolean
|
|
clutter_texture_set_from_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
CoglPixelFormat source_format,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp,
|
|
GError **error)
|
|
{
|
|
CoglHandle new_texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
if ((new_texture = cogl_texture_new_from_data
|
|
(width, height,
|
|
priv->no_slice ? -1 : priv->max_tile_waste,
|
|
priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH,
|
|
source_format,
|
|
COGL_PIXEL_FORMAT_ANY,
|
|
rowstride,
|
|
data)) == COGL_INVALID_HANDLE)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Failed to create COGL texture");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
cogl_texture_set_filters (new_texture,
|
|
clutter_texture_quality_to_cogl_min_filter (priv->filter_quality),
|
|
clutter_texture_quality_to_cogl_mag_filter (priv->filter_quality));
|
|
|
|
clutter_texture_set_cogl_texture (texture, new_texture);
|
|
|
|
cogl_texture_unref (new_texture);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_from_rgb_data:
|
|
* @texture: A #ClutterTexture
|
|
* @data: Image data in RGBA type colorspace.
|
|
* @has_alpha: Set to TRUE if image data has an alpha channel.
|
|
* @width: Width in pixels of image data.
|
|
* @height: Height in pixels of image data
|
|
* @rowstride: Distance in bytes between row starts.
|
|
* @bpp: bytes per pixel ( Currently only 4 supported )
|
|
* @flags: #ClutterTextureFlags
|
|
* @error: return location for a #GError, or %NULL.
|
|
*
|
|
* Sets #ClutterTexture image data.
|
|
*
|
|
* Note: This function is likely to change in future versions.
|
|
*
|
|
* Return value: %TRUE on success, %FALSE on failure.
|
|
*
|
|
* Since: 0.4.
|
|
**/
|
|
gboolean
|
|
clutter_texture_set_from_rgb_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp,
|
|
ClutterTextureFlags flags,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
CoglPixelFormat source_format;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
|
|
|
|
priv = texture->priv;
|
|
|
|
/* Convert the flags to a CoglPixelFormat */
|
|
if (has_alpha)
|
|
{
|
|
if (bpp != 4)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Unsupported BPP");
|
|
return FALSE;
|
|
}
|
|
source_format = COGL_PIXEL_FORMAT_RGBA_8888;
|
|
}
|
|
else
|
|
{
|
|
if (bpp != 3)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Unsupported BPP");
|
|
return FALSE;
|
|
}
|
|
source_format = COGL_PIXEL_FORMAT_RGB_888;
|
|
}
|
|
if ((flags & CLUTTER_TEXTURE_RGB_FLAG_BGR))
|
|
source_format |= COGL_BGR_BIT;
|
|
if ((flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT))
|
|
source_format |= COGL_PREMULT_BIT;
|
|
|
|
return clutter_texture_set_from_data (texture, data,
|
|
source_format,
|
|
width, height,
|
|
rowstride, bpp,
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_from_yuv_data:
|
|
* @texture: A #ClutterTexture
|
|
* @data: Image data in YUV type colorspace.
|
|
* @width: Width in pixels of image data.
|
|
* @height: Height in pixels of image data
|
|
* @flags: #ClutterTextureFlags
|
|
* @error: Return location for a #GError, or %NULL.
|
|
*
|
|
* Sets a #ClutterTexture from YUV image data. If an error occurred,
|
|
* %FALSE is returned and @error is set.
|
|
*
|
|
* This function is likely to change in future versions.
|
|
*
|
|
* Return value: %TRUE if the texture was successfully updated
|
|
*
|
|
* Since 0.4.
|
|
**/
|
|
gboolean
|
|
clutter_texture_set_from_yuv_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gint width,
|
|
gint height,
|
|
ClutterTextureFlags flags,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
|
|
|
|
if (!clutter_feature_available (CLUTTER_FEATURE_TEXTURE_YUV))
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_NO_YUV,
|
|
"YUV textures are not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
priv = texture->priv;
|
|
|
|
/* Convert the flags to a CoglPixelFormat */
|
|
if ((flags & CLUTTER_TEXTURE_YUV_FLAG_YUV2))
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"YUV2 not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
return clutter_texture_set_from_data (texture, data,
|
|
COGL_PIXEL_FORMAT_YUV,
|
|
width, height,
|
|
width * 3, 3,
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_from_file:
|
|
* @texture: A #ClutterTexture
|
|
* @filename: The filename of the image in GLib file name encoding
|
|
* @error: Return location for a #GError, or %NULL
|
|
*
|
|
* Sets the #ClutterTexture image data from an image file. In case of
|
|
* failure, %FALSE is returned and @error is set.
|
|
*
|
|
* Return value: %TRUE if the image was successfully loaded and set
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
gboolean
|
|
clutter_texture_set_from_file (ClutterTexture *texture,
|
|
const gchar *filename,
|
|
GError **error)
|
|
{
|
|
CoglHandle new_texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if ((new_texture = cogl_texture_new_from_file
|
|
(filename,
|
|
priv->no_slice ? -1 : priv->max_tile_waste,
|
|
priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH,
|
|
COGL_PIXEL_FORMAT_ANY,
|
|
error))
|
|
== COGL_INVALID_HANDLE)
|
|
{
|
|
/* If COGL didn't give an error then make one up */
|
|
if (error && *error == NULL)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Failed to create COGL texture");
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
cogl_texture_set_filters (new_texture,
|
|
clutter_texture_quality_to_cogl_min_filter (priv->filter_quality),
|
|
clutter_texture_quality_to_cogl_mag_filter (priv->filter_quality));
|
|
|
|
clutter_texture_set_cogl_texture (texture, new_texture);
|
|
|
|
cogl_texture_unref (new_texture);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_filter_quality:
|
|
* @texture: a #ClutterTexture
|
|
* @filter_quality: new filter quality value
|
|
*
|
|
* Sets the filter quality when scaling a texture. The quality is an
|
|
* enumeration currently the following values are supported:
|
|
* %CLUTTER_TEXTURE_QUALITY_LOW which is fast but only uses nearest neighbour
|
|
* interpolation. %CLUTTER_TEXTURE_QUALITY_MEDIUM which is computationally a
|
|
* bit more expensive (bilinear interpolation), and
|
|
* %CLUTTER_TEXTURE_QUALITY_HIGH which uses extra texture memory resources to
|
|
* improve scaled down rendering as well (by using mipmaps). The default value
|
|
* is %CLUTTER_TEXTURE_QUALITY_MEDIUM.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
void
|
|
clutter_texture_set_filter_quality (ClutterTexture *texture,
|
|
ClutterTextureQuality filter_quality)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
ClutterTextureQuality old_quality;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
|
|
|
|
priv = texture->priv;
|
|
|
|
old_quality = clutter_texture_get_filter_quality (texture);
|
|
if (filter_quality != old_quality)
|
|
{
|
|
priv->filter_quality = filter_quality;
|
|
|
|
if (priv->texture != COGL_INVALID_HANDLE)
|
|
cogl_texture_set_filters (priv->texture,
|
|
clutter_texture_quality_to_cogl_min_filter (priv->filter_quality),
|
|
clutter_texture_quality_to_cogl_mag_filter (priv->filter_quality));
|
|
|
|
if ((old_quality == CLUTTER_TEXTURE_QUALITY_HIGH ||
|
|
filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) &&
|
|
CLUTTER_ACTOR_IS_REALIZED (texture))
|
|
{
|
|
clutter_texture_unrealize (CLUTTER_ACTOR (texture));
|
|
clutter_texture_realize (CLUTTER_ACTOR (texture));
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (texture), "filter-quality");
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (texture))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_filter_quality
|
|
* @texture: A #ClutterTexture
|
|
*
|
|
* Gets the filter quality used when scaling a texture.
|
|
*
|
|
* Return value: The filter quality value.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
ClutterTextureQuality
|
|
clutter_texture_get_filter_quality (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), 0);
|
|
|
|
priv = texture->priv;
|
|
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
return texture->priv->max_tile_waste;
|
|
else
|
|
/* If we have a valid texture handle then use the filter quality
|
|
from that instead */
|
|
|
|
return cogl_filters_to_clutter_texture_quality (
|
|
cogl_texture_get_min_filter (texture->priv->texture),
|
|
cogl_texture_get_mag_filter (texture->priv->texture));
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_max_tile_waste
|
|
* @texture: A #ClutterTexture
|
|
* @max_tile_waste: Maximum amount of waste in pixels or -1
|
|
*
|
|
* Sets the maximum number of pixels in either axis that can be wasted
|
|
* for an individual texture slice. If -1 is specified then the
|
|
* texture is forced not to be sliced and the texture creation will
|
|
* fail if the hardware can't create a texture large enough.
|
|
*
|
|
* The value is only used when first creating a texture so changing it
|
|
* after the texture data has been set has no effect.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
void
|
|
clutter_texture_set_max_tile_waste (ClutterTexture *texture,
|
|
gint max_tile_waste)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
|
|
|
|
priv = texture->priv;
|
|
|
|
/* There's no point in changing the max_tile_waste if the texture
|
|
has already been created because it will be overridden with the
|
|
value from the texture handle */
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
priv->max_tile_waste = max_tile_waste;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_max_tile_waste
|
|
* @texture: A #ClutterTexture
|
|
*
|
|
* Gets the maximum waste that will be used when creating a texture or
|
|
* -1 if slicing is disabled.
|
|
*
|
|
* Return value: The maximum waste or -1 if the texture waste is
|
|
* unlimited.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
gint
|
|
clutter_texture_get_max_tile_waste (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), 0);
|
|
|
|
priv = texture->priv;
|
|
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
return texture->priv->max_tile_waste;
|
|
else
|
|
/* If we have a valid texture handle then use the value from that
|
|
instead */
|
|
return cogl_texture_get_max_waste (texture->priv->texture);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_new_from_file:
|
|
* @filename: The name of an image file to load.
|
|
* @error: Return locatoin for an error.
|
|
*
|
|
* Creates a new ClutterTexture actor to display the image contained a
|
|
* file. If the image failed to load then NULL is returned and @error
|
|
* is set.
|
|
*
|
|
* Return value: A newly created #ClutterTexture object or NULL on
|
|
* error.
|
|
*
|
|
* Since: 0.8
|
|
**/
|
|
ClutterActor*
|
|
clutter_texture_new_from_file (const gchar *filename,
|
|
GError **error)
|
|
{
|
|
ClutterActor *texture = clutter_texture_new ();
|
|
|
|
if (!clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
|
filename, error))
|
|
{
|
|
g_object_ref_sink (texture);
|
|
g_object_unref (texture);
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
return texture;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_new:
|
|
*
|
|
* Creates a new empty #ClutterTexture object.
|
|
*
|
|
* Return value: A newly created #ClutterTexture object.
|
|
**/
|
|
ClutterActor *
|
|
clutter_texture_new (void)
|
|
{
|
|
return g_object_new (CLUTTER_TYPE_TEXTURE, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_base_size:
|
|
* @texture: A #ClutterTexture
|
|
* @width: Pointer to gint to be populated with width value if non NULL.
|
|
* @height: Pointer to gint to be populated with height value if non NULL.
|
|
*
|
|
* Gets the size in pixels of the untransformed underlying texture pixbuf data.
|
|
*
|
|
**/
|
|
void /* FIXME: rename to get_image_size */
|
|
clutter_texture_get_base_size (ClutterTexture *texture,
|
|
gint *width,
|
|
gint *height)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
|
|
|
|
/* Attempt to realize, mainly for subclasses ( such as labels )
|
|
* which may not create pixbuf data and thus base size until
|
|
* realization happens.
|
|
*/
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (texture))
|
|
clutter_actor_realize (CLUTTER_ACTOR (texture));
|
|
|
|
if (width)
|
|
*width = texture->priv->width;
|
|
|
|
if (height)
|
|
*height = texture->priv->height;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_area_from_rgb_data:
|
|
* @texture: A #ClutterTexture
|
|
* @data: Image data in RGB type colorspace.
|
|
* @has_alpha: Set to TRUE if image data has an alpha channel.
|
|
* @x: X coordinate of upper left corner of region to update.
|
|
* @y: Y coordinate of upper left corner of region to update.
|
|
* @width: Width in pixels of region to update.
|
|
* @height: Height in pixels of region to update.
|
|
* @rowstride: Distance in bytes between row starts on source buffer.
|
|
* @bpp: bytes per pixel ( Currently only 4 supported )
|
|
* @flags: #ClutterTextureFlags
|
|
* @error: return location for a #GError, or %NULL
|
|
*
|
|
* Updates a sub-region of the pixel data in a #ClutterTexture.
|
|
*
|
|
* Return value: %TRUE on success, %FALSE on failure.
|
|
*
|
|
* Since 0.6.
|
|
*/
|
|
gboolean
|
|
clutter_texture_set_area_from_rgb_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint x,
|
|
gint y,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp,
|
|
ClutterTextureFlags flags,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
CoglPixelFormat source_format;
|
|
|
|
priv = texture->priv;
|
|
|
|
if (has_alpha)
|
|
{
|
|
if (bpp != 4)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Unsupported BPP");
|
|
return FALSE;
|
|
}
|
|
source_format = COGL_PIXEL_FORMAT_RGBA_8888;
|
|
}
|
|
else
|
|
{
|
|
if (bpp != 3)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Unsupported BPP");
|
|
return FALSE;
|
|
}
|
|
source_format = COGL_PIXEL_FORMAT_RGB_888;
|
|
}
|
|
if ((flags & CLUTTER_TEXTURE_RGB_FLAG_BGR))
|
|
source_format |= COGL_BGR_BIT;
|
|
if ((flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT))
|
|
source_format |= COGL_PREMULT_BIT;
|
|
|
|
clutter_actor_realize (CLUTTER_ACTOR (texture));
|
|
|
|
if (priv->texture == COGL_INVALID_HANDLE)
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Failed to realize actor");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!cogl_texture_set_region (priv->texture,
|
|
0, 0,
|
|
x, y, width, height,
|
|
width, height,
|
|
source_format,
|
|
rowstride,
|
|
data))
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
|
|
"Failed to upload COGL texture data");
|
|
return FALSE;
|
|
}
|
|
|
|
/* rename signal */
|
|
g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (texture))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_fbo_source_size_change (GObject *object,
|
|
GParamSpec *param_spec,
|
|
ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv = texture->priv;
|
|
guint w, h;
|
|
|
|
clutter_actor_get_transformed_size (priv->fbo_source, &w, &h);
|
|
|
|
if (w != priv->width || h != priv->height)
|
|
{
|
|
/* tear down the FBO */
|
|
cogl_offscreen_unref (priv->fbo_handle);
|
|
|
|
texture_free_gl_resources (texture);
|
|
|
|
priv->width = w;
|
|
priv->height = h;
|
|
|
|
priv->texture = cogl_texture_new_with_size (priv->width,
|
|
priv->height,
|
|
-1,
|
|
priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH,
|
|
COGL_PIXEL_FORMAT_RGBA_8888);
|
|
|
|
cogl_texture_set_filters (priv->texture,
|
|
clutter_texture_quality_to_cogl_min_filter (priv->filter_quality),
|
|
clutter_texture_quality_to_cogl_mag_filter (priv->filter_quality));
|
|
|
|
priv->fbo_handle = cogl_offscreen_new_to_texture (priv->texture);
|
|
|
|
if (priv->fbo_handle == COGL_INVALID_HANDLE)
|
|
{
|
|
g_warning ("%s: Offscreen texture creation failed", G_STRLOC);
|
|
CLUTTER_ACTOR_UNSET_FLAGS (CLUTTER_ACTOR (texture),
|
|
CLUTTER_ACTOR_REALIZED);
|
|
return;
|
|
}
|
|
|
|
clutter_actor_set_size (CLUTTER_ACTOR(texture), w, h);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_fbo_parent_change (ClutterActor *actor,
|
|
ClutterActor *old_parent,
|
|
ClutterTexture *texture)
|
|
{
|
|
ClutterActor *parent = CLUTTER_ACTOR(texture);
|
|
|
|
while ((parent = clutter_actor_get_parent (parent)) != NULL)
|
|
if (parent == actor)
|
|
{
|
|
g_warning ("Offscreen texture is ancestor of source!");
|
|
/* Desperate but will avoid infinite loops */
|
|
clutter_actor_unparent (actor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_new_from_actor:
|
|
* @actor: A source #ClutterActor
|
|
*
|
|
* Creates a new #ClutterTexture object with its source a prexisting
|
|
* actor (and associated children). The textures content will contain
|
|
* 'live' redirected output of the actors scene.
|
|
*
|
|
* Note this function is intented as a utility call for uniformly applying
|
|
* shaders to groups and other potential visual effects. It requires that
|
|
* the %CLUTTER_FEATURE_OFFSCREEN feature is supported by the current backend
|
|
* and the target system.
|
|
*
|
|
* Some tips on usage:
|
|
*
|
|
* <itemizedlist>
|
|
* <listitem>
|
|
* <para>The source actor must be made visible (i.e by calling
|
|
* #clutter_actor_show). The source actor does not however have to
|
|
* have a parent.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>Avoid reparenting the source with the created texture.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>A group can be padded with a transparent rectangle as to
|
|
* provide a border to contents for shader output (blurring text
|
|
* for example).</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>The texture will automatically resize to contain a further
|
|
* transformed source. However, this involves overhead and can be
|
|
* avoided by placing the source actor in a bounding group
|
|
* sized large enough to contain any child tranformations.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>Uploading pixel data to the texture (e.g by using
|
|
* clutter_actor_set_from_file()) will destroy the offscreen texture data
|
|
* and end redirection.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>cogl_texture_get_data() with the handle returned by
|
|
* clutter_texture_get_cogl_texture() can be used to read the
|
|
* offscreen texture pixels into a pixbuf.</para>
|
|
* </listitem>
|
|
* </itemizedlist>
|
|
*
|
|
* Return value: A newly created #ClutterTexture object, or %NULL on failure.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
ClutterActor *
|
|
clutter_texture_new_from_actor (ClutterActor *actor)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
guint w, h;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
|
|
|
|
if (clutter_feature_available (CLUTTER_FEATURE_OFFSCREEN) == FALSE)
|
|
return NULL;
|
|
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (actor))
|
|
{
|
|
clutter_actor_realize (actor);
|
|
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (actor))
|
|
return NULL;
|
|
}
|
|
|
|
clutter_actor_get_transformed_size (actor, &w, &h);
|
|
|
|
if (w == 0 || h == 0)
|
|
return NULL;
|
|
|
|
/* Hopefully now were good.. */
|
|
texture = g_object_new (CLUTTER_TYPE_TEXTURE,
|
|
"disable-slicing", TRUE,
|
|
NULL);
|
|
|
|
priv = texture->priv;
|
|
|
|
priv->fbo_source = g_object_ref(actor);
|
|
|
|
/* Connect up any signals which could change our underlying size */
|
|
g_signal_connect (actor,
|
|
"notify::width",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::height",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::scale-x",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::scale-y",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::rotation-angle-x",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::rotation-angle-y",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::rotation-angle-z",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
|
|
/* And a warning if the source becomes a child of the texture */
|
|
g_signal_connect (actor,
|
|
"parent-set",
|
|
G_CALLBACK(on_fbo_parent_change),
|
|
texture);
|
|
|
|
priv->width = w;
|
|
priv->height = h;
|
|
|
|
clutter_actor_set_size (CLUTTER_ACTOR(texture), priv->width, priv->height);
|
|
|
|
return CLUTTER_ACTOR(texture);
|
|
}
|
|
|
|
static void
|
|
texture_fbo_free_resources (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->fbo_source != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_func
|
|
(priv->fbo_source,
|
|
G_CALLBACK(on_fbo_parent_change),
|
|
texture);
|
|
|
|
g_signal_handlers_disconnect_by_func
|
|
(priv->fbo_source,
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
|
|
g_object_unref (priv->fbo_source);
|
|
|
|
priv->fbo_source = NULL;
|
|
}
|
|
|
|
if (priv->fbo_handle != COGL_INVALID_HANDLE)
|
|
{
|
|
cogl_offscreen_unref (priv->fbo_handle);
|
|
priv->fbo_handle = COGL_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
GType
|
|
clutter_texture_handle_get_type (void)
|
|
{
|
|
static GType our_type = 0;
|
|
|
|
if (G_UNLIKELY (!our_type))
|
|
{
|
|
our_type =
|
|
g_boxed_type_register_static (I_("ClutterTextureHandle"),
|
|
(GBoxedCopyFunc) cogl_texture_ref,
|
|
(GBoxedFreeFunc) cogl_texture_unref);
|
|
}
|
|
|
|
return our_type;
|
|
}
|