Coverage Report

Created: 2026-10-08 07:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpac/src/compositor/mpeg4_grouping_2d.c
Line
Count
Source
1
/*
2
 *      GPAC - Multimedia Framework C SDK
3
 *
4
 *      Authors: Jean Le Feuvre
5
 *      Copyright (c) Telecom ParisTech 2000-2023
6
 *          All rights reserved
7
 *
8
 *  This file is part of GPAC / Scene Compositor sub-project
9
 *
10
 *  GPAC is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU Lesser General Public License as published by
12
 *  the Free Software Foundation; either version 2, or (at your option)
13
 *  any later version.
14
 *
15
 *  GPAC is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU Lesser General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU Lesser General Public
21
 *  License along with this library; see the file COPYING.  If not, write to
22
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
 *
24
 */
25
26
#include "nodes_stacks.h"
27
#include "mpeg4_grouping.h"
28
#include "visual_manager.h"
29
30
#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR)
31
32
typedef struct
33
{
34
  s32 last_switch;
35
} SwitchStack;
36
37
static void TraverseSwitch(GF_Node *node, void *rs, Bool is_destroy)
38
0
{
39
0
  GF_ChildNodeItem *l;
40
0
  u32 i;
41
0
  Bool prev_switch;
42
0
  GF_ChildNodeItem *children;
43
0
  s32 whichChoice;
44
0
  GF_Node *child;
45
0
  SwitchStack *st = (SwitchStack *)gf_node_get_private(node);
46
0
  GF_TraverseState *tr_state;
47
0
  tr_state = (GF_TraverseState *)rs;
48
0
  children = NULL;
49
50
0
  whichChoice = -1;
51
0
  if (is_destroy) {
52
0
    gf_sc_check_focus_upon_destroy(node);
53
0
    gf_free(st);
54
0
    return;
55
0
  }
56
  /*WARNING: X3D/MPEG4 NOT COMPATIBLE*/
57
0
  switch (gf_node_get_tag(node)) {
58
0
  case TAG_MPEG4_Switch:
59
0
    children = ((M_Switch *)node)->choice;
60
0
    whichChoice = ((M_Switch *)node)->whichChoice;
61
0
    break;
62
0
#ifndef GPAC_DISABLE_X3D
63
0
  case TAG_X3D_Switch:
64
0
    children = ((X_Switch *)node)->children;
65
0
    whichChoice = ((X_Switch *)node)->whichChoice;
66
0
    break;
67
0
#endif
68
0
  }
69
70
0
  if (tr_state->traversing_mode!=TRAVERSE_GET_BOUNDS) {
71
0
    prev_switch = tr_state->switched_off;
72
    /*check changes in choice field*/
73
0
    if ((gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) || (st->last_switch != whichChoice) ) {
74
0
      tr_state->switched_off = 1;
75
0
      i=0;
76
0
      l = children;
77
0
      while (l) {
78
        //      if ((s32) i!=whichChoice) gf_node_traverse(l->node, tr_state);
79
0
        if ((s32) i == st->last_switch) gf_node_traverse(l->node, tr_state);
80
0
        l = l->next;
81
0
        i++;
82
0
      }
83
0
      tr_state->switched_off = 0;
84
0
      st->last_switch = whichChoice;
85
0
    }
86
87
0
    gf_node_dirty_clear(node, 0);
88
89
    /*no need to check for sensors since a sensor is active for the whole parent group, that is for switch itself
90
    CSQ: switch cannot be used to switch sensors, too bad...*/
91
0
    tr_state->switched_off = prev_switch;
92
0
  }
93
94
0
  if (!children) return;
95
0
  if (whichChoice==-2) {
96
#ifndef GPAC_DISABLE_3D
97
    if (tr_state->visual->autostereo_type) {
98
      u32 idx;
99
      u32 count = gf_node_list_get_count(children);
100
      /*this should be a bit more subtle (reusing views if missing, ...)...*/
101
      idx = tr_state->visual->current_view % count;
102
103
      child = (GF_Node*)gf_node_list_get_child(children, idx);
104
      gf_node_traverse(child, tr_state);
105
      return;
106
    } else
107
#endif //GPAC_DISABLE_3D
108
0
    {
109
      /*fallback to first view*/
110
0
      whichChoice=0;
111
0
    }
112
0
  }
113
0
  if (whichChoice>=0) {
114
0
    child = (GF_Node*)gf_node_list_get_child(children, whichChoice);
115
0
    gf_node_traverse(child, tr_state);
116
0
  }
117
0
}
118
119
void compositor_init_switch(GF_Compositor *compositor, GF_Node *node)
120
0
{
121
0
  SwitchStack *st = (SwitchStack *)gf_malloc(sizeof(SwitchStack));
122
0
  st->last_switch = -1;
123
0
  gf_node_set_private(node, st);
124
0
  gf_node_set_callback_function(node, TraverseSwitch);
125
0
}
126
127
128
/*transform2D*/
129
typedef struct
130
{
131
  GROUPING_MPEG4_STACK_2D
132
  GF_Matrix2D mat;
133
  u8 is_identity;
134
  u8 is_null;
135
  u8 is_untransform;
136
} Transform2DStack;
137
138
static void traverse_transform(GF_Node *node, Transform2DStack *stack, GF_TraverseState *tr_state)
139
0
{
140
0
  if (stack->is_null) return;
141
142
  /*note we don't clear dirty flag, this is done in traversing*/
143
0
  if (stack->is_identity) {
144
0
    group_2d_traverse(node, (GroupingNode2D *)stack, tr_state);
145
0
  }
146
#ifndef GPAC_DISABLE_3D
147
  else if (tr_state->visual->type_3d) {
148
    GF_Matrix mx_bckup;
149
    gf_mx_copy(mx_bckup, tr_state->model_matrix);
150
151
    gf_mx_add_matrix_2d(&tr_state->model_matrix, &stack->mat);
152
    group_2d_traverse(node, (GroupingNode2D *)stack, tr_state);
153
    gf_mx_copy(tr_state->model_matrix, mx_bckup);
154
  }
155
#endif
156
0
  else {
157
0
    GF_Matrix2D bckup;
158
0
    gf_mx2d_copy(bckup, tr_state->transform);
159
0
    gf_mx2d_pre_multiply(&tr_state->transform, &stack->mat);
160
161
0
    group_2d_traverse(node, (GroupingNode2D *)stack, tr_state);
162
163
0
    gf_mx2d_copy(tr_state->transform, bckup);
164
0
  }
165
166
0
  if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
167
0
    gf_mx2d_apply_rect(&stack->mat, &tr_state->bounds);
168
0
  }
169
0
}
170
171
void TraverseUntransformEx(GF_Node *node, void *rs, GroupingNode2D *grp);
172
173
static void TraverseTransform2D(GF_Node *node, void *rs, Bool is_destroy)
174
0
{
175
0
  M_Transform2D *tr = (M_Transform2D *)node;
176
0
  Transform2DStack *ptr = (Transform2DStack *)gf_node_get_private(node);
177
0
  GF_TraverseState *tr_state;
178
179
0
  if (is_destroy) {
180
0
    gf_sc_check_focus_upon_destroy(node);
181
0
    group_2d_destroy(node, (GroupingNode2D*)ptr);
182
0
    gf_free(ptr);
183
0
    return;
184
0
  }
185
186
0
  tr_state = (GF_TraverseState *) rs;
187
188
0
  if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
189
0
    gf_mx2d_init(ptr->mat);
190
0
    ptr->is_identity = 1;
191
0
    if ((tr->scale.x != FIX_ONE) || (tr->scale.y != FIX_ONE)) {
192
0
      gf_mx2d_add_scale_at(&ptr->mat, tr->scale.x, tr->scale.y, 0, 0, tr->scaleOrientation);
193
0
      ptr->is_identity = 0;
194
0
    }
195
0
    if (tr->rotationAngle) {
196
0
      gf_mx2d_add_rotation(&ptr->mat, tr->center.x, tr->center.y, tr->rotationAngle);
197
0
      ptr->is_identity = 0;
198
0
    }
199
0
    if (tr->translation.x || tr->translation.y) {
200
0
      ptr->is_identity = 0;
201
0
      gf_mx2d_add_translation(&ptr->mat, tr->translation.x, tr->translation.y);
202
0
    }
203
0
    gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
204
0
    ptr->is_null = (!tr->scale.x || !tr->scale.y) ? 1 : 0;
205
0
  }
206
207
0
  if (ptr->is_untransform) {
208
0
    TraverseUntransformEx(node, tr_state, (GroupingNode2D *)ptr);
209
0
  } else {
210
0
    traverse_transform(node, ptr, tr_state);
211
0
  }
