| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Cyril Concolato - Jean le Feuvre | |
| * Copyright (c) Telecom ParisTech 2005-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 "visual_manager.h" | |
| #if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_COMPOSITOR) | |
| #include "nodes_stacks.h" | |
| #include "texturing.h" | |
| enum | |
| { | |
| GF_SR_TEXTURE_GRAD_REGISTERED = 1<<6, | |
| GF_SR_TEXTURE_GRAD_NO_RGB = 1<<7, | |
| }; | |
| typedef struct | |
| { | |
| GF_TextureHandler txh; | |
| Bool linear; | |
| Bool animated; | |
| Fixed *keys; | |
| u32 *cols; | |
| u32 current_frame; | |
| } SVG_GradientStack; | |
| static void SVG_DestroyPaintServer(GF_Node *node) | |
| { | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(node); | |
| if (st) { | |
| if (st->cols) gf_free(st->cols); | |
| if (st->keys) gf_free(st->keys); | |
| gf_sc_texture_destroy(&st->txh); | |
| gf_free(st); | |
| } | |
| } | |
| static GF_Node *svg_copy_gradient_attributes_from(GF_Node *node, SVGAllAttributes *all_atts) | |
| { | |
| GF_Node *href_node; | |
| SVGAllAttributes all_href_atts; | |
| GF_FieldInfo info; | |
| /*check gradient redirection ...*/ | |
| href_node = node; | |
| while (href_node && gf_node_get_attribute_by_tag(href_node, TAG_XLINK_ATT_href, GF_FALSE, GF_FALSE, &info) == GF_OK) { | |
| XMLRI*iri = (XMLRI*)info.far_ptr; | |
| if (iri->type != XMLRI_ELEMENTID) { | |
| 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; | |
| } else { | |
| break; | |
| } | |
| } | |
| href_node = (GF_Node*)((XMLRI*)info.far_ptr)->target; | |
| if (href_node == node) href_node = NULL; | |
| } | |
| if (href_node == node) href_node = NULL; | |
| if (href_node) { | |
| gf_svg_flatten_attributes((SVG_Element *)href_node, &all_href_atts); | |
| if (!all_atts->gradientUnits) all_atts->gradientUnits = all_href_atts.gradientUnits; | |
| if (!all_atts->gradientTransform) all_atts->gradientTransform = all_href_atts.gradientTransform; | |
| if (!all_atts->cx) all_atts->cx = all_href_atts.cx; | |
| if (!all_atts->cy) all_atts->cy = all_href_atts.cy; | |
| if (!all_atts->r) all_atts->r = all_href_atts.r; | |
| if (!all_atts->fx) all_atts->fx = all_href_atts.fx; | |
| if (!all_atts->fy) all_atts->fy = all_href_atts.fy; | |
| if (!all_atts->spreadMethod) all_atts->spreadMethod = all_href_atts.spreadMethod; | |
| if (!all_atts->x1) all_atts->x1 = all_href_atts.x1; | |
| if (!all_atts->x2) all_atts->x2 = all_href_atts.x2; | |
| if (!all_atts->y1) all_atts->y1 = all_href_atts.y1; | |
| if (!all_atts->y2) all_atts->y2 = all_href_atts.y2; | |
| } | |
| return href_node; | |
| } | |
| static void svg_gradient_traverse(GF_Node *node, GF_TraverseState *tr_state, Bool real_traverse) | |
| { | |
| u32 count, nb_col; | |
| Bool is_dirty, all_dirty; | |
| Fixed alpha, max_offset; | |
| SVGAllAttributes all_atts; | |
| SVGPropertiesPointers backup_props_1; | |
| u32 backup_flags_1; | |
| GF_Node *href_node; | |
| GF_ChildNodeItem *children; | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(node); | |
| gf_svg_flatten_attributes((SVG_Element *)node, &all_atts); | |
| href_node = svg_copy_gradient_attributes_from(node, &all_atts); | |
| compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props_1, &backup_flags_1); | |
| if (real_traverse && | |
| ! (tr_state->svg_flags & (GF_SG_SVG_STOPCOLOR_OR_OPACITY_DIRTY|GF_SG_SVG_COLOR_DIRTY)) | |
| && !gf_node_dirty_get(node) | |
| && !st->txh.needs_refresh) | |
| { | |
| memcpy(tr_state->svg_props, &backup_props_1, sizeof(SVGPropertiesPointers)); | |
| tr_state->svg_flags = backup_flags_1; | |
| return; | |
| } | |
| /*for gradients we must traverse the gradient stops to trigger animations, even if the | |
| gradient is not marked as dirty*/ | |
| all_dirty = tr_state->svg_flags & (GF_SG_SVG_STOPCOLOR_OR_OPACITY_DIRTY|GF_SG_SVG_COLOR_DIRTY); | |
| is_dirty = GF_FALSE; | |
| if (gf_node_dirty_get(node)) { | |
| is_dirty = all_dirty = GF_TRUE; | |
| gf_node_dirty_clear(node, 0); | |
| if (st->cols) gf_free(st->cols); | |
| st->cols = NULL; | |
| if (st->keys) gf_free(st->keys); | |
| st->keys = NULL; | |
| st->animated = gf_node_animation_count(node) ? GF_TRUE : GF_FALSE; | |
| } | |
| children = ((SVG_Element *)node)->children; | |
| if (!children && href_node) { | |
| children = ((SVG_Element *)href_node)->children; | |
| } | |
| if (!st->cols) { | |
| count = gf_node_list_get_count(children); | |
| st->cols = (u32*)gf_malloc(sizeof(u32)*count); | |
| st->keys = (Fixed*)gf_malloc(sizeof(Fixed)*count); | |
| } | |
| nb_col = 0; | |
| max_offset = 0; | |
| while (children) { | |
| SVGPropertiesPointers backup_props_2; | |
| u32 backup_flags_2; | |
| Fixed key; | |
| GF_Node *stop = children->node; | |
| children = children->next; | |
| if (gf_node_get_tag((GF_Node *)stop) != TAG_SVG_stop) continue; | |
| gf_svg_flatten_attributes((SVG_Element*)stop, &all_atts); | |
| compositor_svg_traverse_base(stop, &all_atts, tr_state, &backup_props_2, &backup_flags_2); | |
| if (gf_node_animation_count(stop)) | |
| st->animated = GF_TRUE; | |
| if (all_dirty || gf_node_dirty_get(stop)) { | |
| is_dirty = GF_TRUE; | |
| gf_node_dirty_clear(stop, 0); | |
| alpha = FIX_ONE; | |
| if (tr_state->svg_props->stop_opacity && (tr_state->svg_props->stop_opacity->type==SVG_NUMBER_VALUE) ) | |
| alpha = tr_state->svg_props->stop_opacity->value; | |
| if (tr_state->svg_props->stop_color) { | |
| if (tr_state->svg_props->stop_color->color.type == SVG_COLOR_CURRENTCOLOR) { | |
| st->cols[nb_col] = GF_COL_ARGB_FIXED(alpha, tr_state->svg_props->color->color.red, tr_state->svg_props->color->color.green, tr_state->svg_props->color->color.blue); | |
| } else { | |
| st->cols[nb_col] = GF_COL_ARGB_FIXED(alpha, tr_state->svg_props->stop_color->color.red, tr_state->svg_props->stop_color->color.green, tr_state->svg_props->stop_color->color.blue); | |
| } | |
| } else { | |
| st->cols[nb_col] = GF_COL_ARGB_FIXED(alpha, 0, 0, 0); | |
| } | |
| if (all_atts.offset) { | |
| key = all_atts.offset->value; | |
| if (all_atts.offset->type==SVG_NUMBER_PERCENTAGE) key/=100; | |
| } else { | |
| key=0; | |
| } | |
| if (key>max_offset) max_offset=key; | |
| else key = max_offset; | |
| st->keys[nb_col] = key; | |
| } else { | |
| if (st->keys[nb_col]>max_offset) max_offset = st->keys[nb_col]; | |
| } | |
| nb_col++; | |
| memcpy(tr_state->svg_props, &backup_props_2, sizeof(SVGPropertiesPointers)); | |
| tr_state->svg_flags = backup_flags_2; | |
| } | |
| if (is_dirty) { | |
| u32 i; | |
| GF_EVGStencil * stencil; | |
| if (!st->txh.tx_io) gf_sc_texture_allocate(&st->txh); | |
| stencil = gf_sc_texture_get_stencil(&st->txh); | |
| if (!stencil) stencil = gf_evg_stencil_new(st->linear ? GF_STENCIL_LINEAR_GRADIENT : GF_STENCIL_RADIAL_GRADIENT); | |
| /*set stencil even if assigned, this invalidates the associated bitmap state in 3D*/ | |
| gf_sc_texture_set_stencil(&st->txh, stencil); | |
| st->txh.transparent = GF_FALSE; | |
| for (i=0; i<nb_col; i++) { | |
| if (GF_COL_A(st->cols[i]) != 0xFF) { | |
| st->txh.transparent = GF_TRUE; | |
| break; | |
| } | |
| } | |
| gf_evg_stencil_set_gradient_interpolation(stencil, st->keys, st->cols, nb_col); | |
| gf_evg_stencil_set_gradient_mode(stencil, /*lg->spreadMethod*/ GF_GRADIENT_MODE_PAD); | |
| st->txh.needs_refresh = GF_TRUE; | |
| } else { | |
| st->txh.needs_refresh = GF_FALSE; | |
| } | |
| memcpy(tr_state->svg_props, &backup_props_1, sizeof(SVGPropertiesPointers)); | |
| tr_state->svg_flags = backup_flags_1; | |
| } | |
| static void svg_update_gradient(SVG_GradientStack *st, GF_ChildNodeItem *children, Bool linear) | |
| { | |
| SVGPropertiesPointers *svgp; | |
| GF_Node *node = st->txh.owner; | |
| GF_TraverseState *tr_state = st->txh.compositor->traverse_state; | |
| if (!gf_node_dirty_get(node)) { | |
| if (st->current_frame==st->txh.compositor->current_frame) return; | |
| st->current_frame = st->txh.compositor->current_frame; | |
| st->txh.needs_refresh = GF_FALSE; | |
| // if (!st->animated) return; | |
| } | |
| if (!tr_state->svg_props) { | |
| GF_SAFEALLOC(svgp, SVGPropertiesPointers); | |
| if (svgp) { | |
| gf_svg_properties_init_pointers(svgp); | |
| tr_state->svg_props = svgp; | |
| svg_gradient_traverse(node, tr_state, GF_FALSE); | |
| gf_svg_properties_reset_pointers(svgp); | |
| gf_free(svgp); | |
| } | |
| tr_state->svg_props = NULL; | |
| } else { | |
| svg_gradient_traverse(node, tr_state, GF_FALSE); | |
| } | |
| } | |
| static void svg_traverse_gradient(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| GF_TraverseState *tr_state = (GF_TraverseState *) rs; | |
| if (is_destroy) { | |
| SVG_DestroyPaintServer(node); | |
| return; | |
| } | |
| if (tr_state->traversing_mode != TRAVERSE_SORT) return; | |
| svg_gradient_traverse(node, tr_state, GF_TRUE); | |
| } | |
| #define GRAD_TEXTURE_SIZE 128 | |
| #define GRAD_TEXTURE_HSIZE 64 | |
| static GF_Rect compositor_svg_get_gradient_bounds(GF_TextureHandler *txh, SVGAllAttributes *all_atts) | |
| { | |
| GF_Rect rc; | |
| if (gf_node_get_tag(txh->owner)==TAG_SVG_radialGradient) { | |
| rc.x = rc.y = rc.width = FIX_ONE/2; | |
| if (all_atts->r) { | |
| rc.width = 2*all_atts->r->value; | |
| if (all_atts->r->type==SVG_NUMBER_PERCENTAGE) rc.width /= 100; | |
| } | |
| if (all_atts->cx) { | |
| rc.x = all_atts->cx->value; | |
| if (all_atts->cx->type==SVG_NUMBER_PERCENTAGE) rc.x /= 100; | |
| } | |
| if (all_atts->cy) { | |
| rc.y = all_atts->cy->value; | |
| if (all_atts->cy->type==SVG_NUMBER_PERCENTAGE) rc.y /= 100; | |
| } | |
| rc.height = rc.width; | |
| rc.x -= rc.width/2; | |
| rc.y -= rc.height/2; | |
| } else { | |
| rc.x = rc.y = rc.height = 0; | |
| rc.width = FIX_ONE; | |
| if (all_atts->x1) { | |
| rc.x = all_atts->x1->value; | |
| if (all_atts->x1->type==SVG_NUMBER_PERCENTAGE) rc.x /= 100; | |
| } | |
| if (all_atts->y1) { | |
| rc.y = all_atts->y1->value; | |
| if (all_atts->y1->type==SVG_NUMBER_PERCENTAGE) rc.y /= 100; | |
| } | |
| if (all_atts->x2) { | |
| rc.width = all_atts->x2->value; | |
| if (all_atts->x2->type==SVG_NUMBER_PERCENTAGE) rc.width/= 100; | |
| } | |
| rc.width -= rc.x; | |
| if (all_atts->y2) { | |
| rc.height = all_atts->y2->value; | |
| if (all_atts->y2->type==SVG_NUMBER_PERCENTAGE) rc.height /= 100; | |
| } | |
| rc.height -= rc.y; | |
| if (!rc.width) rc.width = rc.height; | |
| if (!rc.height) rc.height = rc.width; | |
| } | |
| rc.y += rc.height; | |
| return rc; | |
| } | |
| void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) | |
| { | |
| u32 i; | |
| Fixed size; | |
| GF_Matrix2D mat; | |
| GF_EVGStencil * stencil; | |
| GF_EVGSurface *surface; | |
| GF_EVGStencil * texture2D; | |
| GF_Path *path; | |
| GF_Err e; | |
| Bool transparent; | |
| SVGAllAttributes all_atts; | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(txh->owner); | |
| if (!txh->tx_io) return; | |
| if (!(txh->flags & GF_SR_TEXTURE_GRAD_REGISTERED)) { | |
| txh->flags |= GF_SR_TEXTURE_GRAD_REGISTERED; | |
| if (gf_list_find(txh->compositor->textures, txh)<0) | |
| gf_list_insert(txh->compositor->textures, txh, 0); | |
| } | |
| if (txh->data) { | |
| gf_free(txh->data); | |
| txh->data = NULL; | |
| } | |
| stencil = gf_sc_texture_get_stencil(txh); | |
| if (!stencil) return; | |
| /*init our 2D graphics stuff*/ | |
| texture2D = gf_evg_stencil_new(GF_STENCIL_TEXTURE); | |
| if (!texture2D) return; | |
| surface = gf_evg_surface_new(GF_TRUE); | |
| if (!surface) { | |
| gf_evg_stencil_delete(texture2D); | |
| return; | |
| } | |
| transparent = st->txh.transparent; | |
| if (st->txh.flags & GF_SR_TEXTURE_GRAD_NO_RGB) transparent = GF_TRUE; | |
| if (transparent) { | |
| if (!txh->data) { | |
| txh->data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4); | |
| } else { | |
| memset(txh->data, 0, sizeof(char)*txh->stride*txh->height); | |
| } | |
| e = gf_evg_stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB); | |
| } else { | |
| if (!txh->data) { | |
| txh->data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*3); | |
| } | |
| e = gf_evg_stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB); | |
| /*try with ARGB (it actually is needed for GDIplus module since GDIplus cannot handle native RGB texture (it works in BGR)*/ | |
| if (e) { | |
| /*remember for later use*/ | |
| st->txh.flags |= GF_SR_TEXTURE_GRAD_NO_RGB; | |
| transparent = GF_TRUE; | |
| gf_free(txh->data); | |
| txh->data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4); | |
| e = gf_evg_stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB); | |
| } | |
| } | |
| if (e) { | |
| gf_free(txh->data); | |
| txh->data = NULL; | |
| gf_evg_stencil_delete(texture2D); | |
| gf_evg_surface_delete(surface); | |
| return; | |
| } | |
| e = gf_evg_surface_attach_to_texture(surface, texture2D); | |
| if (e) { | |
| gf_evg_stencil_delete(texture2D); | |
| gf_evg_surface_delete(surface); | |
| return; | |
| } | |
| size = INT2FIX(GRAD_TEXTURE_HSIZE); | |
| /*fill surface*/ | |
| path = gf_path_new(); | |
| gf_path_add_move_to(path, -size, -size); | |
| gf_path_add_line_to(path, size, -size); | |
| gf_path_add_line_to(path, size, size); | |
| gf_path_add_line_to(path, -size, size); | |
| gf_path_close(path); | |
| gf_mx2d_init(mat); | |
| txh->compute_gradient_matrix(txh, NULL, &mat, GF_FALSE); | |
| gf_svg_flatten_attributes((SVG_Element*)txh->owner, &all_atts); | |
| if (all_atts.gradientUnits && (*(SVG_GradientUnit*)all_atts.gradientUnits==SVG_GRADIENTUNITS_OBJECT) ) { | |
| if (all_atts.gradientTransform) | |
| gf_mx2d_copy(mat, all_atts.gradientTransform->mat); | |
| gf_mx2d_add_scale(&mat, 2*size, 2*size); | |
| gf_mx2d_add_translation(&mat, -size, -size); | |
| } else { | |
| GF_Rect rc = compositor_svg_get_gradient_bounds(txh, &all_atts); | |
| /*recenter the gradient to use full texture*/ | |
| gf_mx2d_add_translation(&mat, -rc.x, rc.height-rc.y); | |
| gf_mx2d_add_scale(&mat, gf_divfix(2*size, rc.width), gf_divfix(2*size , rc.height)); | |
| gf_mx2d_add_translation(&mat, -size, -size); | |
| } | |
| gf_evg_stencil_set_matrix(stencil, &mat); | |
| gf_evg_stencil_set_auto_matrix(stencil, GF_FALSE); | |
| gf_evg_surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); | |
| gf_evg_surface_set_path(surface, path); | |
| gf_evg_surface_fill(surface, stencil); | |
| gf_evg_surface_delete(surface); | |
| gf_evg_stencil_delete(texture2D); | |
| gf_path_del(path); | |
| txh->width = GRAD_TEXTURE_SIZE; | |
| txh->height = GRAD_TEXTURE_SIZE; | |
| txh->transparent = transparent; | |
| txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; | |
| if (transparent) { | |
| u32 j; | |
| txh->stride = GRAD_TEXTURE_SIZE*4; | |
| /*back to RGBA texturing*/ | |
| txh->pixelformat = GF_PIXEL_RGBA; | |
| for (i=0; i<txh->height; i++) { | |
| char *data = txh->data + i*txh->stride; | |
| for (j=0; j<txh->width; j++) { | |
| u32 val = *(u32 *) &data[4*j]; | |
| data[4*j] = (val>>16) & 0xFF; | |
| data[4*j+1] = (val>>8) & 0xFF; | |
| data[4*j+2] = (val) & 0xFF; | |
| data[4*j+3] = (val>>24) & 0xFF; | |
| } | |
| } | |
| } else { | |
| txh->stride = GRAD_TEXTURE_SIZE*3; | |
| txh->pixelformat = GF_PIXEL_RGB; | |
| } | |
| gf_sc_texture_set_data(txh); | |
| return; | |
| } | |
| /* linear gradient */ | |
| static void SVG_UpdateLinearGradient(GF_TextureHandler *txh) | |
| { | |
| SVG_Element *lg = (SVG_Element *) txh->owner; | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(txh->owner); | |
| svg_update_gradient(st, lg->children, GF_TRUE); | |
| } | |
| static void SVG_LG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d) | |
| { | |
| GF_EVGStencil * stencil; | |
| SFVec2f start, end; | |
| SVGAllAttributes all_atts; | |
| SVG_Element *lg = (SVG_Element *) txh->owner; | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(txh->owner); | |
| if (!txh->tx_io) return; | |
| stencil = gf_sc_texture_get_stencil(txh); | |
| if (!stencil) return; | |
| svg_update_gradient(st, lg->children, GF_TRUE); | |
| gf_svg_flatten_attributes((SVG_Element*)txh->owner, &all_atts); | |
| /*get "transfered" attributed from xlink:href if any*/ | |
| svg_copy_gradient_attributes_from(txh->owner, &all_atts); | |
| gf_mx2d_init(*mat); | |
| /*gradient is a texture, only update the bounds*/ | |
| if (for_3d) { | |
| GF_Rect rc; | |
| if (!all_atts.gradientUnits || (*(SVG_GradientUnit*)all_atts.gradientUnits==SVG_GRADIENTUNITS_OBJECT)) | |
| return; | |
| /*get gradient bounds in local coord system*/ | |
| rc = compositor_svg_get_gradient_bounds(txh, &all_atts); | |
| gf_mx2d_add_scale(mat, gf_divfix(rc.width, bounds->width), gf_divfix(rc.height, bounds->height)); | |
| return; | |
| } | |
| if (all_atts.gradientTransform) | |
| gf_mx2d_copy(*mat, all_atts.gradientTransform->mat ); | |
| if (all_atts.x1) { | |
| start.x = all_atts.x1->value; | |
| if (all_atts.x1->type==SVG_NUMBER_PERCENTAGE) start.x /= 100; | |
| } else { | |
| start.x = 0; | |
| } | |
| if (all_atts.y1) { | |
| start.y = all_atts.y1->value; | |
| if (all_atts.y1->type==SVG_NUMBER_PERCENTAGE) start.y /= 100; | |
| } else { | |
| start.y = 0; | |
| } | |
| if (all_atts.x2) { | |
| end.x = all_atts.x2->value; | |
| if (all_atts.x2->type==SVG_NUMBER_PERCENTAGE) end.x /= 100; | |
| } else { | |
| end.x = FIX_ONE; | |
| } | |
| if (all_atts.y2) { | |
| end.y = all_atts.y2->value; | |
| if (all_atts.y2->type==SVG_NUMBER_PERCENTAGE) end.y /= 100; | |
| } else { | |
| end.y = 0; | |
| } | |
| gf_evg_stencil_set_gradient_mode(stencil, (GF_GradientMode) (all_atts.spreadMethod ? *(SVG_SpreadMethod*)all_atts.spreadMethod : 0) ); | |
| if (bounds && (!all_atts.gradientUnits || (*(SVG_GradientUnit*)all_atts.gradientUnits==SVG_GRADIENTUNITS_OBJECT)) ) { | |
| /*move to local coord system - cf SVG spec*/ | |
| gf_mx2d_add_scale(mat, bounds->width, bounds->height); | |
| gf_mx2d_add_translation(mat, bounds->x, bounds->y - bounds->height); | |
| } | |
| gf_evg_stencil_set_linear_gradient(stencil, start.x, start.y, end.x, end.y); | |
| } | |
| void compositor_init_svg_linearGradient(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| SVG_GradientStack *st; | |
| GF_SAFEALLOC(st, SVG_GradientStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg gradient stack\n")); | |
| return; | |
| } | |
| /*!!! Gradients are textures but are not registered as textures with the compositor in order to avoid updating | |
| too many textures each frame - gradients are only registered with the compositor when they are used in OpenGL, in order | |
| to release associated HW resource when no longer used*/ | |
| st->txh.owner = node; | |
| st->txh.compositor = compositor; | |
| st->txh.update_texture_fcnt = SVG_UpdateLinearGradient; | |
| st->txh.flags = GF_SR_TEXTURE_SVG; | |
| st->txh.compute_gradient_matrix = SVG_LG_ComputeMatrix; | |
| st->linear = GF_TRUE; | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, svg_traverse_gradient); | |
| } | |
| /* radial gradient */ | |
| static void SVG_UpdateRadialGradient(GF_TextureHandler *txh) | |
| { | |
| SVG_Element *rg = (SVG_Element *) txh->owner; | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(txh->owner); | |
| svg_update_gradient(st, rg->children, GF_FALSE); | |
| } | |
| static void SVG_RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d) | |
| { | |
| GF_EVGStencil * stencil; | |
| SFVec2f center, focal; | |
| Fixed radius; | |
| SVGAllAttributes all_atts; | |
| SVG_Element *rg = (SVG_Element *) txh->owner; | |
| SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(txh->owner); | |
| /*create gradient brush if needed*/ | |
| if (!txh->tx_io) return; | |
| stencil = gf_sc_texture_get_stencil(txh); | |
| if (!stencil) return; | |
| svg_update_gradient(st, rg->children, GF_FALSE); | |
| gf_svg_flatten_attributes((SVG_Element*)txh->owner, &all_atts); | |
| /*get transfered attributed from xlink:href if any*/ | |
| svg_copy_gradient_attributes_from(txh->owner, &all_atts); | |
| gf_mx2d_init(*mat); | |
| /*gradient is a texture, only update the bounds*/ | |
| if (for_3d && bounds) { | |
| GF_Rect rc; | |
| if (!all_atts.gradientUnits || (*(SVG_GradientUnit*)all_atts.gradientUnits==SVG_GRADIENTUNITS_OBJECT)) | |
| return; | |
| /*get gradient bounds in local coord system*/ | |
| rc = compositor_svg_get_gradient_bounds(txh, &all_atts); | |
| gf_mx2d_add_translation(mat, gf_divfix(rc.x-bounds->x, rc.width), gf_divfix(bounds->y - rc.y, rc.height) ); | |
| gf_mx2d_add_scale(mat, gf_divfix(rc.width, bounds->width), gf_divfix(rc.height, bounds->height)); | |
| gf_mx2d_inverse(mat); | |
| return; | |
| } | |
| if (all_atts.gradientTransform) | |
| gf_mx2d_copy(*mat, all_atts.gradientTransform->mat); | |
| if (all_atts.r) { | |
| radius = all_atts.r->value; | |
| if (all_atts.r->type==SVG_NUMBER_PERCENTAGE) radius /= 100; | |
| } else { | |
| radius = FIX_ONE/2; | |
| } | |
| if (all_atts.cx) { | |
| center.x = all_atts.cx->value; | |
| if (all_atts.cx->type==SVG_NUMBER_PERCENTAGE) center.x /= 100; | |
| } else { | |
| center.x = FIX_ONE/2; | |
| } | |
| if (all_atts.cy) { | |
| center.y = all_atts.cy->value; | |
| if (all_atts.cy->type==SVG_NUMBER_PERCENTAGE) center.y /= 100; | |
| } else { | |
| center.y = FIX_ONE/2; | |
| } | |
| gf_evg_stencil_set_gradient_mode(stencil, (GF_GradientMode) (all_atts.spreadMethod ? *(SVG_SpreadMethod*)all_atts.spreadMethod : 0)); | |
| if (all_atts.fx) { | |
| focal.x = all_atts.fx->value; | |
| if (all_atts.fx->type==SVG_NUMBER_PERCENTAGE) focal.x /= 100; | |
| } else { | |
| focal.x = center.x; | |
| } | |
| if (all_atts.fy) { | |
| focal.y = all_atts.fy->value; | |
| if (all_atts.fy->type==SVG_NUMBER_PERCENTAGE) focal.y /= 100; | |
| } else { | |
| focal.y = center.y; | |
| } | |
| /* clamping fx/fy according to: | |
| http://www.w3.org/TR/SVG11/pservers.html#RadialGradients | |
| If the point defined by fx and fy lies outside the circle defined by cx, cy and r, | |
| then the user agent shall set the focal point to the intersection of the line from (cx, cy) | |
| to (fx, fy) with the circle defined by cx, cy and r.*/ | |
| { | |
| Fixed norm = gf_v2d_distance(&focal, ¢er); | |
| if (norm > radius) { | |
| Fixed xdelta = gf_muldiv(radius, focal.x-center.x, norm); | |
| Fixed ydelta = gf_muldiv(radius, focal.y-center.y, norm); | |
| focal.x = center.x + xdelta; | |
| focal.y = center.y + ydelta; | |
| } | |
| } | |
| if (bounds && (!all_atts.gradientUnits || (*(SVG_GradientUnit*)all_atts.gradientUnits==SVG_GRADIENTUNITS_OBJECT)) ) { | |
| /*move to local coord system - cf SVG spec*/ | |
| gf_mx2d_add_scale(mat, bounds->width, bounds->height); | |
| gf_mx2d_add_translation(mat, bounds->x, bounds->y - bounds->height); | |
| } | |
| gf_evg_stencil_set_radial_gradient(stencil, center.x, center.y, focal.x, focal.y, radius, radius); | |
| } | |
| void compositor_init_svg_radialGradient(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| SVG_GradientStack *st; | |
| GF_SAFEALLOC(st, SVG_GradientStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg gradient stack\n")); | |
| return; | |
| } | |
| /*!!! Gradients are textures but are not registered as textures with the compositor in order to avoid updating | |
| too many textures each frame - gradients are only registered with the compositor when they are used in OpenGL, in order | |
| to release associated HW resource when no longer used*/ | |
| st->txh.owner = node; | |
| st->txh.compositor = compositor; | |
| st->txh.flags = GF_SR_TEXTURE_SVG; | |
| st->txh.update_texture_fcnt = SVG_UpdateRadialGradient; | |
| st->txh.compute_gradient_matrix = SVG_RG_ComputeMatrix; | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, svg_traverse_gradient); | |
| } | |
| static void svg_traverse_PaintServer(GF_Node *node, void *rs, Bool is_destroy, Bool is_solid_color) | |
| { | |
| SVGPropertiesPointers backup_props; | |
| SVGAllAttributes all_atts; | |
| u32 backup_flags; | |
| u32 styling_size = sizeof(SVGPropertiesPointers); | |
| SVG_Element *elt = (SVG_Element *)node; | |
| GF_TraverseState *tr_state = (GF_TraverseState *) rs; | |
| if (is_destroy) { | |
| if (!is_solid_color) | |
| SVG_DestroyPaintServer(node); | |
| return; | |
| } | |
| gf_svg_flatten_attributes(elt, &all_atts); | |
| compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags); | |
| if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) { | |
| return; | |
| } else { | |
| compositor_svg_traverse_children(elt->children, tr_state); | |
| } | |
| memcpy(tr_state->svg_props, &backup_props, styling_size); | |
| tr_state->svg_flags = backup_flags; | |
| } | |
| typedef struct | |
| { | |
| u32 current_frame; | |
| Bool is_dirty; | |
| } GF_SolidColorStack; | |
| Bool compositor_svg_solid_color_dirty(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| GF_SolidColorStack *st = (GF_SolidColorStack*)gf_node_get_private(node); | |
| if (st->current_frame==compositor->current_frame) return st->is_dirty; | |
| st->current_frame = compositor->current_frame; | |
| st->is_dirty = gf_node_dirty_get(node) ? GF_TRUE : GF_FALSE; | |
| gf_node_dirty_clear(node, 0); | |
| return st->is_dirty; | |
| } | |
| static void svg_traverse_solidColor(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| if (is_destroy) { | |
| GF_SolidColorStack *st = (GF_SolidColorStack*)gf_node_get_private(node); | |
| gf_free(st); | |
| return; | |
| } | |
| svg_traverse_PaintServer(node, rs, is_destroy, GF_TRUE); | |
| } | |
| void compositor_init_svg_solidColor(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| GF_SolidColorStack *st; | |
| GF_SAFEALLOC(st, GF_SolidColorStack); | |
| if (!st) return; | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, svg_traverse_solidColor); | |
| } | |
| static void svg_traverse_stop(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| svg_traverse_PaintServer(node, rs, is_destroy, GF_TRUE); | |
| } | |
| void compositor_init_svg_stop(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| gf_node_set_callback_function(node, svg_traverse_stop); | |
| } | |
| GF_TextureHandler *compositor_svg_get_gradient_texture(GF_Node *node) | |
| { | |
| SVG_GradientStack *st = (SVG_GradientStack*) gf_node_get_private((GF_Node *)node); | |
| st->txh.update_texture_fcnt(&st->txh); | |
| return &st->txh; | |
| } | |
| static void svg_traverse_clip_path(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| u32 tr_mode_bck; | |
| Bool immediate_bck; | |
| DrawableContext *ctx_bck; | |
| SVGPropertiesPointers *props_bck; | |
| GF_Matrix2D mx_bck; | |
| Bool is_first = GF_FALSE; | |
| GF_TraverseState *tr_state = (GF_TraverseState *) rs; | |
| SVGPropertiesPointers *svg_props = gf_node_get_private(node); | |
| if (is_destroy) { | |
| gf_free(svg_props); | |
| return; | |
| } | |
| if (tr_state->traversing_mode!=TRAVERSE_DRAW_2D) { | |
| return; | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| if (tr_state->visual->type_3d && !tr_state->visual->compositor->hybrid_opengl) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] clipPath not supported in 3D mode, try using `--ogl=hybrid`\n")); | |
| return; | |
| } | |
| #endif | |
| if (! tr_state->visual->CheckAttached(tr_state->visual) ) return; | |
| if (!tr_state->ctx) return; | |
| tr_mode_bck = tr_state->traversing_mode; | |
| immediate_bck = tr_state->immediate_draw; | |
| ctx_bck = tr_state->ctx; | |
| props_bck = tr_state->svg_props; | |
| gf_mx2d_copy(mx_bck, tr_state->transform); | |
| //force immediate mode draw | |
| tr_state->traversing_mode = TRAVERSE_SORT; | |
| tr_state->immediate_draw = GF_TRUE; | |
| gf_mx2d_copy(tr_state->transform, tr_state->ctx->transform); | |
| tr_state->ctx = NULL; | |
| if (gf_evg_surface_get_mask_mode(tr_state->visual->raster_surface)==GF_EVGMASK_NONE) { | |
| is_first = GF_TRUE; | |
| gf_evg_surface_set_mask_mode(tr_state->visual->raster_surface, GF_EVGMASK_DRAW); | |
| } | |
| tr_state->svg_props = svg_props; | |
| gf_svg_properties_init_pointers(tr_state->svg_props); | |
| compositor_svg_traverse_children(((SVG_Element *)node)->children, tr_state); | |
| gf_svg_properties_reset_pointers(tr_state->svg_props); | |
| if (is_first) { | |
| gf_evg_surface_set_mask_mode(tr_state->visual->raster_surface, GF_EVGMASK_USE); | |
| } | |
| tr_state->traversing_mode = tr_mode_bck; | |
| tr_state->immediate_draw = immediate_bck; | |
| tr_state->ctx = ctx_bck; | |
| tr_state->svg_props = props_bck; | |
| gf_mx2d_copy(tr_state->transform, mx_bck); | |
| } | |
| void compositor_init_svg_clip_path(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| SVGPropertiesPointers *svg_props; | |
| GF_SAFEALLOC(svg_props, SVGPropertiesPointers); | |
| if (!svg_props) return; | |
| gf_node_set_private(node, svg_props); | |
| gf_node_set_callback_function(node, svg_traverse_clip_path); | |
| } | |
| #endif // !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_COMPOSITOR) |