/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Compositor sub-project
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *
 *  GPAC 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; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "drawable.h"
#include "visual_manager.h"
#include "nodes_stacks.h"

#ifndef GPAC_DISABLE_COMPOSITOR

/*default draw routine*/
void drawable_draw(Drawable *drawable, GF_TraverseState *tr_state)
{
	visual_2d_texture_path(tr_state->visual, tr_state->ctx->drawable->path, tr_state->ctx, tr_state);
	visual_2d_draw_path(tr_state->visual, tr_state->ctx->drawable->path, tr_state->ctx, NULL, NULL, tr_state);
}

void call_drawable_draw(DrawableContext *ctx, GF_TraverseState *tr_state, Bool set_cyclic)
{
	if (ctx->cliper) {
		gf_node_traverse(ctx->cliper, tr_state);
	}

	if (ctx->drawable->flags & DRAWABLE_USE_TRAVERSE_DRAW) {
		if (set_cyclic)
			gf_node_allow_cyclic_traverse(ctx->drawable->node);
		gf_node_traverse(ctx->drawable->node, tr_state);
	} else {
		drawable_draw(ctx->drawable, tr_state);
	}
	if (ctx->cliper)
		gf_evg_surface_set_mask_mode(tr_state->visual->raster_surface, GF_EVGMASK_NONE);
}

/*default point_over routine*/
#ifndef GPAC_DISABLE_VRML
void vrml_drawable_pick(Drawable *drawable, GF_TraverseState *tr_state)
{
	DrawAspect2D asp;
	GF_Matrix2D inv_2d;
	StrikeInfo2D *si;
	Fixed x, y;
	u32 i, count;
	GF_Node *appear = tr_state->override_appearance ? tr_state->override_appearance : tr_state->appear;
	GF_Compositor *compositor = tr_state->visual->compositor;

#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		visual_3d_vrml_drawable_pick(drawable->node, tr_state, NULL, drawable);
		return;
	}
#endif
	gf_mx2d_copy(inv_2d, tr_state->transform);
	gf_mx2d_inverse(&inv_2d);
	x = tr_state->ray.orig.x;
	y = tr_state->ray.orig.y;
	gf_mx2d_apply_coords(&inv_2d, &x, &y);

	memset(&asp, 0, sizeof(DrawAspect2D));
	drawable_get_aspect_2d_mpeg4(drawable->node, &asp, tr_state);

	/*MPEG-4 picking is always on regardless of color properties*/
	if (/*tr_state->ctx->aspect.fill_texture */
	    /* (tr_state->pick_type<PICK_FULL) */
	    /* GF_COL_A(asp.fill_color)*/
	    1) {
		if (gf_path_point_over(drawable->path, x, y)) {
			goto picked;
		}
	}

	if (asp.pen_props.width || asp.line_texture ) {
		si = drawable_get_strikeinfo(tr_state->visual->compositor, drawable, &asp, appear, NULL, 0, NULL);
		if (si && si->outline && gf_path_point_over(si->outline, x, y)) {
			goto picked;
		}
	}
	return;

picked:
	compositor->hit_local_point.x = x;
	compositor->hit_local_point.y = y;
	compositor->hit_local_point.z = 0;

	gf_mx_from_mx2d(&compositor->hit_world_to_local, &tr_state->transform);
	gf_mx_from_mx2d(&compositor->hit_local_to_world, &inv_2d);

	gf_list_reset(compositor->hit_use_stack);
	compositor->hit_node = drawable->node;
	compositor->hit_use_dom_events = 0;
	compositor->hit_normal.x = compositor->hit_normal.y = 0;
	compositor->hit_normal.z = FIX_ONE;
	compositor->hit_texcoords.x = gf_divfix(x - drawable->path->bbox.x, drawable->path->bbox.width);
	compositor->hit_texcoords.y = FIX_ONE - gf_divfix(drawable->path->bbox.y - y, drawable->path->bbox.height);

#ifndef GPAC_DISABLE_VRML
	if (compositor_is_composite_texture(appear)) {
		compositor->hit_appear = appear;
	} else
#endif
	{
		compositor->hit_appear = NULL;
	}
	compositor->hit_text = NULL;

	gf_list_reset(tr_state->visual->compositor->sensors);
	count = gf_list_count(tr_state->vrml_sensors);
	for (i=0; i<count; i++) {
		gf_list_add(tr_state->visual->compositor->sensors, gf_list_get(tr_state->vrml_sensors, i));
	}
}
#endif /*GPAC_DISABLE_VRML*/

void drawable_init_ex(Drawable *tmp)
{
	tmp->path = gf_path_new();
	GF_SAFEALLOC(tmp->dri, DRInfo);
	if (tmp->dri) {
		/*allocate a default bounds container*/
		GF_SAFEALLOC(tmp->dri->current_bounds, BoundInfo);
	}
}

Drawable *drawable_new()
{
	Drawable *tmp;
	GF_SAFEALLOC(tmp, Drawable)
	if (!tmp) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate drawable object\n"));
		return NULL;
	}
	tmp->path = gf_path_new();
	/*allocate a default visual container*/
	GF_SAFEALLOC(tmp->dri, DRInfo);
	if (tmp->dri) {
		/*allocate a default bounds container*/
		GF_SAFEALLOC(tmp->dri->current_bounds, BoundInfo);
	}
	
	if (!tmp->dri || !tmp->dri->current_bounds) {
		if (tmp->dri) gf_free(tmp->dri);
		gf_path_del(tmp->path);
		gf_free(tmp);
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate drawable object bounds\n"));
		return NULL;
	}
	return tmp;
}

void drawable_reset_bounds(Drawable *dr, GF_VisualManager *visual)
{
	DRInfo *dri;
	BoundInfo *bi, *_cur;

	dri = dr->dri;
	while (dri) {
		if (dri->visual != visual) {
			dri = dri->next;
			continue;
		}
		/*destroy previous bounds only, since current ones are always used during traversing*/
		bi = dri->previous_bounds;
		while (bi) {
			_cur = bi;
			bi = bi->next;
			gf_free(_cur);
		}
		dri->previous_bounds = NULL;
		return;
	}
}

void drawable_del_ex(Drawable *dr, GF_Compositor *compositor, Bool no_free)
{
	StrikeInfo2D *si;
	DRInfo *dri;
	BoundInfo *bi, *_cur;


	/*remove node from all visuals it's on*/
	dri = dr->dri;
	while (dri) {
		DRInfo *cur;
		Bool is_reg = compositor ? gf_sc_visual_is_registered(compositor, dri->visual) : 0;

		bi = dri->current_bounds;
		while (bi) {
			_cur = bi;
			if (is_reg && bi->clip.width) {
				ra_add(&dri->visual->to_redraw, &bi->clip);
			}
			bi = bi->next;
			gf_free(_cur);
		}
		bi = dri->previous_bounds;
		while (bi) {
			_cur = bi;
			if (is_reg && bi->clip.width) {
				ra_add(&dri->visual->to_redraw, &bi->clip);
			}
			bi = bi->next;
			gf_free(_cur);
		}
		if (is_reg) visual_2d_drawable_delete(dri->visual, dr);
		cur = dri;
		dri = dri->next;
		gf_free(cur);
	}
	if (compositor) {
		gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME);

		/*check node isn't being tracked*/
		if (compositor->grab_node==dr->node)
			compositor->grab_node = NULL;

		if (compositor->focus_node==dr->node) {
			compositor->focus_node = NULL;
			compositor->focus_text_type = 0;
		}
		if (compositor->hit_node==dr->node) compositor->hit_node = NULL;
		if (compositor->hit_text==dr->node) compositor->hit_text = NULL;
	}

	/*remove path object*/
	if (dr->path) gf_path_del(dr->path);

