Coverage Report

Created: 2026-06-14 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openexr/src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp
Line
Count
Source
1
//
2
// SPDX-License-Identifier: BSD-3-Clause
3
// Copyright (c) Weta Digital, Ltd and Contributors to the OpenEXR Project.
4
//
5
6
#include "ImfCompositeDeepScanLine.h"
7
#include "IlmThreadPool.h"
8
#include "ImfChannelList.h"
9
#include "ImfDeepCompositing.h"
10
#include "ImfDeepFrameBuffer.h"
11
#include "ImfDeepScanLineInputFile.h"
12
#include "ImfDeepScanLineInputPart.h"
13
#include "ImfFrameBuffer.h"
14
#include "ImfPixelType.h"
15
16
#include <Iex.h>
17
#include <stddef.h>
18
#include <vector>
19
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
20
21
using ILMTHREAD_NAMESPACE::Task;
22
using ILMTHREAD_NAMESPACE::TaskGroup;
23
using ILMTHREAD_NAMESPACE::ThreadPool;
24
using IMATH_NAMESPACE::Box2i;
25
using std::string;
26
using std::vector;
27
28
struct CompositeDeepScanLine::Data
29
{
30
public:
31
    vector<DeepScanLineInputFile*> _file; // array of files
32
    vector<DeepScanLineInputPart*> _part; // array of parts
33
    FrameBuffer _outputFrameBuffer;       // output frame buffer provided
34
    bool _zback; // true if we are using zback (otherwise channel 1 = channel 0)
35
    vector<vector<float>>
36
        _channeldata; // pixel values, read from the input, one array per channel
37
    vector<int>      _sampleCounts; // total per-pixel sample counts,
38
    Box2i            _dataWindow;   // data window of combined inputs
39
    DeepCompositing* _comp;         // user-provided compositor
40
    vector<string>   _channels;     // names of channels that will be composited
41
    vector<int>
42
        _bufferMap; // entry _outputFrameBuffer[n].name() == _channels[ _bufferMap[n] ].name()
43
44
    void check_valid (
45
        const Header&
46
            header); // check newly added part/file is OK; on first good call, set _zback/_dataWindow
47
48
    //
49
    // set up the given deep frame buffer to contain the required channels
50
    // resize counts and pointers to the width of _dataWindow
51
    // zero-out all counts, since the datawindow may be smaller than/not include this part
52
    //
53
54
    void handleDeepFrameBuffer (
55
        DeepFrameBuffer&      buf,
56
        vector<unsigned int>& counts, //per-pixel counts
57
        vector<vector<float*>>&
58
                      pointers, //per-channel-per-pixel pointers to data
59
        const Header& header,
60
        int           start,
61
        int           end);
62
63
    Data ();
64
};
65
66
635
CompositeDeepScanLine::Data::Data () : _zback (false), _comp (NULL)
67
635
{}
68
69
635
CompositeDeepScanLine::CompositeDeepScanLine () : _Data (new Data)
70
635
{}
71
72
CompositeDeepScanLine::~CompositeDeepScanLine ()
73
635
{
74
635
    delete _Data;
75
635
}
76
77
void
78
CompositeDeepScanLine::addSource (DeepScanLineInputPart* part)
79
0
{
80
0
    _Data->check_valid (part->header ());
81
0
    _Data->_part.push_back (part);
82
0
}
83
84
void
85
CompositeDeepScanLine::addSource (DeepScanLineInputFile* file)
86
635
{
87
635
    _Data->check_valid (file->header ());
88
635
    _Data->_file.push_back (file);
89
635
}
90
91
int
92
CompositeDeepScanLine::sources () const
93
0
{
94
0
    return int (_Data->_part.size ()) + int (_Data->_file.size ());
95
0
}
96
97
void
98
CompositeDeepScanLine::Data::check_valid (const Header& header)
99
635
{
100
101
635
    bool has_z     = false;
102
635
    bool has_alpha = false;
103
    // check good channel names
104
635
    for (ChannelList::ConstIterator i = header.channels ().begin ();
105
8.36k
         i != header.channels ().end ();
106
7.72k
         ++i)
107
7.72k
    {
108
7.72k
        std::string n (i.name ());
109
7.72k
        if (n == "ZBack") { _zback = true; }
110
7.61k
        else if (n == "Z") { has_z = true; }
111
7.02k
        else if (n == "A") { has_alpha = true; }
112
7.72k
    }
113
114
635
    if (!has_z)
115
45
    {
116
45
        throw IEX_NAMESPACE::ArgExc (
117
45
            "Deep data provided to CompositeDeepScanLine is missing a Z channel");
118
45
    }
119
120
590
    if (!has_alpha)
121
9
    {
122
9
        throw IEX_NAMESPACE::ArgExc (
123
9
            "Deep data provided to CompositeDeepScanLine is missing an alpha channel");
124
9
    }
125
126
581
    if (_part.size () == 0 && _file.size () == 0)
127
581
    {
128
        // first in - update and return
129
130
581
        _dataWindow = header.dataWindow ();
131
132
581
        return;
133
581
    }
134
135
0
    const Header* const match_header = _part.size () > 0 ? &_part[0]->header ()
136
0
                                                         : &_file[0]->header ();
137
138
    // check the sizes match
139
0
    if (match_header->displayWindow () != header.displayWindow ())
140
0
    {
141
0
        throw IEX_NAMESPACE::ArgExc (
142
0
            "Deep data provided to CompositeDeepScanLine has a different displayWindow to previously provided data");
143
0
    }
144
145
0
    _dataWindow.extendBy (header.dataWindow ());
146
0
}
147
void
148
CompositeDeepScanLine::Data::handleDeepFrameBuffer (
149
    DeepFrameBuffer&             buf,
150
    std::vector<unsigned int>&   counts,
151
    vector<std::vector<float*>>& pointers,
152
    const Header&                header,
153
    int                          start,
154
    int                          end)