212
0
}
213
void transform2d_set_untransform(GF_Node *node)
214
0
{
215
0
  Transform2DStack *stack;
216
0
  if (gf_node_get_tag(node)!=TAG_MPEG4_Transform2D) return;
217
0
  stack = (Transform2DStack *)gf_node_get_private(node);
218
0
  stack->is_untransform = 1;
219
0
}
220
221
void compositor_init_transform2d(GF_Compositor *compositor, GF_Node *node)
222
0
{
223
0
  Transform2DStack *stack;
224
0
  GF_SAFEALLOC(stack, Transform2DStack);
225
0
  if (!stack) {
226
0
    GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate transform2d stack\n"));
227
0
    return;
228
0
  }
229
230
0
  gf_mx2d_init(stack->mat);
231
0
  stack->is_identity = 1;
232
0
  gf_node_set_private(node, stack);
233
0
  gf_node_set_callback_function(node, TraverseTransform2D);
234
0
}
235
236
void tr_mx2d_get_matrix(GF_Node *n, GF_Matrix2D *mat)
237
0
{
238
0
  M_TransformMatrix2D *tr = (M_TransformMatrix2D*)n;
239
0
  gf_mx2d_init(*mat);
240
0
  mat->m[0] = tr->mxx;
241
0
  mat->m[1] = tr->mxy;
242
0
  mat->m[2] = tr->tx;
243
0
  mat->m[3] = tr->myx;
244
0
  mat->m[4] = tr->myy;
245
0
  mat->m[5] = tr->ty;
246
0
}
247
248
249
/*TransformMatrix2D*/
250
static void TraverseTransformMatrix2D(GF_Node *node, void *rs, Bool is_destroy)
251
0
{
252
0
  Transform2DStack *ptr = (Transform2DStack *) gf_node_get_private(node);
253
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
254
255
0
  if (is_destroy) {
256
0
    gf_sc_check_focus_upon_destroy(node);
257
0
    group_2d_destroy(node, (GroupingNode2D*)ptr);
258
0
    gf_free(ptr);
259
0
    return;
260
0
  }
261
262
0
  if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
263
0
    M_TransformMatrix2D *tr = (M_TransformMatrix2D*)node;
264
0
    tr_mx2d_get_matrix(node, &ptr->mat);
265
0
    if ((tr->mxx==FIX_ONE) && (tr->mxy==0) && (tr->tx==0)
266
0
            && (tr->myx==0) && (tr->myy==FIX_ONE) && (tr->ty==0) )
267
0
      ptr->is_identity = 1;
268
0
    else
269
0
      ptr->is_identity = 0;
270
271
0
    ptr->is_null = ( (!ptr->mat.m[0] && !ptr->mat.m[1]) || (!ptr->mat.m[3] && !ptr->mat.m[4]) ) ? 1 : 0;
272
0
    gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
273
0
  }
274
0
  traverse_transform(node, ptr, tr_state);
275
0
}
276
277
278
void compositor_init_transformmatrix2d(GF_Compositor *compositor, GF_Node *node)
279
0
{
280
0
  Transform2DStack *stack;
281
0
  GF_SAFEALLOC(stack, Transform2DStack);
282
0
  if (!stack) return;
283
284
0
  gf_mx2d_init(stack->mat);
285
0
  gf_node_set_private(node, stack);
286
0
  gf_node_set_callback_function(node, TraverseTransformMatrix2D);
287
0
}
288
289
290
typedef struct
291
{
292
  GROUPING_MPEG4_STACK_2D
293
  GF_ColorMatrix cmat;
294
} ColorTransformStack;
295
296
/*ColorTransform*/
297
static void TraverseColorTransform(GF_Node *node, void *rs, Bool is_destroy)
298
0
{
299
0
  Bool c_changed;
300
0
  M_ColorTransform *tr = (M_ColorTransform *)node;
301
0
  ColorTransformStack *ptr = (ColorTransformStack  *)gf_node_get_private(node);
302
0
  GF_TraverseState *tr_state = (GF_TraverseState *) rs;
303
0
  Bool prev_inv;
304
305
0
  if (is_destroy) {
306
0
    gf_sc_check_focus_upon_destroy(node);
307
0
    group_2d_destroy(node, (GroupingNode2D*)ptr);
308
0
    gf_free(ptr);
309
0
    return;
310
0
  }
311
0
  if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
312
0
    group_2d_traverse(node, (GroupingNode2D *) ptr, tr_state);
313
0
    return;
314
0
  }
315
316
0
  prev_inv = tr_state->invalidate_all;
317
0
  c_changed = 0;
318
0
  if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
319
0
    gf_cmx_set(&ptr->cmat,
320
0
               tr->mrr , tr->mrg, tr->mrb, tr->mra, tr->tr,
321
0
               tr->mgr , tr->mgg, tr->mgb, tr->mga, tr->tg,
322
0
               tr->mbr, tr->mbg, tr->mbb, tr->mba, tr->tb,
323
0
               tr->mar, tr->mag, tr->mab, tr->maa, tr->ta);
324
0
    c_changed = 1;
325
0
    gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
326
0
  }
327
328
0
  if ((tr_state->traversing_mode==TRAVERSE_SORT)
329
0
          && !tr->maa && !tr->mar && !tr->mag && !tr->mab && !tr->ta)
330
0
    return;
331
332
  /*if modified redraw all nodes*/
333
0
  if (c_changed) tr_state->invalidate_all = 1;
334
335
  /*note we don't clear dirty flag, this is done in traversing*/
336
0
  if (ptr->cmat.identity) {
337
0
    group_2d_traverse(node, (GroupingNode2D *) ptr, tr_state);
338
0
  } else {
339
0
    GF_ColorMatrix gf_cmx_bck;
340
0
    gf_cmx_copy(&gf_cmx_bck, &tr_state->color_mat);
341
0
    gf_cmx_multiply(&tr_state->color_mat, &ptr->cmat);
342
343
344
0
    group_2d_traverse(node, (GroupingNode2D *) ptr, tr_state);
345
    /*restore traversing state*/
346
0
    gf_cmx_copy(&tr_state->color_mat, &gf_cmx_bck);
347
0
  }
348
0
  tr_state->invalidate_all = prev_inv;
349
0
}
350
351
void compositor_init_colortransform(GF_Compositor *compositor, GF_Node *node)
352
0
{
353
0
  ColorTransformStack *stack;
354
0
  GF_SAFEALLOC(stack, ColorTransformStack);
355
0
  if (!stack) return;
356
357
0
  gf_cmx_init(&stack->cmat);
358
0
  gf_node_set_private(node, stack);
359
0
  gf_node_set_callback_function(node, TraverseColorTransform);
360
0
}
361
362
363
struct og_pos
364
{
365
  Fixed priority;
366
  u32 position;
367
};
368
typedef struct
369
{
370
  GROUPING_MPEG4_STACK_2D
371
  u32 *positions;
372
} OrderedGroupStack;
373
374
375
static s32 compare_priority(const void* elem1, const void* elem2)
376
0
{
377
0
  struct og_pos *p1, *p2;
378
0
  p1 = (struct og_pos *)elem1;
379
0
  p2 = (struct og_pos *)elem2;
380
0
  if (p1->priority < p2->priority) return -1;
381
0
  if (p1->priority > p2->priority) return 1;
382
0
  return 0;
383
0
}
384
385
386
static void TraverseOrderedGroup(GF_Node *node, void *rs, Bool is_destroy)
387
0
{
388
0
  u32 i, count;
389
0
  struct og_pos *priorities;
390
0
  Bool invalidate_backup;
391
0
  OrderedGroupStack *stack = (OrderedGroupStack *) gf_node_get_private(node);
392
0
  M_OrderedGroup *og = (M_OrderedGroup *) node;
393
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
394
395
0
  if (is_destroy) {
396
0
    gf_sc_check_focus_upon_destroy(node);
397
0
    group_2d_destroy(node, (GroupingNode2D*)stack);
398
0
    if (stack->positions) gf_free(stack->positions);
399
0
    gf_free(stack);
400
0
    return;
401
0
  }
402
403
0
  if (!og->order.count || (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) ) {
404
0
    gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
405
0
    group_2d_traverse(node, (GroupingNode2D*)stack, tr_state);
406
0
    return;
407
0
  }
408
409
0
  invalidate_backup = tr_state->invalidate_all;
410
  /*check whether the OrderedGroup node has changed*/
411
0
  if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
412
0
    if (stack->positions) gf_free(stack->positions);
413
0
    count = gf_node_list_get_count(og->children);
414
0
    priorities = (struct og_pos*)gf_malloc(sizeof(struct og_pos)*count);
415
0
    for (i=0; i<count; i++) {
416
0
      priorities[i].position = i;
417
0
      priorities[i].priority = (i<og->order.count) ? og->order.vals[i] : 0;
418
0
    }
419
0
    qsort(priorities, count, sizeof(struct og_pos), compare_priority);
420
421
0
    stack->positions = (u32*)gf_malloc(sizeof(u32) * count);
422
0
    for (i=0; i<count; i++) stack->positions[i] = priorities[i].position;
423
0
    gf_free(priorities);
424
425
0
    tr_state->invalidate_all = 1;
426
0
    gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
427
0
  }
428
0
  group_2d_traverse_with_order(node, (GroupingNode2D*)stack, tr_state, stack->positions);
429
0
  tr_state->invalidate_all = invalidate_backup;
430
0
}
431
432
void compositor_init_orderedgroup(GF_Compositor *compositor, GF_Node *node)
433
0
{
434
0
  OrderedGroupStack *ptr;
435
0
  GF_SAFEALLOC(ptr, OrderedGroupStack);
436
0
  if (!ptr) return;
437
  
438
0
  gf_node_set_private(node, ptr);
439
0
  gf_node_set_callback_function(node, TraverseOrderedGroup);
440
0
}
441
442
#endif //!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR)
443