#ifndef GPAC_DISABLE_3D
	if (dr->mesh) mesh_free(dr->mesh);
#endif

	si = dr->outline;
	while (si) {
		StrikeInfo2D *next = si->next;
		/*remove from main strike list*/
		if (compositor) gf_list_del_item(compositor->strike_bank, si);
		delete_strikeinfo2d(si);
		si = next;
	}
	if (!no_free)
		gf_free(dr);
}

void drawable_del(Drawable *dr)
{
	GF_Compositor *compositor = gf_sc_get_compositor(dr->node);
	drawable_del_ex(dr, compositor, GF_FALSE);
}
void drawable_node_del(GF_Node *node)
{
	drawable_del( (Drawable *)gf_node_get_private(node) );
}

Drawable *drawable_stack_new(GF_Compositor *compositor, GF_Node *node)
{
	Drawable *stack = drawable_new();
	stack->node = node;
	gf_node_set_private(node, stack);
	return stack;
}

static BoundInfo *drawable_check_alloc_bounds(struct _drawable_context *ctx, GF_VisualManager *visual)
{
	DRInfo *dri, *prev;
	BoundInfo *bi, *_prev;

	/*get bounds info for this visual manager*/
	prev = NULL;
	dri = ctx->drawable->dri;
	while (dri) {
		if (dri->visual == visual) break;
		if (!dri->visual) {
			dri->visual = visual;
			break;
		}
		prev = dri;
		dri = dri->next;
	}
	if (!dri) {
		GF_SAFEALLOC(dri, DRInfo);
		if (!dri) return NULL;
		dri->visual = visual;
		if (prev) prev->next = dri;
		else ctx->drawable->dri = dri;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Allocating new bound info storage on visual %08x for drawable %s\n", visual, gf_node_get_class_name(ctx->drawable->node)));
	}

	/*get available bound info slot*/
	_prev = NULL;
	bi = dri->current_bounds;
	while (bi) {
		if (!bi->clip.width) break;
		_prev = bi;
		bi = bi->next;
	}
	if (!bi) {
		GF_SAFEALLOC(bi, BoundInfo);
		if (!bi) return NULL;
		if (_prev) {
			_prev->next = bi;
		}
		else {
			dri->current_bounds = bi;
		}
		//GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Allocating new bound info for drawable %s\n", gf_node_get_class_name(ctx->drawable->node)));
	}
	/*reset next bound info*/
	if (bi->next) bi->next->clip.width = 0;
	return bi;
}

void drawable_reset_group_highlight(GF_TraverseState *tr_state, GF_Node *n)
{
	Drawable *hlight = tr_state->visual->compositor->focus_highlight;
	if (hlight && (n == gf_node_get_private(hlight->node))) gf_node_set_private(hlight->node, NULL);
}

void drawable_mark_modified(Drawable *drawable, GF_TraverseState *tr_state)
{
	/*mark drawable as modified*/
	drawable->flags |= tr_state->visual->bounds_tracker_modif_flag;
	/*and remove overlay flag*/
	drawable->flags &= ~DRAWABLE_IS_OVERLAY;

	drawable_reset_group_highlight(tr_state, drawable->node);
}

/*move current bounds to previous bounds*/
Bool drawable_flush_bounds(Drawable *drawable, GF_VisualManager *on_visual, u32 mode2d)
{
	Bool was_drawn;
	DRInfo *dri;
	BoundInfo *tmp;

	/*reset node modified flag*/
	drawable->flags &= ~DRAWABLE_HAS_CHANGED;
	if (drawable->flags & DRAWABLE_HAS_CHANGED_IN_LAST_TRAVERSE) {
		drawable->flags |= DRAWABLE_HAS_CHANGED;
		drawable->flags &= ~DRAWABLE_HAS_CHANGED_IN_LAST_TRAVERSE;
	}

	dri = drawable->dri;
	while (dri) {
		if (dri->visual == on_visual) break;
		dri = dri->next;
	}
	if (!dri) return 0;

	was_drawn = (dri->current_bounds && dri->current_bounds->clip.width) ? 1 : 0;

	if (mode2d) {
		/*permanent direct drawing mode, destroy previous bounds*/
		if (mode2d==1) {
			if (dri->previous_bounds) {
				GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Destroying previous bounds info for drawable %s\n", gf_node_get_class_name(drawable->node)));
				while (dri->previous_bounds) {
					BoundInfo *bi = dri->previous_bounds;
					dri->previous_bounds = bi->next;
					gf_free(bi);
				}
			}
		}
	}
	/*indirect drawing, flush bounds*/
	else {
		tmp = dri->previous_bounds;
		dri->previous_bounds = dri->current_bounds;
		dri->current_bounds = tmp;
	}
	/*reset first allocated bound*/
	if (dri->current_bounds) dri->current_bounds->clip.width = 0;

	drawable->flags &= ~DRAWABLE_DRAWN_ON_VISUAL;
	return was_drawn;
}

/*
	return 1 if same bound is found in previous list (and remove it from the list)
	return 0 otherwise
*/

Bool drawable_has_same_bounds(struct _drawable_context *ctx, GF_VisualManager *visual)
{
	DRInfo *dri;
	BoundInfo *bi;

	dri = ctx->drawable->dri;
	while (dri) {
		if (dri->visual == visual) break;
		dri = dri->next;
	}
	if (!dri) return 0;

	bi = dri->previous_bounds;
	while (bi) {
		if (
		    /*if 0, end of bounds used in the previous pass*/
		    bi->clip.width
		    /*we need the same Appearance || parent <use>*/
		    && (bi->extra_check == ctx->appear)
		    /*we need exact same cliper*/
		    && (bi->clip.x==ctx->bi->clip.x) && (bi->clip.y==ctx->bi->clip.y)
		    && (bi->clip.width==ctx->bi->clip.width) && (bi->clip.height==ctx->bi->clip.height)
		    /*only check x and y (if w or h have changed, object has changed -> bounds info has been reset*/
		    && (bi->unclip.x==ctx->bi->unclip.x) && (bi->unclip.y==ctx->bi->unclip.y)
		) {
			/*remove*/
			bi->clip.width = 0;
			return 1;
		}
		bi = bi->next;
	}
	return 0;
}

/*
	return any previous bounds related to the same visual in @rc if any
	if nothing found return 0
*/
Bool drawable_get_previous_bound(Drawable *drawable, GF_IRect *rc, GF_VisualManager *visual)
{
	DRInfo *dri;
	BoundInfo *bi;

	dri = drawable->dri;
	while (dri) {
		if (dri->visual == visual) break;
		dri = dri->next;
	}
	if (!dri) return 0;

	bi = dri->previous_bounds;
	while (bi) {
		if (bi->clip.width) {
			*rc = bi->clip;
			bi->clip.width = 0;
			return 1;
		}
		bi = bi->next;
	}
	return 0;
}



DrawableContext *NewDrawableContext()
{
	DrawableContext *tmp;
	GF_SAFEALLOC(tmp, DrawableContext);
	return tmp;
}
void DeleteDrawableContext(DrawableContext *ctx)
{
	drawctx_reset(ctx);
	gf_free(ctx);
}
void drawctx_reset(DrawableContext *ctx)
{
	DrawableContext *next = ctx->next;
	if (ctx->col_mat) gf_free(ctx->col_mat);
	memset(ctx, 0, sizeof(DrawableContext));
	ctx->next = next;

	/*by default all nodes are transparent*/
	ctx->flags |= CTX_IS_TRANSPARENT;

	/*BIFS has default value for 2D appearance ...*/
	ctx->aspect.fill_color = 0xFFCCCCCC;
	ctx->aspect.line_color = 0xFFCCCCCC;
	ctx->aspect.pen_props.width = FIX_ONE;
	ctx->aspect.pen_props.cap = GF_LINE_CAP_FLAT;
	ctx->aspect.pen_props.join = GF_LINE_JOIN_BEVEL;
	ctx->aspect.pen_props.miterLimit = 4*FIX_ONE;

}