155
1.22k
{
156
1.22k
    ptrdiff_t width      = _dataWindow.size ().x + 1;
157
1.22k
    size_t    pixelcount = width * (end - start + 1);
158
1.22k
    pointers.resize (_channels.size ());
159
1.22k
    counts.resize (pixelcount);
160
1.22k
    buf.insertSampleCountSlice (Slice (
161
1.22k
        OPENEXR_IMF_INTERNAL_NAMESPACE::UINT,
162
1.22k
        (char*) (&counts[0] - _dataWindow.min.x - start * width),
163
1.22k
        sizeof (unsigned int),
164
1.22k
        sizeof (unsigned int) * width));
165
166
1.22k
    pointers[0].resize (pixelcount);
167
1.22k
    buf.insert (
168
1.22k
        "Z",
169
1.22k
        DeepSlice (
170
1.22k
            OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT,
171
1.22k
            (char*) (&pointers[0][0] - _dataWindow.min.x - start * width),
172
1.22k
            sizeof (float*),
173
1.22k
            sizeof (float*) * width,
174
1.22k
            sizeof (float)));
175
176
1.22k
    if (_zback)
177
574
    {
178
574
        pointers[1].resize (pixelcount);
179
574
        buf.insert (
180
574
            "ZBack",
181
574
            DeepSlice (
182
574
                OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT,
183
574
                (char*) (&pointers[1][0] - _dataWindow.min.x - start * width),
184
574
                sizeof (float*),
185
574
                sizeof (float*) * width,
186
574
                sizeof (float)));
187
574
    }
188
189
1.22k
    pointers[2].resize (pixelcount);
190
1.22k
    buf.insert (
191
1.22k
        "A",
192
1.22k
        DeepSlice (
193
1.22k
            OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT,
194
1.22k
            (char*) (&pointers[2][0] - _dataWindow.min.x - start * width),
195
1.22k
            sizeof (float*),
196
1.22k
            sizeof (float*) * width,
197
1.22k
            sizeof (float)));
198
199
1.22k
    size_t i = 0;
200
1.22k
    for (FrameBuffer::ConstIterator qt = _outputFrameBuffer.begin ();
201
4.00k
         qt != _outputFrameBuffer.end ();
202
2.77k
         qt++)
203
2.77k
    {
204
2.77k
        int channel_in_source = _bufferMap[i];
205
2.77k
        if (channel_in_source > 2)
206
2.32k
        {
207
            // not dealt with yet (0,1,2 previously inserted)
208
2.32k
            pointers[channel_in_source].resize (pixelcount);
209
2.32k
            buf.insert (
210
2.32k
                qt.name (),
211
2.32k
                DeepSlice (
212
2.32k
                    OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT,
213
2.32k
                    (char*) (&pointers[channel_in_source][0] -
214
2.32k
                             _dataWindow.min.x - start * width),
215
2.32k
                    sizeof (float*),
216
2.32k
                    sizeof (float*) * width,
217
2.32k
                    sizeof (float)));
218
2.32k
        }
219
220
2.77k
        i++;
221
2.77k
    }
222
1.22k
}
223
224
void
225
CompositeDeepScanLine::setCompositing (DeepCompositing* c)
226
0
{
227
0
    _Data->_comp = c;
228
0
}
229
230
const IMATH_NAMESPACE::Box2i&
231
CompositeDeepScanLine::dataWindow () const
232
0
{
233
0
    return _Data->_dataWindow;
234
0
}
235
236
void
237
CompositeDeepScanLine::setFrameBuffer (const FrameBuffer& fr)
238
1.15k
{
239
240
    //
241
    // count channels; build map between channels in frame buffer
242
    // and channels in internal buffers
243
    //
244
245
1.15k
    _Data->_channels.resize (3);
246
1.15k
    _Data->_channels[0] = "Z";
247
1.15k
    _Data->_channels[1] = _Data->_zback ? "ZBack" : "Z";
248
1.15k
    _Data->_channels[2] = "A";
249
1.15k
    _Data->_bufferMap.resize (0);
250
251
3.29k
    for (FrameBuffer::ConstIterator q = fr.begin (); q != fr.end (); q++)
252
2.13k
    {
253
254
        //
255
        // Frame buffer must have xSampling and ySampling set to 1
256
        // (Sampling in FrameBuffers must match sampling in file,
257
        //  and Header::sanityCheck enforces sampling in deep files is 1)
258
        //
259
260
2.13k
        if (q.slice ().xSampling != 1 || q.slice ().ySampling != 1)
261
0
        {
262
0
            THROW (
263
0
                IEX_NAMESPACE::ArgExc,
264
0
                "X and/or y subsampling factors "
265
0
                "of \""
266
0
                    << q.name ()
267
0
                    << "\" channel in framebuffer "
268
0
                       "are not 1");
269
0
        }
270
271
2.13k
        string name (q.name ());
272
2.13k
        if (name == "ZBack") { _Data->_bufferMap.push_back (1); }
273
2.13k
        else if (name == "Z") { _Data->_bufferMap.push_back (0); }
274
2.13k
        else if (name == "A") { _Data->_bufferMap.push_back (2); }
275
2.08k
        else
276
2.08k
        {
277
2.08k
            _Data->_bufferMap.push_back (
278
2.08k
                static_cast<int> (_Data->_channels.size ()));
279
2.08k
            _Data->_channels.push_back (name);
280
2.08k
        }
281
2.13k
    }
282
283
1.15k
    _Data->_outputFrameBuffer = fr;
284
1.15k
}
285
286
namespace
287
{
288
289
class LineCompositeTask : public Task
290
{
291
public:
292
    LineCompositeTask (
293
        TaskGroup*                      group,
294
        CompositeDeepScanLine::Data*    data,
295
        int                             y,
296
        int                             start,
297
        vector<const char*>*            names,
298
        vector<vector<vector<float*>>>* pointers,
299
        vector<unsigned int>*           total_sizes,
300
        vector<unsigned int>*           num_sources)
301
671
        : Task (group)
302
671
        , _Data (data)
303
671
        , _y (y)
304
671
        , _start (start)
305
671
        , _names (names)
306
671
        , _pointers (pointers)
307
671
        , _total_sizes (total_sizes)
308
671
        , _num_sources (num_sources)
309
671
    {}
310
311
0
    virtual ~LineCompositeTask () {}
312
313
    virtual void                    execute ();
314
    CompositeDeepScanLine::Data*    _Data;
315
    int                             _y;
316
    int                             _start;
317
    vector<const char*>*            _names;
318
    vector<vector<vector<float*>>>* _pointers;
319
    vector<unsigned int>*           _total_sizes;
320
    vector<unsigned int>*           _num_sources;
321
};
322
323
void
324
composite_line (
325
    int                                   y,
326
    int                                   start,
327
    CompositeDeepScanLine::Data*          _Data,
328
    vector<const char*>&                  names,
329
    const vector<vector<vector<float*>>>& pointers,
330
    const vector<unsigned int>&           total_sizes,
331
    const vector<unsigned int>&           num_sources)
332
671
{
333
671
    vector<float> output_pixel (names.size ()); //the pixel we'll output to
334
671
    vector<const float*> inputs (names.size ());
335
671
    DeepCompositing      d; // fallback compositing engine
336
671
    DeepCompositing*     comp = _Data->_comp ? _Data->_comp : &d;
337
338
671
    int pixel =
339
671
        (y - start) * (_Data->_dataWindow.max.x + 1 - _Data->_dataWindow.min.x);
340
341
360k
    for (int x = _Data->_dataWindow.min.x; x <= _Data->_dataWindow.max.x; x++)
342
359k
    {
343
        // set inputs[] to point to the first sample of the first part of each channel
344
        // if there's a zback, set all channel independently...
345
346
359k
        if (_Data->_zback)
347
358k
        {
348
349
1.43M
            for (size_t channel = 0; channel < names.size (); channel++)
350
1.07M
            {
351
1.07M
                inputs[channel] = pointers[0][channel][pixel];
352
1.07M
            }
353
358k
        }
354
492
        else
355
492
        {
356
357
            // otherwise, set 0 and 1 to point to Z
358
359
492
            inputs[0] = pointers[0][0][pixel];
360
492
            inputs[1] = pointers[0][0][pixel];
361
1.26k
            for (size_t channel = 2; channel < names.size (); channel++)
362
769
            {
363
769
                inputs[channel] = pointers[0][channel][pixel];
364
769
            }
365
492
        }
366
359k
        comp->composite_pixel (
367
359k
            &output_pixel[0],
368
359k
            &inputs[0],
369
359k
            &names[0],
370
359k
            static_cast<int> (names.size ()),
371
359k
            total_sizes[pixel],
372
359k
            num_sources[pixel]);
373
374
359k
        size_t channel_number = 0;
375
376
        //
377
        // write out composited value into internal frame buffer
378
        //
379
359k
        for (FrameBuffer::Iterator it = _Data->_outputFrameBuffer.begin ();
380
719k
             it != _Data->_outputFrameBuffer.end ();
381
359k
             it++)
382
359k
        {
383
384
359k
            float value = output_pixel
385
359k
                [_Data->_bufferMap[channel_number]]; // value to write
386
359k
            intptr_t base = reinterpret_cast<intptr_t> (it.slice ().base);
387
388
            // cast to half float if necessary
389
359k
            if (it.slice ().type == OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT)
390
0
            {
391
0
                float* ptr = reinterpret_cast<float*> (
392
0
                    base + y * it.slice ().yStride + x * it.slice ().xStride);
393
0
                *ptr = value;
394
0
            }
395
359k
            else if (it.slice ().type == HALF)
396
359k
            {
397
359k
                half* ptr = reinterpret_cast<half*> (
398
359k
                    base + y * it.slice ().yStride + x * it.slice ().xStride);
399
359k
                *ptr = half (value);
400
359k
            }
401
402
359k
            channel_number++;
403
359k
        }
404
405
359k
        pixel++;
406
407
359k
    } // next pixel on row
408
671
}
409
410
void
411
LineCompositeTask::execute ()
412
671
{
413
671
    composite_line (
414
671
        _y, _start, _Data, *_names, *_pointers, *_total_sizes, *_num_sources);
415
671
}
416
417
} // namespace
418
419
namespace
420
{
421
int64_t maximumSampleCount = 0;
422
}
423
424
void
425
CompositeDeepScanLine::setMaximumSampleCount (int64_t c)
426
0
{
427
0
    maximumSampleCount = c;
428
0
}
429
430
int64_t
431
CompositeDeepScanLine::getMaximumSampleCount ()
432
0
{
433
0
    return maximumSampleCount;
434
0
}
435
436
void
437
CompositeDeepScanLine::readPixels (int start, int end)
438
1.22k
{
439
1.22k
    size_t parts =
440
1.22k
        _Data->_file.size () + _Data->_part.size (); // total of files+parts
441
442
1.22k
    vector<DeepFrameBuffer>      framebuffers (parts);
443
1.22k
    vector<vector<unsigned int>> counts (parts);
444
445
    //
446
    // for each part, a pointer to an array of channels
447
    //
448
1.22k
    vector<vector<vector<float*>>> pointers (parts);
449
1.22k
    vector<const Header*>          headers (parts);
450
451
1.22k
    {
452
1.22k
        size_t i;
453
2.45k
        for (i = 0; i < _Data->_file.size (); i++)
454
1.22k
        {
455
1.22k
            headers[i] = &_Data->_file[i]->header ();
456
1.22k
        }
457
458
1.22k
        for (size_t j = 0; j < _Data->_part.size (); j++)
459
0
        {
460
0
            headers[i + j] = &_Data->_part[j]->header ();
461
0
        }
462
1.22k
    }
463
464
2.45k
    for (size_t i = 0; i < parts; i++)
465
1.22k
    {
466
1.22k
        _Data->handleDeepFrameBuffer (
467
1.22k
            framebuffers[i], counts[i], pointers[i], *headers[i], start, end);
468
1.22k
    }
469
470
    //
471
    // set frame buffers and read scanlines from all parts
472
    // TODO what happens if SCANLINE not in data window?
473
    //
474
475
1.22k
    {
476
1.22k
        size_t i = 0;
477
2.45k
        for (i = 0; i < _Data->_file.size (); i++)
478
1.22k
        {
479
1.22k
            _Data->_file[i]->setFrameBuffer (framebuffers[i]);
480
1.22k
            _Data->_file[i]->readPixelSampleCounts (start, end);
481
1.22k
        }
482
1.22k
        for (size_t j = 0; j < _Data->_part.size (); j++)
483
0
        {
484
0
            _Data->_part[j]->setFrameBuffer (framebuffers[i + j]);
485
0
            _Data->_part[j]->readPixelSampleCounts (start, end);
486
0
        }
487
1.22k
    }
488
489
    //
490
    //  total width
491
    //
492
493
1.22k
    size_t               total_width  = _Data->_dataWindow.size ().x + 1;
494
1.22k
    size_t               total_pixels = total_width * (end - start + 1);
495
1.22k
    vector<unsigned int> total_sizes (total_pixels);
496
1.22k
    vector<unsigned int> num_sources (
497
1.22k
        total_pixels); //number of parts with non-zero sample count
498
499
1.22k
    int64_t overall_sample_count =
500
1.22k
        0; // sum of all samples in all images between start and end
501
502
    //
503
    // accumulate pixel counts
504
    //
505
360k
    for (size_t ptr = 0; ptr < total_pixels; ptr++)
506
359k
    {
507
359k
        total_sizes[ptr] = 0;
508
359k
        num_sources[ptr] = 0;
509
718k
        for (size_t j = 0; j < parts; j++)
510
359k
        {
511
359k
            if (total_sizes[ptr] > std::numeric_limits<unsigned int>::max() - counts[j][ptr])
512
0
                throw IEX_NAMESPACE::ArgExc (
513
0
                    "Cannot composite scanline: pixel cannot have more than UINT_MAX samples");
514
                
515
359k
            total_sizes[ptr] += counts[j][ptr];
516
359k
            if (counts[j][ptr] > 0) num_sources[ptr]++;
517
359k
        }
518
359k
        overall_sample_count += total_sizes[ptr];
519
359k
    }
520
521
1.22k
    if (maximumSampleCount > 0 && overall_sample_count > maximumSampleCount)
522
0
    {
523
0
        throw IEX_NAMESPACE::ArgExc (
524
0
            "Cannot composite scanline: total sample count on scanline exceeds "
525
0
            "limit set by CompositeDeepScanLine::setMaximumSampleCount()");
526
0
    }
527
528
    //
529
    // allocate arrays for pixel data
530
    // samples array accessed as in pixels[channel][sample]
531
    //
532
533
1.22k
    vector<vector<float>> samples (_Data->_channels.size ());
534
535
3.56k
    for (size_t channel = 0; channel < _Data->_channels.size (); channel++)
536
2.34k
    {
537
2.34k
        if (channel != 1 || _Data->_zback)
538
2.14k
        {
539
2.14k
            samples[channel].resize (overall_sample_count);
540
2.14k
        }
541
2.34k
    }
542
543
3.56k
    for (size_t channel = 0; channel < samples.size (); channel++)
544
2.34k
    {
545
546
2.34k
        if (channel != 1 || _Data->_zback)
547
2.14k
        {
548
549
2.14k
            samples[channel].resize (overall_sample_count);
550
551
            //
552
            // allocate pointers for channel data
553
            //
554
555
2.14k
            int64_t offset = 0;
556
557
1.08M
            for (size_t pixel = 0; pixel < total_pixels; pixel++)
558
1.07M
            {
559
1.07M
                for (size_t part = 0;
560
1.08M
                     part < parts && offset < overall_sample_count;
561
1.07M
                     part++)
562
3.31k
                {
563
3.31k
                    pointers[part][channel][pixel] = &samples[channel][offset];
564
3.31k
                    offset += counts[part][pixel];
565
3.31k
                }
566
1.07M
            }
567
2.14k
        }
568
2.34k
    }
569
570
    //
571
    // read data
572
    //
573
574
1.89k
    for (size_t i = 0; i < _Data->_file.size (); i++)
575
673
    {
576
673
        _Data->_file[i]->readPixels (start, end);
577
673
    }
578
1.22k
    for (size_t j = 0; j < _Data->_part.size (); j++)
579
0
    {
580
0
        _Data->_part[j]->readPixels (start, end);
581
0
    }
582
583
    //
584
    // composite pixels and write back to framebuffer
585
    //
586
587
    // turn vector of strings into array of char *
588
    // and make sure 'ZBack' channel is correct
589
1.22k
    vector<const char*> names (_Data->_channels.size ());
590
3.55k
    for (size_t i = 0; i < names.size (); i++)
591
2.33k
    {
592
2.33k
        names[i] = _Data->_channels[i].c_str ();
593
2.33k
    }
594
595
1.22k
    if (!_Data->_zback)
596
192
        names[1] = names[0]; // no zback channel, so make it point to z
597
598
1.22k
    TaskGroup g;
599
1.89k
    for (int y = start; y <= end; y++)
600
671
    {
601
671
        ThreadPool::addGlobalTask (new LineCompositeTask (
602
671
            &g,
603
671
            _Data,
604
671
            y,
605
671
            start,
606
671
            &names,
607
671
            &pointers,
608
671
            &total_sizes,
609
671
            &num_sources));
610
671
    } //next row
611
1.22k
}
612
613
const FrameBuffer&
614
CompositeDeepScanLine::frameBuffer () const
615
3
{
616
3
    return _Data->_outputFrameBuffer;
617
3
}
618
619
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT