| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * 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 "texturing.h" | |
| #include <gpac/network.h> | |
| #include <gpac/nodes_mpeg4.h> | |
| #include <gpac/nodes_x3d.h> | |
| #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) | |
| /*for cache texture decode and hash*/ | |
| #include <gpac/avparse.h> | |
| #include <gpac/crypt.h> | |
| #include "nodes_stacks.h" | |
| typedef struct | |
| { | |
| GF_TextureHandler txh; | |
| GF_TimeNode time_handle; | |
| Bool fetch_first_frame, first_frame_fetched, is_x3d; | |
| Double start_time; | |
| } MovieTextureStack; | |
| static void movietexture_destroy(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| if (is_destroy) { | |
| MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(node); | |
| gf_sc_texture_destroy(&st->txh); | |
| if (st->time_handle.is_registered) gf_sc_unregister_time_node(st->txh.compositor, &st->time_handle); | |
| gf_free(st); | |
| } | |
| } | |
| static Fixed movietexture_get_speed(MovieTextureStack *stack, M_MovieTexture *mt) | |
| { | |
| return gf_mo_get_speed(stack->txh.stream, mt->speed); | |
| } | |
| static Bool movietexture_get_loop(MovieTextureStack *stack, M_MovieTexture *mt) | |
| { | |
| return gf_mo_get_loop(stack->txh.stream, mt->loop); | |
| } | |
| static void movietexture_activate(MovieTextureStack *stack, M_MovieTexture *mt, Double scene_time) | |
| { | |
| mt->isActive = 1; | |
| gf_node_event_out((GF_Node*)mt, 8/*"isActive"*/); | |
| if (!stack->txh.is_open) { | |
| scene_time -= mt->startTime; | |
| gf_sc_texture_play_from_to(&stack->txh, &mt->url, scene_time, -1, gf_mo_get_loop(stack->txh.stream, mt->loop), 0); | |
| } else if (stack->first_frame_fetched) { | |
| gf_mo_resume(stack->txh.stream); | |
| } | |
| gf_mo_set_speed(stack->txh.stream, mt->speed); | |
| } | |
| static void movietexture_deactivate(MovieTextureStack *stack, M_MovieTexture *mt) | |
| { | |
| mt->isActive = 0; | |
| gf_node_event_out((GF_Node*)mt, 8/*"isActive"*/); | |
| stack->time_handle.needs_unregister = 1; | |
| if (stack->txh.is_open) { | |
| gf_sc_texture_stop_no_unregister(&stack->txh); | |
| } | |
| } | |
| static void movietexture_update(GF_TextureHandler *txh) | |
| { | |
| M_MovieTexture *txnode = (M_MovieTexture *) txh->owner; | |
| MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(txh->owner); | |
| /*setup texture if needed*/ | |
| if (!txh->is_open) return; | |
| if (!txnode->isActive && st->first_frame_fetched) return; | |
| /*when fetching the first frame disable resync*/ | |
| gf_sc_texture_update_frame(txh, 0); | |
| if (txh->stream_finished) { | |
| if (movietexture_get_loop(st, txnode)) { | |
| gf_sc_texture_restart(txh); | |
| } | |
| /*if active deactivate*/ | |
| else if (txnode->isActive && (txnode->startTime<txnode->stopTime) && gf_mo_should_deactivate(st->txh.stream) ) { | |
| movietexture_deactivate(st, txnode); | |
| } | |
| } | |
| /*first frame is fetched*/ | |
| if (!st->first_frame_fetched && (txh->needs_refresh) ) { | |
| st->first_frame_fetched = 1; | |
| txnode->duration_changed = gf_mo_get_duration(txh->stream); | |
| gf_node_event_out(txh->owner, 7/*"duration_changed"*/); | |
| /*stop stream if needed*/ | |
| if (!txnode->isActive && txh->is_open) { | |
| gf_mo_pause(txh->stream); | |
| /*make sure the refresh flag is not cleared*/ | |
| txh->needs_refresh = 1; | |
| gf_sc_invalidate(txh->compositor, NULL); | |
| } | |
| } | |
| if (txh->needs_refresh) { | |
| /*mark all subtrees using this image as dirty*/ | |
| gf_node_dirty_parents(txh->owner); | |
| } | |
| } | |
| static void movietexture_update_time(GF_TimeNode *st) | |
| { | |
| Double time; | |
| M_MovieTexture *mt = (M_MovieTexture *)st->udta; | |
| MovieTextureStack *stack = (MovieTextureStack *)gf_node_get_private(st->udta); | |
| /*not active, store start time and speed*/ | |
| if ( ! mt->isActive) { | |
| stack->start_time = mt->startTime; | |
| } | |
| time = gf_node_get_scene_time(st->udta); | |
| if (time < stack->start_time || | |
| /*special case if we're getting active AFTER stoptime */ | |
| (!mt->isActive && (mt->stopTime > stack->start_time) && (time>=mt->stopTime)) | |
| // || (!stack->start_time && !stack->is_x3d && !mt->loop) | |
| ) { | |
| /*opens stream only at first access to fetch first frame*/ | |
| if (stack->fetch_first_frame) { | |
| stack->fetch_first_frame = 0; | |
| if (!stack->txh.is_open) | |
| gf_sc_texture_play(&stack->txh, &mt->url); | |
| else | |
| gf_mo_resume(stack->txh.stream); | |
| } | |
| return; | |
| } | |
| if (movietexture_get_speed(stack, mt) && mt->isActive) { | |
| /*if stoptime is reached (>startTime) deactivate*/ | |
| if ((mt->stopTime > stack->start_time) && (time >= mt->stopTime) ) { | |
| movietexture_deactivate(stack, mt); | |
| return; | |
| } | |
| } | |
| /*we're (about to be) active: VRML: | |
| "A time-dependent node is inactive until its startTime is reached. When time now becomes greater than or | |
| equal to startTime, an isActive TRUE event is generated and the time-dependent node becomes active */ | |
| if (! mt->isActive) movietexture_activate(stack, mt, time); | |
| stack->txh.stream_finished = GF_FALSE; | |
| if (!mt->url.count) | |
| stack->txh.stream_finished = GF_TRUE; | |
| } | |
| void compositor_init_movietexture(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| MovieTextureStack *st; | |
| GF_SAFEALLOC(st, MovieTextureStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate movie texture stack\n")); | |
| return; | |
| } | |
| gf_sc_texture_setup(&st->txh, compositor, node); | |
| st->txh.update_texture_fcnt = movietexture_update; | |
| st->time_handle.UpdateTimeNode = movietexture_update_time; | |
| st->time_handle.udta = node; | |
| st->fetch_first_frame = 1; | |
| st->txh.flags = 0; | |
| if (((M_MovieTexture*)node)->repeatS) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S; | |
| if (((M_MovieTexture*)node)->repeatT) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T; | |
| #ifndef GPAC_DISABLE_X3D | |
| st->is_x3d = (gf_node_get_tag(node)==TAG_X3D_MovieTexture) ? 1 : 0; | |
| #endif | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, movietexture_destroy); | |
| gf_sc_register_time_node(compositor, &st->time_handle); | |
| } | |
| GF_TextureHandler *mt_get_texture(GF_Node *node) | |
| { | |
| MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(node); | |
| return &st->txh; | |
| } | |
| void compositor_movietexture_modified(GF_Node *node) | |
| { | |
| M_MovieTexture *mt = (M_MovieTexture *)node; | |
| MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(node); | |
| if (!st) return; | |
| /*if open and changed, stop and play*/ | |
| if (gf_sc_texture_check_url_change(&st->txh, &mt->url)) { | |
| if (st->txh.is_open) gf_sc_texture_stop(&st->txh); | |
| if (mt->isActive) gf_sc_texture_play(&st->txh, &mt->url); | |
| } | |
| /*update state if we're active*/ | |
| else if (mt->isActive) { | |
| movietexture_update_time(&st->time_handle); | |
| if (!mt->isActive) return; | |
| } | |
| /*reregister if needed*/ | |
| st->time_handle.needs_unregister = 0; | |
| if (!st->time_handle.is_registered) gf_sc_register_time_node(st->txh.compositor, &st->time_handle); | |
| } | |
| static void imagetexture_destroy(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| if (is_destroy) { | |
| GF_TextureHandler *txh = (GF_TextureHandler *) gf_node_get_private(node); | |
| /*cleanup cache if needed*/ | |
| if (gf_node_get_tag(node)==TAG_MPEG4_CacheTexture) { | |
| char section[64]; | |
| const char *opt, *file; | |
| Bool delete_file = GF_TRUE; | |
| M_CacheTexture *ct = (M_CacheTexture*)node; | |
| sprintf(section, "@cache=%p", ct); | |
| file = gf_opts_get_key(section, "cacheFile"); | |
| opt = gf_opts_get_key(section, "expireAfterNTP"); | |
| if (opt) { | |
| u32 sec, frac, exp; | |
| sscanf(opt, "%u", &exp); | |
| gf_net_get_ntp(&sec, &frac); | |
| if (!exp || (exp>sec)) delete_file=GF_FALSE; | |
| } | |
| if (delete_file) { | |
| if (file) gf_file_delete((char*)file); | |
| gf_opts_del_section(section); | |
| } | |
| if (txh->data) gf_free(txh->data); | |
| txh->data = NULL; | |
| } | |
| gf_sc_texture_destroy(txh); | |
| gf_free(txh); | |
| } | |
| } | |
| static void imagetexture_update(GF_TextureHandler *txh) | |
| { | |
| if (gf_node_get_tag(txh->owner)!=TAG_MPEG4_CacheTexture) { | |
| MFURL url = ((M_ImageTexture *) txh->owner)->url; | |
| /*setup texture if needed*/ | |
| if (!txh->is_open && url.count) { | |
| gf_sc_texture_play(txh, &url); | |
| } | |
| gf_sc_texture_update_frame(txh, 0); | |
| if ( | |
| /*URL is present but not opened - redraw till fetch*/ | |
| /* (txh->stream && !txh->tx_io) && */ | |
| /*image has been updated*/ | |
| txh->needs_refresh) { | |
| /*mark all subtrees using this image as dirty*/ | |
| gf_node_dirty_parents(txh->owner); | |
| gf_sc_invalidate(txh->compositor, NULL); | |
| } | |
| return; | |
| } | |
| /*cache texture case*/ | |
| else { | |
| M_CacheTexture *ct = (M_CacheTexture *) txh->owner; | |
| /*decode cacheTexture data */ | |
| if ((ct->data || ct->image.buffer) && !txh->data) { | |
| #ifndef GPAC_DISABLE_AV_PARSERS | |
| u32 out_size; | |
| GF_Err e; | |
| /*BT/XMT playback: load to memory*/ | |
| if (ct->image.buffer) { | |
| char *par = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) ); | |
| char *src_url = gf_url_concatenate(par, ct->image.buffer); | |
| if (ct->data) gf_free(ct->data); | |
| ct->data = NULL; | |
| ct->data_len = 0; | |
| e = gf_file_load_data(src_url ? src_url : ct->image.buffer, &ct->data, &ct->data_len); | |
| if (e) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: %s\n", src_url ? src_url : ct->image.buffer, gf_error_to_string(e) ) ); | |
| } | |
| if (ct->image.buffer) gf_free(ct->image.buffer); | |
| ct->image.buffer = NULL; | |
| if (src_url) gf_free(src_url); | |
| } | |
| /*BIFS decoded playback*/ | |
| switch (ct->objectTypeIndication) { | |
| case GF_CODECID_JPEG: | |
| out_size = 0; | |
| e = gf_img_jpeg_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size, 3); | |
| if (e==GF_BUFFER_TOO_SMALL) { | |
| u32 BPP; | |
| txh->data = gf_malloc(sizeof(char) * out_size); | |
| if (txh->pixelformat==GF_PIXEL_GREYSCALE) BPP = 1; | |
| else BPP = 3; | |
| e = gf_img_jpeg_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size, BPP); | |
| if (e==GF_OK) { | |
| gf_sc_texture_allocate(txh); | |
| gf_sc_texture_set_data(txh); | |
| txh->needs_refresh = 1; | |
| txh->stride = out_size / txh->height; | |
| } | |
| } | |
| break; | |
| case GF_CODECID_PNG: | |
| out_size = 0; | |
| e = gf_img_png_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size); | |
| if (e==GF_BUFFER_TOO_SMALL) { | |
| txh->data = gf_malloc(sizeof(char) * out_size); | |
| e = gf_img_png_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size); | |
| if (e==GF_OK) { | |
| gf_sc_texture_allocate(txh); | |
| gf_sc_texture_set_data(txh); | |
| txh->needs_refresh = 1; | |
| txh->stride = out_size / txh->height; | |
| } | |
| } | |
| break; | |
| } | |
| #endif // GPAC_DISABLE_AV_PARSERS | |
| /*cacheURL is specified, store the image*/ | |
| if (ct->cacheURL.buffer) { | |
| u32 i; | |
| u8 hash[20]; | |
| FILE *cached_texture; | |
| char szExtractName[GF_MAX_PATH], *opt, *src_url; | |
| opt = (char *) gf_opts_get_key("core", "cache"); | |
| if (opt) { | |
| gf_strcpy(szExtractName, opt); | |
| } else { | |
| gf_strcpy(szExtractName, gf_get_default_cache_directory()); | |
| } | |
| gf_strcat(szExtractName, "/"); | |
| src_url = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) ); | |
| gf_sha1_csum((u8 *)src_url, (u32) strlen(src_url), hash); | |
| for (i=0; i<20; i++) { | |
| char t[3]; | |
| t[2] = 0; | |
| sprintf(t, "%02X", hash[i]); | |
| gf_strcat(szExtractName, t); | |
| } | |
| gf_strcat(szExtractName, "_"); | |
| gf_strcat(szExtractName, ct->cacheURL.buffer); | |
| cached_texture = gf_fopen(szExtractName, "wb"); | |
| if (cached_texture) { | |
| gf_fwrite(ct->data, ct->data_len, cached_texture); | |
| gf_fclose(cached_texture); | |
| } | |
| /*and write cache info*/ | |
| if (ct->expirationDate!=0) { | |
| char section[64]; | |
| sprintf(section, "@cache=%p", ct); | |
| gf_opts_set_key(section, "serviceURL", src_url); | |
| gf_opts_set_key(section, "cacheFile", szExtractName); | |
| gf_opts_set_key(section, "cacheName", ct->cacheURL.buffer); | |
| if (ct->expirationDate>0) { | |
| char exp[50]; | |
| u32 sec, frac; | |
| gf_net_get_ntp(&sec, &frac); | |
| sec += ct->expirationDate; | |
| sprintf(exp, "%u", sec); | |
| gf_opts_set_key(section, "expireAfterNTP", exp); | |
| } else { | |
| gf_opts_set_key(section, "expireAfterNTP", "0"); | |
| } | |
| } | |
| } | |
| /*done with image, destroy buffer*/ | |
| if (ct->data) gf_free(ct->data); | |
| ct->data = NULL; | |
| ct->data_len = 0; | |
| } | |
| } | |
| } | |
| void compositor_init_imagetexture(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| GF_TextureHandler *txh; | |
| GF_SAFEALLOC(txh, GF_TextureHandler); | |
| if (!txh) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate image texture stack\n")); | |
| return; | |
| } | |
| gf_sc_texture_setup(txh, compositor, node); | |
| txh->update_texture_fcnt = imagetexture_update; | |
| gf_node_set_private(node, txh); | |
| gf_node_set_callback_function(node, imagetexture_destroy); | |
| txh->flags = 0; | |
| if (gf_node_get_tag(txh->owner)!=TAG_MPEG4_CacheTexture) { | |
| if (((M_ImageTexture*)node)->repeatS) txh->flags |= GF_SR_TEXTURE_REPEAT_S; | |
| if (((M_ImageTexture*)node)->repeatT) txh->flags |= GF_SR_TEXTURE_REPEAT_T; | |
| } else { | |
| const char *url; | |
| u32 i, count; | |
| M_CacheTexture*ct = (M_CacheTexture*)node; | |
| if (!ct->image.buffer) return; | |
| if (ct->repeatS) txh->flags |= GF_SR_TEXTURE_REPEAT_S; | |
| if (ct->repeatT) txh->flags |= GF_SR_TEXTURE_REPEAT_T; | |
| /*locate existing cache*/ | |
| url = gf_scene_get_service_url( gf_node_get_graph(node) ); | |
| count = gf_opts_get_section_count(); | |
| for (i=0; i<count; i++) { | |
| const char *opt; | |
| const char *name = gf_opts_get_section_name(i); | |
| if (strncmp(name, "@cache=", 7)) continue; | |
| opt = gf_opts_get_key(name, "serviceURL"); | |
| if (!opt || stricmp(opt, url)) continue; | |
| opt = gf_opts_get_key(name, "cacheName"); | |
| if (opt && ct->cacheURL.buffer && !stricmp(opt, ct->cacheURL.buffer)) { | |
| opt = gf_opts_get_key(name, "cacheFile"); | |
| if (opt) gf_file_delete((char*)opt); | |
| gf_opts_del_section(name); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| GF_TextureHandler *it_get_texture(GF_Node *node) | |
| { | |
| return gf_node_get_private(node); | |
| } | |
| void compositor_imagetexture_modified(GF_Node *node) | |
| { | |
| MFURL url; | |
| SFURL sfurl; | |
| GF_TextureHandler *txh = (GF_TextureHandler *) gf_node_get_private(node); | |
| if (!txh) return; | |
| if (gf_node_get_tag(node)!=TAG_MPEG4_CacheTexture) { | |
| url = ((M_ImageTexture *) node)->url; | |
| } else { | |
| url.count = 1; | |
| sfurl.OD_ID=GF_MEDIA_EXTERNAL_ID; | |
| sfurl.url = ((M_CacheTexture *) node)->image.buffer; | |
| url.vals = &sfurl; | |
| } | |
| /*if open and changed, stop and play*/ | |
| if (txh->is_open) { | |
| if (! gf_sc_texture_check_url_change(txh, &url)) return; | |
| gf_sc_texture_stop(txh); | |
| gf_sc_texture_play(txh, &url); | |
| return; | |
| } | |
| /*if not open and changed play*/ | |
| if (url.count) | |
| gf_sc_texture_play(txh, &url); | |
| } | |
| typedef struct | |
| { | |
| GF_TextureHandler txh; | |
| char *pixels; | |
| } PixelTextureStack; | |
| static void pixeltexture_destroy(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| if (is_destroy) { | |
| PixelTextureStack *st = (PixelTextureStack *) gf_node_get_private(node); | |
| if (st->pixels) gf_free(st->pixels); | |
| gf_sc_texture_destroy(&st->txh); | |
| gf_free(st); | |
| } | |
| } | |
| static void pixeltexture_update(GF_TextureHandler *txh) | |
| { | |
| u32 pix_format, stride, i; | |
| M_PixelTexture *pt = (M_PixelTexture *) txh->owner; | |
| PixelTextureStack *st = (PixelTextureStack *) gf_node_get_private(txh->owner); | |
| if (!gf_node_dirty_get(txh->owner)) return; | |
| gf_node_dirty_clear(txh->owner, 0); | |
| /*pixel texture doesn not use any media object but has data in the content. | |
| However we still use the same texture object, just be careful not to use media funtcions*/ | |
| txh->transparent = 0; | |
| stride = pt->image.width; | |
| /*num_components are as in VRML (1->4) not as in BIFS bitstream (0->3)*/ | |
| switch (pt->image.numComponents) { | |
| case 1: | |
| pix_format = GF_PIXEL_GREYSCALE; | |
| break; | |
| case 2: | |
| pix_format = GF_PIXEL_ALPHAGREY; | |
| txh->transparent = 1; | |
| stride *= 2; | |
| break; | |
| case 3: | |
| pix_format = GF_PIXEL_RGB; | |
| txh->transparent = 0; | |
| stride *= 3; | |
| break; | |
| case 4: | |
| pix_format = GF_PIXEL_RGBA; | |
| txh->transparent = 1; | |
| stride *= 4; | |
| break; | |
| default: | |
| return; | |
| } | |
| if (!txh->tx_io) { | |
| gf_sc_texture_allocate(txh); | |
| if (!txh->tx_io) return; | |
| } | |
| if (st->pixels) gf_free(st->pixels); | |
| st->pixels = (char*)gf_malloc(sizeof(char) * stride * pt->image.height); | |
| /*FIXME FOR OPENGL !!*/ | |
| #if 0 | |
| for (i=0; i<pt->image.height; i++) { | |
| memcpy(st->pixels + i * stride, pt->image.pixels + i * stride, stride); | |
| } | |
| #else | |
| /*revert pixel ordering...*/ | |
| for (i=0; i<pt->image.height; i++) { | |
| memcpy(st->pixels + i * stride, pt->image.pixels + (pt->image.height - 1 - i) * stride, stride); | |
| } | |
| #endif | |
| txh->width = pt->image.width; | |
| txh->height = pt->image.height; | |
| txh->stride = stride; | |
| txh->pixelformat = pix_format; | |
| txh->data = st->pixels; | |
| gf_sc_texture_set_data(txh); | |
| } | |
| void compositor_init_pixeltexture(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| PixelTextureStack *st; | |
| GF_SAFEALLOC(st, PixelTextureStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate pixel texture stack\n")); | |
| return; | |
| } | |
| gf_sc_texture_setup(&st->txh, compositor, node); | |
| st->pixels = NULL; | |
| st->txh.update_texture_fcnt = pixeltexture_update; | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, pixeltexture_destroy); | |
| st->txh.flags = 0; | |
| if (((M_PixelTexture*)node)->repeatS) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S; | |
| if (((M_PixelTexture*)node)->repeatT) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T; | |
| } | |
| GF_TextureHandler *pt_get_texture(GF_Node *node) | |
| { | |
| PixelTextureStack *st = (PixelTextureStack *) gf_node_get_private(node); | |
| return &st->txh; | |
| } | |
| static void matte_update(GF_TextureHandler *txh) | |
| { | |
| /*nothing to do*/ | |
| } | |
| void compositor_init_mattetexture(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| GF_TextureHandler *txh; | |
| GF_SAFEALLOC(txh, GF_TextureHandler); | |
| if (!txh) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate matte texture stack\n")); | |
| return; | |
| } | |
| gf_sc_texture_setup(txh, compositor, node); | |
| txh->flags = GF_SR_TEXTURE_MATTE; | |
| txh->update_texture_fcnt = matte_update; | |
| gf_node_set_private(node, txh); | |
| gf_node_set_callback_function(node, imagetexture_destroy); | |
| } | |
| void gf_sc_mo_destroyed(GF_Node *n) | |
| { | |
| void *st = gf_node_get_private(n); | |
| if (!st) return; | |
| switch (gf_node_get_tag(n)) { | |
| case TAG_MPEG4_Background2D: | |
| ((Background2DStack *)st)->txh.stream = NULL; | |
| break; | |
| case TAG_MPEG4_Background: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Background: | |
| #endif | |
| #ifndef GPAC_DISABLE_3D | |
| ((BackgroundStack *)st)->txh_back.stream = NULL; | |
| ((BackgroundStack *)st)->txh_front.stream = NULL; | |
| ((BackgroundStack *)st)->txh_left.stream = NULL; | |
| ((BackgroundStack *)st)->txh_right.stream = NULL; | |
| ((BackgroundStack *)st)->txh_top.stream = NULL; | |
| ((BackgroundStack *)st)->txh_bottom.stream = NULL; | |
| #endif | |
| break; | |
| case TAG_MPEG4_ImageTexture: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_ImageTexture: | |
| #endif | |
| ((GF_TextureHandler *)st)->stream = NULL; | |
| break; | |
| case TAG_MPEG4_MovieTexture: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_MovieTexture: | |
| #endif | |
| ((MovieTextureStack *)st)->txh.stream = NULL; | |
| break; | |
| case TAG_MPEG4_MediaSensor: | |
| ((MediaSensorStack *)st)->stream = NULL; | |
| break; | |
| case TAG_MPEG4_MediaControl: | |
| ((MediaControlStack *)st)->stream = NULL; | |
| break; | |
| case TAG_MPEG4_AudioSource: | |
| ((AudioSourceStack *)st)->input.stream = NULL; | |
| break; | |
| case TAG_MPEG4_AudioClip: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_AudioClip: | |
| #endif | |
| ((AudioClipStack *)st)->input.stream = NULL; | |
| break; | |
| #ifndef GPAC_DISABLE_SVG | |
| case TAG_SVG_video: | |
| case TAG_SVG_image: | |
| ((SVG_video_stack *)st)->txh.stream = NULL; | |
| break; | |
| case TAG_SVG_audio: | |
| ((SVG_audio_stack *)st)->input.stream = NULL; | |
| break; | |
| #endif | |
| default: | |
| break; | |
| } | |
| } | |
| #endif //!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) |