void drawctx_update_info(DrawableContext *ctx, GF_VisualManager *visual)
{
	DRInfo *dri;
	Bool moved, need_redraw, drawn;
	need_redraw = (ctx->flags & CTX_REDRAW_MASK) ? 1 : 0;

	drawn = 0;
	dri = ctx->drawable->dri;
	while (dri) {
		if (dri->visual==visual) {
			if (dri->current_bounds && dri->current_bounds->clip.width) drawn = 1;
			break;
		}
		dri = dri->next;
	}
	if (drawn) {
		ctx->drawable->flags |= DRAWABLE_DRAWN_ON_VISUAL;
		/*node has been modified, do not check bounds, just assumed it moved*/
		if (ctx->drawable->flags & DRAWABLE_HAS_CHANGED) {
			moved = 1;
		} else {
			/*check if same bounds are used*/
			moved = !drawable_has_same_bounds(ctx, visual);
		}

		if (need_redraw || moved)
			ctx->flags |= CTX_REDRAW_MASK;
	}

	/*in all cases reset dirty flag of appearance and its sub-nodes*/
	//if (ctx->flags & CTX_HAS_APPEARANCE) gf_node_dirty_reset(ctx->appear);
}

#ifndef GPAC_DISABLE_VRML
static Bool drawable_lineprops_dirty(GF_Node *node)
{
	LinePropStack *st = (LinePropStack *)gf_node_get_private(node);
	if (!st) return 0;

	if (st->compositor->current_frame == st->last_mod_time) return st->is_dirty;
	if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
		gf_node_dirty_clear(node, 0);
		st->is_dirty = 1;
	} else {
		st->is_dirty = 0;
	}
	st->last_mod_time = st->compositor->current_frame;
	return st->is_dirty;
}

u32 drawable_get_aspect_2d_mpeg4(GF_Node *node, DrawAspect2D *asp, GF_TraverseState *tr_state)
{
	M_Material2D *m = NULL;
	M_LineProperties *LP;
	M_XLineProperties *XLP;
	GF_Node *appear = tr_state->override_appearance ? tr_state->override_appearance : tr_state->appear;
	u32 ret = 0;

	asp->pen_props.cap = GF_LINE_CAP_FLAT;
	asp->pen_props.join = GF_LINE_JOIN_MITER;
	asp->pen_props.align = GF_PATH_LINE_CENTER;
	asp->pen_props.miterLimit = 4*FIX_ONE;
	asp->line_color = 0xFFCCCCCC;
	asp->pen_props.width = 0;

	if (appear == NULL) goto check_default;

	if ( ((M_Appearance *) appear)->texture ) {
		asp->fill_texture = gf_sc_texture_get_handler( ((M_Appearance *) appear)->texture );
	}


	m = (M_Material2D *) ((M_Appearance *)appear)->material;
	if ( m == NULL) {
		asp->fill_color &= 0x00FFFFFF;
		goto check_default;
	}
	switch (gf_node_get_tag((GF_Node *) m) ) {
	case TAG_MPEG4_Material2D:
		break;
	case TAG_MPEG4_Material:
#ifndef GPAC_DISABLE_X3D
	case TAG_X3D_Material:
#endif
	{
		M_Material *mat = (M_Material *)m;
		asp->pen_props.width = 0;
		asp->fill_color = GF_COL_ARGB_FIXED(FIX_ONE, mat->diffuseColor.red, mat->diffuseColor.green, mat->diffuseColor.blue);
		if (!tr_state->color_mat.identity)
			asp->fill_color = gf_cmx_apply(&tr_state->color_mat, asp->fill_color);
	}
	return 0;
	default:
		return 0;
	}

	asp->fill_color = GF_COL_ARGB_FIXED(FIX_ONE-m->transparency, m->emissiveColor.red, m->emissiveColor.green, m->emissiveColor.blue);
	if (!tr_state->color_mat.identity)
		asp->fill_color = gf_cmx_apply(&tr_state->color_mat, asp->fill_color);

	asp->line_color = asp->fill_color;
	if (!m->filled) asp->fill_color = 0;

	if (m->lineProps == NULL) {
check_default:
		/*this is a bug in the spec: by default line width is 1.0, but in meterMetrics this means half of the screen :)*/
		asp->pen_props.width = FIX_ONE;
		if (!tr_state->pixel_metrics) asp->pen_props.width = gf_divfix(asp->pen_props.width, tr_state->min_hsize);
		if (m && m->transparency==FIX_ONE) {
			asp->pen_props.width = 0;
		} else {
			switch (gf_node_get_tag(node)) {
			case TAG_MPEG4_IndexedLineSet2D:
				asp->fill_color &= 0x00FFFFFF;
				break;
			case TAG_MPEG4_PointSet2D:
				asp->fill_color |= ((u32 )(FIX2INT(255 * (m ? (FIX_ONE - m->transparency) : FIX_ONE))) ) << 24;
				asp->pen_props.width = 0;
				break;
			default:
				if (GF_COL_A(asp->fill_color)) asp->pen_props.width = 0;
				/*spec is unclear about that*/
				//else if (!m && ctx->aspect.fill_texture) ctx->aspect.pen_props.width = 0;
				break;
			}
		}
		return 0;
	}
	LP = NULL;
	XLP = NULL;
	switch (gf_node_get_tag((GF_Node *) m->lineProps) ) {
	case TAG_MPEG4_LineProperties:
		LP = (M_LineProperties *) m->lineProps;
		break;
	case TAG_MPEG4_XLineProperties:
		XLP = (M_XLineProperties *) m->lineProps;
		break;
	default:
		asp->pen_props.width = 0;
		return 0;
	}
	if (m->lineProps && drawable_lineprops_dirty(m->lineProps))
		ret = CTX_APP_DIRTY;

	if (LP) {
		asp->pen_props.dash = (u8) LP->lineStyle;
		asp->line_color = GF_COL_ARGB_FIXED(FIX_ONE-m->transparency, LP->lineColor.red, LP->lineColor.green, LP->lineColor.blue);
		asp->pen_props.width = LP->width;
		if (!tr_state->color_mat.identity) {
			asp->line_color = gf_cmx_apply(&tr_state->color_mat, asp->line_color);
		}
		return ret;
	}

	asp->pen_props.dash = (u8) XLP->lineStyle;
	asp->line_color = GF_COL_ARGB_FIXED(FIX_ONE-XLP->transparency, XLP->lineColor.red, XLP->lineColor.green, XLP->lineColor.blue);
	asp->pen_props.width = XLP->width;
	if (!tr_state->color_mat.identity) {
		asp->line_color = gf_cmx_apply(&tr_state->color_mat, asp->line_color);
	}

	asp->line_scale = XLP->isScalable ? FIX_ONE : 0;
	asp->pen_props.align = XLP->isCenterAligned ? GF_PATH_LINE_CENTER : GF_PATH_LINE_INSIDE;
	asp->pen_props.cap = (u8) XLP->lineCap;
	asp->pen_props.join = (u8) XLP->lineJoin;
	asp->pen_props.miterLimit = XLP->miterLimit;
	asp->pen_props.dash_offset = XLP->dashOffset;

	/*dash settings strutc is the same as MFFloat from XLP, typecast without storing*/
	if (XLP->dashes.count) {
		asp->pen_props.dash_set = (GF_DashSettings *) &XLP->dashes;
	} else {
		asp->pen_props.dash_set = NULL;
	}
	asp->line_texture = gf_sc_texture_get_handler(XLP->texture);
	return ret;
}
#endif /*GPAC_DISABLE_VRML*/


static Bool check_transparent_skip(DrawableContext *ctx, Bool skipFill)
{
	/*if texture, cannot skip*/
	if (ctx->aspect.fill_texture) return 0;
	if (! GF_COL_A(ctx->aspect.fill_color) && !GF_COL_A(ctx->aspect.line_color) ) return 1;
	if (ctx->aspect.pen_props.width == 0) {
		if (skipFill) return 1;
		if (!GF_COL_A(ctx->aspect.fill_color) ) return 1;
	}
	return 0;
}


void drawable_check_texture_dirty(DrawableContext *ctx, Drawable *drawable, GF_TraverseState *tr_state)
{
#ifndef GPAC_DISABLE_3D
	Bool texture_ready=0;
#endif

	/*Update texture info - draw even if texture not created (this may happen if the media is removed)*/
	if (ctx->aspect.fill_texture) {
		if (ctx->aspect.fill_texture->needs_refresh) {
			ctx->flags |= CTX_TEXTURE_DIRTY;
		}
#ifndef GPAC_DISABLE_3D
		//in autoGL mode, a texture is dirty only if transparent. If not, the area covered over the video doesn't need to be repainted if unchanged
		//disable for color matrix as we don't yet handle them in GL
		if (tr_state->visual->compositor->hybrid_opengl && !tr_state->visual->offscreen) {
			u8 alpha = GF_COL_A(ctx->aspect.fill_color);
			if (!alpha) alpha = GF_COL_A(ctx->aspect.line_color);

			if (!ctx->aspect.fill_texture->transparent && (alpha==0xFF) && !ctx->aspect.fill_texture->compute_gradient_matrix && (drawable->flags & DRAWABLE_HYBGL_INIT)) {
				ctx->flags |= CTX_HYBOGL_NO_CLEAR;
			}
			//otherwise, we need to redraw all object below, whether they changed ot not, because we have erased this part of the canvas
			else {
				ctx->flags |= CTX_TEXTURE_DIRTY;
			}
			//wait until we have something to draw to decide that the texture is ready, otherwise we will not clear the canvas when texture is ready
			if (ctx->aspect.fill_texture->compute_gradient_matrix || ctx->aspect.fill_texture->data)
				texture_ready=1;
		}
#endif
	}
	//same as above
	if (ctx->aspect.line_texture) {
		if (ctx->aspect.line_texture->needs_refresh)
			ctx->flags |= CTX_TEXTURE_DIRTY;

#ifndef GPAC_DISABLE_3D
		//in autoGL mode, a texture is dirty only if transparent. If not, the area covered over the video doesn't need to be repainted if unchanged
		//disable for color matrix as we don't yet handle them in GL
		if (tr_state->visual->compositor->hybrid_opengl && !tr_state->visual->offscreen) {
			u8 alpha = GF_COL_A(ctx->aspect.line_color);
			if (!ctx->aspect.line_texture->transparent && (alpha==0xFF) && !ctx->aspect.line_texture->compute_gradient_matrix && (drawable->flags & DRAWABLE_HYBGL_INIT))
				ctx->flags |= CTX_HYBOGL_NO_CLEAR;
			//otherwise, we need to redraw all object below, whether they changed ot not, bacause we have erased this part of the canvas
			else
				ctx->flags |= CTX_TEXTURE_DIRTY;

			//wait until we have something to draw to decide that the texture is ready, otherwise we will not clear the canvas when texture is ready
			if (ctx->aspect.line_texture->compute_gradient_matrix || ctx->aspect.line_texture->data)
				texture_ready=1;
		}
#endif
	}

#ifndef GPAC_DISABLE_3D
	//from now on, we won't clear the canvas when updating this texture (unless transparent, cf above)
	if (texture_ready)
		drawable->flags |= DRAWABLE_HYBGL_INIT;
#endif
}

#ifndef GPAC_DISABLE_VRML

DrawableContext *drawable_init_context_mpeg4(Drawable *drawable, GF_TraverseState *tr_state)
{
	DrawableContext *ctx;
	Bool skipFill;
	GF_Node *appear;
	gf_assert(tr_state->visual);

	/*switched-off geometry nodes are not drawn*/
	if (tr_state->switched_off) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Drawable is switched off - skipping\n"));
		return NULL;
	}

	//Get a empty context from the current visual
	ctx = visual_2d_get_drawable_context(tr_state->visual);
	if (!ctx) return NULL;

	ctx->drawable = drawable;

	appear = tr_state->override_appearance ? tr_state->override_appearance : tr_state->appear;

	/*usually set by colorTransform or changes in OrderedGroup*/
	if (tr_state->invalidate_all)
		ctx->flags |= CTX_APP_DIRTY;

	ctx->aspect.fill_texture = NULL;
	if (appear) {
		ctx->appear = appear;
		if (gf_node_dirty_get(appear))
			ctx->flags |= CTX_APP_DIRTY;
	}
	/*todo cliper*/

	/*FIXME - only needed for texture*/
	if (!tr_state->color_mat.identity) {
		GF_SAFEALLOC(ctx->col_mat, GF_ColorMatrix);
		if (ctx->col_mat)
			gf_cmx_copy(ctx->col_mat, &tr_state->color_mat);
	}

	/*IndexedLineSet2D and PointSet2D ignores fill flag and texturing*/
	skipFill = 0;
	ctx->aspect.fill_texture = NULL;
	switch (gf_node_get_tag(ctx->drawable->node) ) {
#ifndef GPAC_DISABLE_VRML
	case TAG_MPEG4_IndexedLineSet2D:
		skipFill = 1;
		break;
#endif
	default:
		break;
	}

	ctx->flags |= drawable_get_aspect_2d_mpeg4(drawable->node, &ctx->aspect, tr_state);

	drawable_check_texture_dirty(ctx, drawable, tr_state);

	/*not clear in the spec: what happens when a transparent node is in form/layout ?? this may
	completely break layout of children. We consider the node should be drawn*/
	if (!tr_state->parent && check_transparent_skip(ctx, skipFill)) {
		visual_2d_remove_last_context(tr_state->visual);
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Drawable is fully transparent - skipping\n"));
		return NULL;
	}
	ctx->flags |= CTX_HAS_APPEARANCE;

	/*we are drawing on a fliped coord surface, remember to flip the texture*/
	if (tr_state->fliped_coords)
		ctx->flags |= CTX_FLIPED_COORDS;

#ifdef GF_SR_USE_DEPTH
	ctx->depth_gain=tr_state->depth_gain;
	ctx->depth_offset=tr_state->depth_offset;
#endif

	return ctx;
}
#endif

static Bool drawable_finalize_end(struct _drawable_context *ctx, GF_TraverseState *tr_state)
{
	/*if direct draw we can remove the context*/
	Bool res = tr_state->immediate_draw ? 1 : 0;
	/*copy final transform, once all parent layout is done*/
	gf_mx2d_copy(ctx->transform, tr_state->transform);

	/*setup clipper and register bounds & sensors*/
	gf_irect_intersect(&ctx->bi->clip, &tr_state->visual->top_clipper);
	if (!ctx->bi->clip.width || !ctx->bi->clip.height) {
		ctx->bi->clip.width = 0;
		/*remove if this is the last context*/
		if (tr_state->visual->cur_context == ctx) tr_state->visual->cur_context->drawable = NULL;

		return res;
	}
	if (tr_state->visual->compositor->clipframe)
		gf_irect_union(&tr_state->visual->frame_bounds, &ctx->bi->clip);

	/*keep track of node drawn, whether direct or indirect drawing*/
	if (!(ctx->drawable->flags & DRAWABLE_REGISTERED_WITH_VISUAL) ) {
		struct _drawable_store *it;
		GF_SAFEALLOC(it, struct _drawable_store);
		if (!it) {
			return 0;
		}
		it->drawable = ctx->drawable;
		if (tr_state->visual->last_prev_entry) {
			tr_state->visual->last_prev_entry->next = it;
			tr_state->visual->last_prev_entry = it;
		} else {
			tr_state->visual->prev_nodes = tr_state->visual->last_prev_entry = it;
		}
		//GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Registering new drawn node %s on visual\n", gf_node_get_class_name(it->drawable->node)));
		ctx->drawable->flags |= DRAWABLE_REGISTERED_WITH_VISUAL;
	}

	/*we are in direct draw mode, draw ...*/
	if (res) {
		/*if over an overlay we cannot remove the context and cannot draw directly*/
		if (visual_2d_overlaps_overlay(tr_state->visual, ctx, tr_state))
			return 0;

		gf_assert(!tr_state->traversing_mode);
		tr_state->traversing_mode = TRAVERSE_DRAW_2D;
		tr_state->ctx = ctx;

		call_drawable_draw(ctx, tr_state, GF_TRUE);

		tr_state->ctx = NULL;
		tr_state->traversing_mode = TRAVERSE_SORT;
	}
	/*if the drawable is an overlay, always mark it as dirty to avoid flickering*/
	else if (ctx->drawable->flags & DRAWABLE_IS_OVERLAY) {
		ctx->flags |= CTX_APP_DIRTY;
	}
	/*if direct draw we can remove the context*/
	return res;
}

void drawable_check_bounds(struct _drawable_context *ctx, GF_VisualManager *visual)
{
	if (!ctx->bi) {
		ctx->bi = drawable_check_alloc_bounds(ctx, visual);
		if (ctx->bi)
			ctx->bi->extra_check = ctx->appear;
	}
}

void drawable_compute_line_scale(GF_TraverseState *tr_state, DrawAspect2D *asp)
{
	GF_Rect rc;
	rc.x = rc.y = 0;
	rc.width = rc.height = FIX_ONE;
#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d)
		gf_mx_apply_rect(&tr_state->model_matrix, &rc);
	else
#endif
		gf_mx2d_apply_rect(&tr_state->transform, &rc);

	asp->line_scale = MAX(gf_divfix(tr_state->visual->compositor->scale_x, rc.width), gf_divfix(tr_state->visual->compositor->scale_y, rc.height));
}

void drawable_finalize_sort_ex(DrawableContext *ctx, GF_TraverseState *tr_state, GF_Rect *orig_bounds, Bool skip_focus)
{
	Bool can_remove = 0;
	Fixed pw;
	GF_Rect unclip, store_orig_bounds;
	GF_Node *appear = tr_state->override_appearance ? tr_state->override_appearance : tr_state->appear;

	drawable_check_bounds(ctx, tr_state->visual);

	if (orig_bounds) {
		store_orig_bounds = *orig_bounds;
	} else {
		gf_path_get_bounds(ctx->drawable->path, &store_orig_bounds);
	}
	ctx->bi->unclip = store_orig_bounds;
	gf_mx2d_apply_rect(&tr_state->transform, &ctx->bi->unclip);

	/*apply pen width*/
	if (ctx->aspect.pen_props.width) {
		StrikeInfo2D *si = NULL;

		if (!ctx->aspect.line_scale)
			drawable_compute_line_scale(tr_state, &ctx->aspect);

#if 0
		/*if pen is not scalable, apply user/viewport transform so that original aspect is kept*/
		if (!ctx->aspect.line_scale) {
			GF_Point2D pt;
			pt.x = tr_state->transform.m[0] + tr_state->transform.m[1];
			pt.y = tr_state->transform.m[3] + tr_state->transform.m[4];
			ctx->aspect.line_scale = gf_divfix(FLT2FIX(1.41421356f) , gf_v2d_len(&pt));
		}
#endif

		/*get strike info & outline for exact bounds compute. If failure use default offset*/
		si = drawable_get_strikeinfo(tr_state->visual->compositor, ctx->drawable, &ctx->aspect, appear, ctx->drawable->path, ctx->flags, NULL);
		if (si && si->outline) {
			gf_path_get_bounds(si->outline, &ctx->bi->unclip);
			gf_mx2d_apply_rect(&tr_state->transform, &ctx->bi->unclip);
		} else {
			pw = gf_mulfix(ctx->aspect.pen_props.width, ctx->aspect.line_scale);
			ctx->bi->unclip.x -= pw/2;
			ctx->bi->unclip.y += pw/2;
			ctx->bi->unclip.width += pw;
			ctx->bi->unclip.height += pw;
		}
	}

	if (ctx->bi->unclip.width && ctx->bi->unclip.height) {
		unclip = ctx->bi->unclip;
		if (! (ctx->flags & CTX_NO_ANTIALIAS)) {
			/*grow of 2 pixels (-1 and +1) to handle AA, but ONLY on cliper otherwise we will modify layout/form */
			pw = (tr_state->pixel_metrics) ? FIX_ONE : 2*FIX_ONE/tr_state->visual->width;
			unclip.x -= pw;
			unclip.y += pw;
			unclip.width += 2*pw;
			unclip.height += 2*pw;
		}
		ctx->bi->clip = gf_rect_pixelize(&unclip);
	} else {
		ctx->bi->clip.width = 0;
	}

	can_remove = drawable_finalize_end(ctx, tr_state);
	if (ctx->drawable && !skip_focus)
		drawable_check_focus_highlight(ctx->drawable->node, tr_state, &store_orig_bounds);

	/*remove if this is the last context*/
	if (can_remove && (tr_state->visual->cur_context == ctx))
		tr_state->visual->cur_context->drawable = NULL;
}

void drawable_finalize_sort(struct _drawable_context *ctx, GF_TraverseState *tr_state, GF_Rect *orig_bounds)
{
	drawable_finalize_sort_ex(ctx, tr_state, orig_bounds, 0);
}


void drawable_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, GF_Rect *orig_bounds)
{
	DrawableContext *hl_ctx;
	Drawable *hlight;
	GF_Node *prev_node;
	u32 prev_mode;
	GF_Matrix2D cur;
	GF_Compositor *compositor = tr_state->visual->compositor;

	if (compositor->disable_focus_highlight) return;

	if (compositor->focus_node!=node) return;
	/*if key navigator, don't draw a focus highlight*/
	if (compositor->keynav_node) return;

	if (compositor->focus_used) {
		u32 count = gf_list_count(tr_state->use_stack);
		if (!count || (gf_list_get(tr_state->use_stack, count-1)!=compositor->focus_used) )
			return;
	}

	hlight = compositor->focus_highlight;
	if (!hlight) return;

	/*check if focus node has changed*/
	prev_node = gf_node_get_private(hlight->node);
	if (prev_node != node) {
		GF_Rect *bounds;
		/*this is a grouping node, get its bounds*/
		if (!orig_bounds) {
			gf_mx2d_copy(cur, tr_state->transform);
			gf_mx2d_init(tr_state->transform);
			prev_mode = tr_state->traversing_mode;
			tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
			tr_state->bounds.width = tr_state->bounds.height = 0;
			tr_state->bounds.x = tr_state->bounds.y = 0;

			gf_sc_get_nodes_bounds(node, ((GF_ParentNode *)node)->children, tr_state, NULL);

			tr_state->traversing_mode = prev_mode;
			gf_mx2d_copy(tr_state->transform, cur);
			bounds = &tr_state->bounds;
		} else {
			bounds = orig_bounds;
		}
		gf_node_set_private(hlight->node, node);

		drawable_reset_path(hlight);
		gf_path_reset(hlight->path);
		gf_path_add_rect(hlight->path, bounds->x, bounds->y, bounds->width, bounds->height);
	}
	hl_ctx = visual_2d_get_drawable_context(tr_state->visual);
	hl_ctx->drawable = hlight;
	hl_ctx->aspect.fill_color = compositor->hlfill;
	hl_ctx->aspect.line_color = compositor->hlline;
	hl_ctx->aspect.line_scale = 0;
	hl_ctx->aspect.pen_props.width = compositor->hllinew;
	hl_ctx->aspect.pen_props.join = GF_LINE_JOIN_BEVEL;
	hl_ctx->aspect.pen_props.dash = GF_DASH_STYLE_DOT;

	/*editing this node - move to solid stroke*/
	if (compositor->edited_text) {
		hl_ctx->aspect.pen_props.width = 2*FIX_ONE;
		hl_ctx->aspect.pen_props.dash = 1;
		hl_ctx->aspect.line_color = compositor->hlline;
	}


#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		gf_mx2d_copy(hl_ctx->transform, tr_state->transform);
		visual_3d_draw_2d_with_aspect(hl_ctx->drawable, tr_state, &hl_ctx->aspect);
		return;
	}
#endif
	gf_mx2d_copy(hl_ctx->transform, tr_state->transform);
	drawable_finalize_sort_ex(hl_ctx, tr_state, NULL, 1);
}


void drawable_traverse_focus(GF_Node *node, void *rs, Bool is_destroy)
{
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	if (is_destroy) return;
	if (tr_state->traversing_mode == TRAVERSE_DRAW_2D)
		visual_2d_draw_path(tr_state->visual, tr_state->ctx->drawable->path, tr_state->ctx, NULL, NULL, tr_state);
}


void delete_strikeinfo2d(StrikeInfo2D *info)
{
	if (info->outline) gf_path_del(info->outline);
#ifndef GPAC_DISABLE_3D
	if (info->mesh_outline) mesh_free(info->mesh_outline);
#endif
	gf_free(info);
}


StrikeInfo2D *drawable_get_strikeinfo(GF_Compositor *compositor, Drawable *drawable, DrawAspect2D *asp, GF_Node *appear, GF_Path *path, u32 svg_flags, GF_TraverseState *tr_state)
{
	StrikeInfo2D *si, *prev;
	GF_Node *lp;
#ifndef GPAC_DISABLE_VRML
	Bool dirty;
#endif
	if (!asp->pen_props.width) return NULL;
	if (path && !path->n_points) return NULL;

	lp = NULL;
#ifndef GPAC_DISABLE_VRML
	if (appear && (gf_node_get_tag(appear) < GF_NODE_RANGE_LAST_X3D) ) {
		lp = ((M_Appearance *)appear)->material;
		if (lp) lp = ((M_Material2D *) lp)->lineProps;
	}
#endif

	prev = NULL;
	si = drawable->outline;
	while (si) {
		/*note this includes default LP (NULL)*/
		if ((si->lineProps == lp) && (!path || (path==si->original)) ) break;
		if (!si->lineProps) {
			gf_list_del_item(compositor->strike_bank, si);
			if (si->outline) gf_path_del(si->outline);
#ifndef GPAC_DISABLE_3D
			if (si->mesh_outline) mesh_free(si->mesh_outline);
#endif
			if (prev) prev->next = si->next;
			else drawable->outline = si->next;
			gf_free(si);
			si = prev ? prev->next : drawable->outline;
			continue;
		}
		prev = si;
		si = si->next;
	}
	/*not found, add*/
	if (!si) {
		GF_SAFEALLOC(si, StrikeInfo2D);
		if (!si) {
			return NULL;
		}
		si->lineProps = lp;
		si->drawable = drawable;

		if (drawable->outline) {
			prev = drawable->outline;
			while (prev->next) prev = prev->next;
			prev->next = si;
		} else {
			drawable->outline = si;
		}
		gf_list_add(compositor->strike_bank, si);
	}

	/*3D drawing of outlines*/
#ifndef GPAC_DISABLE_3D
	if (tr_state && !asp->line_scale) {
		drawable_compute_line_scale(tr_state, asp);
	}
#endif

	/*picking*/
	if (!asp->line_scale) return si;

	/*node changed or outline not build*/
#ifndef GPAC_DISABLE_VRML
	dirty = lp ? drawable_lineprops_dirty(lp) : 0;
#endif

	if (!si->outline
#ifndef GPAC_DISABLE_VRML
		|| dirty
#endif
		|| (si->line_scale != asp->line_scale) || (si->path_length != asp->pen_props.path_length) || (svg_flags & CTX_SVG_OUTLINE_GEOMETRY_DIRTY)) {
		u32 i;
		Fixed w = asp->pen_props.width;
		Fixed dash_o = asp->pen_props.dash_offset;
		si->line_scale = asp->line_scale;
		if (si->outline) gf_path_del(si->outline);
#ifndef GPAC_DISABLE_3D
		if (si->mesh_outline) {
			mesh_free(si->mesh_outline);
			si->mesh_outline = NULL;
		}
#endif
		/*apply scale whether scalable or not (if not scalable, scale is still needed for scalable zoom)*/
		asp->pen_props.width = gf_mulfix(asp->pen_props.width, asp->line_scale);
		if (asp->pen_props.dash != GF_DASH_STYLE_SVG)
			asp->pen_props.dash_offset = gf_mulfix(asp->pen_props.dash_offset, asp->pen_props.width);

		if (asp->pen_props.dash_set) {
			for(i=0; i<asp->pen_props.dash_set->num_dash; i++) {
				asp->pen_props.dash_set->dashes[i] = gf_mulfix(asp->pen_props.dash_set->dashes[i], asp->line_scale);
			}
		}

		if (path) {
			si->outline = gf_path_get_outline(path, asp->pen_props);
			si->original = path;
		} else {
			si->outline = gf_path_get_outline(drawable->path, asp->pen_props);
		}
		/*restore*/
		asp->pen_props.width = w;
		asp->pen_props.dash_offset = dash_o;
		if (asp->pen_props.dash_set) {
			for(i=0; i<asp->pen_props.dash_set->num_dash; i++) {
				asp->pen_props.dash_set->dashes[i] = gf_divfix(asp->pen_props.dash_set->dashes[i], asp->line_scale);
			}
		}
	}

	return si;
}

void drawable_reset_path_outline(Drawable *st)
{
	StrikeInfo2D *si = st->outline;
	while (si) {
		if (si->outline) gf_path_del(si->outline);
		si->outline = NULL;
#ifndef GPAC_DISABLE_3D
		if (si->mesh_outline) mesh_free(si->mesh_outline);
		si->mesh_outline = NULL;
#endif
		si->original = NULL;
		si = si->next;
	}
#ifndef GPAC_DISABLE_3D
	if (st->mesh) {
		mesh_free(st->mesh);
		st->mesh = NULL;
	}
#endif
}

void drawable_reset_path(Drawable *st)
{
	drawable_reset_path_outline(st);
	if (st->path) gf_path_reset(st->path);
#ifndef GPAC_DISABLE_3D
	if (st->mesh) {
		mesh_free(st->mesh);
		st->mesh = NULL;
	}
#endif
}

#ifndef GPAC_DISABLE_VRML

static void DestroyLineProps(GF_Node *n, void *rs, Bool is_destroy)
{
	StrikeInfo2D *si, *cur, *prev;
	u32 i;
	LinePropStack *st;
	if (!is_destroy) return;

	st = (LinePropStack *)gf_node_get_private(n);
	i = 0;

	while ((si = (StrikeInfo2D*)gf_list_enum(st->compositor->strike_bank, &i))) {
		if (si->lineProps == n) {
			/*remove from node*/
			if (si->drawable) {
				gf_assert(si->drawable->outline);
				cur = si->drawable->outline;
				prev = NULL;
				while (cur) {
					if (cur!=si) {
						prev = cur;
						cur = cur->next;
						continue;
					}
					if (prev) prev->next = cur->next;
					else si->drawable->outline = cur->next;
					break;
				}
			}
			i--;
			gf_list_rem(st->compositor->strike_bank, i);
			delete_strikeinfo2d(si);
		}
	}

	gf_free(st);

}

void compositor_init_lineprops(GF_Compositor *compositor, GF_Node *node)
{
	LinePropStack *st;
	GF_SAFEALLOC(st, LinePropStack);
	if (!st) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate line properties stack\n"));
		return;
	}
	st->compositor = compositor;
	st->last_mod_time = 0;
	gf_node_set_private(node, st);
	gf_node_set_callback_function(node, DestroyLineProps);
}

#endif /*GPAC_DISABLE_VRML*/


#ifdef GPAC_DISABLE_SVG

Bool drawable_get_aspect_2d_svg(GF_Node *node, DrawAspect2D *asp, GF_TraverseState *tr_state)
{
	return 0;
}

#else

Bool drawable_get_aspect_2d_svg(GF_Node *node, DrawAspect2D *asp, GF_TraverseState *tr_state)
{
	Bool ret = 0;
	SVGPropertiesPointers *props = tr_state->svg_props;
	Fixed clamped_opacity = FIX_ONE;
	Fixed clamped_solid_opacity = FIX_ONE;
	Fixed clamped_fill_opacity = (props->fill_opacity->value < 0 ? 0 : (props->fill_opacity->value > FIX_ONE ? FIX_ONE : props->fill_opacity->value));
	Fixed clamped_stroke_opacity = (props->stroke_opacity->value < 0 ? 0 : (props->stroke_opacity->value > FIX_ONE ? FIX_ONE : props->stroke_opacity->value));

	if (props->opacity) {
		clamped_opacity = (props->opacity->value < 0 ? 0 : (props->opacity->value > FIX_ONE ? FIX_ONE : props->opacity->value));
		if (clamped_opacity!=FIX_ONE) {
			clamped_fill_opacity = gf_mulfix(clamped_fill_opacity, clamped_opacity);
			clamped_stroke_opacity = gf_mulfix(clamped_stroke_opacity, clamped_opacity);
		}
	}
	asp->fill_color = 0;

	if (props->fill->type==SVG_PAINT_URI) {
		if (props->fill->iri.type != XMLRI_ELEMENTID) {
			/* trying to resolve the IRI to the Paint Server */
			XMLRI *iri = &props->fill->iri;
			GF_SceneGraph *sg = gf_node_get_graph(node);
			GF_Node *n = gf_sg_find_node_by_name(sg, &(iri->string[1]));
			if (n) {
				iri->type = XMLRI_ELEMENTID;
				iri->target = n;
				gf_node_register_iri(sg, iri);
				gf_free(iri->string);
				iri->string = NULL;
			}
		}
		/* If paint server not found, paint is equivalent to none */
		if (props->fill->iri.type == XMLRI_ELEMENTID) {
			asp->fill_color = GF_COL_ARGB_FIXED(clamped_opacity, 0, 0, 0);
			switch (gf_node_get_tag((GF_Node *)props->fill->iri.target)) {
			case TAG_SVG_solidColor:
			{
				SVGAllAttributes all_atts;
				gf_svg_flatten_attributes((SVG_Element*)props->fill->iri.target, &all_atts);

				gf_node_traverse(props->fill->iri.target, tr_state);

				ret += compositor_svg_solid_color_dirty(tr_state->visual->compositor, props->fill->iri.target);

				if (all_atts.solid_color) {
					if (all_atts.solid_opacity) {
						Fixed val = all_atts.solid_opacity->value;
						clamped_solid_opacity = MIN(FIX_ONE, MAX(0, val) );
						clamped_solid_opacity = gf_mulfix(clamped_solid_opacity, clamped_opacity);
					}
					asp->fill_color = GF_COL_ARGB_FIXED(clamped_solid_opacity, all_atts.solid_color->color.red, all_atts.solid_color->color.green, all_atts.solid_color->color.blue);
				}
			}
			break;
			case TAG_SVG_linearGradient:
			case TAG_SVG_radialGradient:
				asp->fill_texture = gf_sc_texture_get_handler((GF_Node *)props->fill->iri.target);
				break;
			/*FIXME*/
			default:
				break;
			}
		}
	} else if (props->fill->type == SVG_PAINT_COLOR) {
		if (props->fill->color.type == SVG_COLOR_CURRENTCOLOR) {
			asp->fill_color = GF_COL_ARGB_FIXED(clamped_fill_opacity, props->color->color.red, props->color->color.green, props->color->color.blue);
		} else if (props->fill->color.type == SVG_COLOR_RGBCOLOR) {
			asp->fill_color = GF_COL_ARGB_FIXED(clamped_fill_opacity, props->fill->color.red, props->fill->color.green, props->fill->color.blue);
		} else if (props->fill->color.type >= SVG_COLOR_ACTIVE_BORDER) {
			asp->fill_color = tr_state->visual->compositor->sys_colors[props->fill->color.type - 3];
			asp->fill_color |= ((u32) (clamped_fill_opacity*255) ) << 24;
		}
	}
	if (!tr_state->color_mat.identity)
		asp->fill_color = gf_cmx_apply(&tr_state->color_mat, asp->fill_color);

	asp->line_color = 0;
	asp->pen_props.width = (props->stroke->type != SVG_PAINT_NONE) ? props->stroke_width->value : 0;
	if (props->stroke->type==SVG_PAINT_URI) {
		if (props->stroke->iri.type != XMLRI_ELEMENTID) {
			/* trying to resolve the IRI to the Paint Server */
			XMLRI *iri = &props->stroke->iri;
			GF_SceneGraph *sg = gf_node_get_graph(node);
			GF_Node *n = gf_sg_find_node_by_name(sg, &(iri->string[1]));
			if (n) {
				iri->type = XMLRI_ELEMENTID;
				iri->target = n;
				gf_node_register_iri(sg, iri);
				gf_free(iri->string);
				iri->string = NULL;
			}
		}
		/* Paint server not found, stroke is equivalent to none */
		if ((props->stroke->iri.type == XMLRI_ELEMENTID) && props->stroke->iri.target) {
			switch (gf_node_get_tag((GF_Node *)props->stroke->iri.target)) {
			case TAG_SVG_solidColor:
			{
				SVGAllAttributes all_atts;
				gf_svg_flatten_attributes((SVG_Element*)props->stroke->iri.target, &all_atts);

				gf_node_traverse(props->stroke->iri.target, tr_state);

				ret += compositor_svg_solid_color_dirty(tr_state->visual->compositor, props->stroke->iri.target);

				if (all_atts.solid_color) {
					if (all_atts.solid_opacity) {
						Fixed val = all_atts.solid_opacity->value;
						clamped_solid_opacity = MIN(FIX_ONE, MAX(0, val) );
					}
					asp->line_color = GF_COL_ARGB_FIXED(clamped_solid_opacity, all_atts.solid_color->color.red, all_atts.solid_color->color.green, all_atts.solid_color->color.blue);
				}
			}
			break;
			case TAG_SVG_linearGradient:
			case TAG_SVG_radialGradient:
				asp->line_texture = gf_sc_texture_get_handler((GF_Node *)props->stroke->iri.target);
				break;
			default:
				break;
			}
		}
	} else if (props->stroke->type == SVG_PAINT_COLOR) {
		if (props->stroke->color.type == SVG_COLOR_CURRENTCOLOR) {
			asp->line_color = GF_COL_ARGB_FIXED(clamped_stroke_opacity, props->color->color.red, props->color->color.green, props->color->color.blue);
		} else if (props->stroke->color.type == SVG_COLOR_RGBCOLOR) {
			asp->line_color = GF_COL_ARGB_FIXED(clamped_stroke_opacity, props->stroke->color.red, props->stroke->color.green, props->stroke->color.blue);
		} else if (props->stroke->color.type >= SVG_COLOR_ACTIVE_BORDER) {
			asp->line_color = tr_state->visual->compositor->sys_colors[SVG_COLOR_ACTIVE_BORDER - 3];
			asp->line_color |= ((u32) (clamped_stroke_opacity*255)) << 24;
		}
	}
	if (!tr_state->color_mat.identity)
		asp->line_color = gf_cmx_apply(&tr_state->color_mat, asp->line_color);

	if (props->stroke_dasharray->type != SVG_STROKEDASHARRAY_NONE) {
		asp->pen_props.dash = GF_DASH_STYLE_SVG;
		asp->pen_props.dash_offset = props->stroke_dashoffset->value;
		asp->pen_props.dash_set = (GF_DashSettings *) &(props->stroke_dasharray->array);
	}
	asp->line_scale = (props->vector_effect && (*props->vector_effect == SVG_VECTOREFFECT_NONSCALINGSTROKE)) ? 0 : FIX_ONE;

	asp->pen_props.cap = (u8) *props->stroke_linecap;
	asp->pen_props.join = (u8) *props->stroke_linejoin;
	asp->pen_props.miterLimit = props->stroke_miterlimit->value;

	if (!tr_state->color_mat.identity) {
		asp->fill_color = gf_cmx_apply(&tr_state->color_mat, asp->fill_color);
		asp->line_color = gf_cmx_apply(&tr_state->color_mat, asp->line_color);
	}
	return ret;
}

static Bool svg_appearance_flag_dirty(u32 flags)
{
#if 1
	/* fill-related */
	if (flags & GF_SG_SVG_FILL_DIRTY)				return 1;
	if (flags & GF_SG_SVG_FILLOPACITY_DIRTY)		return 1;
	if (flags & GF_SG_SVG_FILLRULE_DIRTY)			return 1;

	/* stroke-related */
	if (flags & GF_SG_SVG_STROKE_DIRTY)				return 1;
	if (flags & GF_SG_SVG_STROKEDASHARRAY_DIRTY)	return 1;
	if (flags & GF_SG_SVG_STROKEDASHOFFSET_DIRTY)	return 1;
	if (flags & GF_SG_SVG_STROKELINECAP_DIRTY)		return 1;
	if (flags & GF_SG_SVG_STROKELINEJOIN_DIRTY)		return 1;
	if (flags & GF_SG_SVG_STROKEMITERLIMIT_DIRTY)	return 1;
	if (flags & GF_SG_SVG_STROKEOPACITY_DIRTY)		return 1;
	if (flags & GF_SG_SVG_STROKEWIDTH_DIRTY)		return 1;
	if (flags & GF_SG_SVG_VECTOREFFECT_DIRTY)		return 1;

	/* gradients stops and solidcolor do not affect appearance directly */
	return 0;
#else
	if (flags &
	        (GF_SG_SVG_FILL_DIRTY | GF_SG_SVG_FILLOPACITY_DIRTY | GF_SG_SVG_FILLRULE_DIRTY
	         | GF_SG_SVG_STROKE_DIRTY | GF_SG_SVG_STROKEDASHARRAY_DIRTY
	         | GF_SG_SVG_STROKEDASHOFFSET_DIRTY | GF_SG_SVG_STROKELINECAP_DIRTY
	         | GF_SG_SVG_STROKELINEJOIN_DIRTY | GF_SG_SVG_STROKEMITERLIMIT_DIRTY
	         | GF_SG_SVG_STROKEOPACITY_DIRTY | GF_SG_SVG_STROKEWIDTH_DIRTY
	         | GF_SG_SVG_VECTOREFFECT_DIRTY) )
		return 1;
	return 0;
#endif
}

DrawableContext *drawable_init_context_svg(Drawable *drawable, GF_TraverseState *tr_state, SVG_ClipPath *clip_path)
{
	DrawableContext *ctx;
	gf_assert(tr_state->visual);

	if (clip_path && !clip_path->target.target) {
		clip_path->target.target = clip_path->target.string ? gf_sg_find_node_by_name(tr_state->visual->compositor->scene, clip_path->target.string+1) : NULL;
		if (!clip_path->target.target) clip_path = NULL;
	}

#ifndef GPAC_DISABLE_VRML
	/*setup SVG based on override appearance node */
	if (tr_state->override_appearance) {
		ctx = drawable_init_context_mpeg4(drawable, tr_state);
		if (ctx && clip_path) ctx->cliper = (GF_Node*)clip_path->target.target;
		return ctx;
	}
#endif

	/*switched-off geometry nodes are not drawn*/
	if (tr_state->switched_off) return NULL;

	//Get a empty context from the current visual
	ctx = visual_2d_get_drawable_context(tr_state->visual);
	if (!ctx) return NULL;

	gf_mx2d_copy(ctx->transform, tr_state->transform);

	ctx->drawable = drawable;
	if (clip_path) ctx->cliper = (GF_Node*)clip_path->target.target;

	if (tr_state->invalidate_all || svg_appearance_flag_dirty(tr_state->svg_flags)) {
		ctx->flags |= CTX_APP_DIRTY;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("Node %s dirty - invalidating\n", gf_node_get_log_name(drawable->node) ));
	}
	if (tr_state->svg_flags & (GF_SG_SVG_STROKEDASHARRAY_DIRTY |
	                           GF_SG_SVG_STROKEDASHOFFSET_DIRTY |
	                           GF_SG_SVG_STROKELINECAP_DIRTY |
	                           GF_SG_SVG_STROKELINEJOIN_DIRTY |
	                           GF_SG_SVG_STROKEMITERLIMIT_DIRTY |
	                           GF_SG_SVG_STROKEWIDTH_DIRTY |
	                           GF_SG_SVG_VECTOREFFECT_DIRTY ))
		ctx->flags |= CTX_SVG_OUTLINE_GEOMETRY_DIRTY;

	ctx->aspect.fill_texture = NULL;

	/*FIXME - only needed for texture*/
	if (!tr_state->color_mat.identity) {
		GF_SAFEALLOC(ctx->col_mat, GF_ColorMatrix);
		if (ctx->col_mat)
			gf_cmx_copy(ctx->col_mat, &tr_state->color_mat);
	}

	switch (gf_node_get_tag(ctx->drawable->node) ) {
	case TAG_SVG_image:
	case TAG_SVG_video:
		ctx->aspect.fill_texture = gf_sc_texture_get_handler(ctx->drawable->node);
		break;
	case TAG_SVG_line:
	case TAG_SVG_polyline:
		break;
	default:
		break;
	}

	if (drawable_get_aspect_2d_svg(drawable->node, &ctx->aspect, tr_state))
		ctx->flags |= CTX_APP_DIRTY;

	if (ctx->drawable->path) {
		if (*tr_state->svg_props->fill_rule == SVG_FILLRULE_NONZERO) {
			ctx->drawable->path->flags |= GF_PATH_FILL_ZERO_NONZERO;
		} else {
			ctx->drawable->path->flags &= ~GF_PATH_FILL_ZERO_NONZERO;
		}
	}

	drawable_check_texture_dirty(ctx, drawable, tr_state);

	/*we are drawing on a centered coord surface, remember to flip the texture*/
	if (tr_state->fliped_coords)
		ctx->flags |= CTX_FLIPED_COORDS;


#ifdef GF_SR_USE_DEPTH
	ctx->depth_gain=tr_state->depth_gain;
	ctx->depth_offset=tr_state->depth_offset;
#endif

	return ctx;
}



#endif	//SVG


#endif // GPAC_DISABLE_COMPOSITOR
