Coverage Report

Created: 2025-08-09 06:51

/src/openexr/src/lib/OpenEXR/ImfOutputFile.cpp
Line
Count
Source (jump to first uncovered line)
1
//
2
// SPDX-License-Identifier: BSD-3-Clause
3
// Copyright (c) Contributors to the OpenEXR Project.
4
//
5
6
//-----------------------------------------------------------------------------
7
//
8
//  class OutputFile
9
//
10
//-----------------------------------------------------------------------------
11
12
#include "ImfOutputFile.h"
13
#include "ImfChannelList.h"
14
#include "ImfHeader.h"
15
#include "ImfInputFile.h"
16
17
#include "Iex.h"
18
#include "IlmThreadPool.h"
19
#include "IlmThreadSemaphore.h"
20
#include "ImfArray.h"
21
#include "ImfCompressor.h"
22
#include "ImfFrameBuffer.h"
23
#include "ImfInputPart.h"
24
#include "ImfMisc.h"
25
#include "ImfOutputStreamMutex.h"
26
#include "ImfPartType.h"
27
#include "ImfPreviewImageAttribute.h"
28
#include "ImfStdIO.h"
29
#include "ImfXdr.h"
30
#include <ImathBox.h>
31
#include <ImathFun.h>
32
33
#include "ImfOutputPartData.h"
34
35
#include <algorithm>
36
#include <assert.h>
37
#include <fstream>
38
#include <string>
39
#include <vector>
40
41
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
42
43
using ILMTHREAD_NAMESPACE::Semaphore;
44
using ILMTHREAD_NAMESPACE::Task;
45
using ILMTHREAD_NAMESPACE::TaskGroup;
46
using ILMTHREAD_NAMESPACE::ThreadPool;
47
using IMATH_NAMESPACE::Box2i;
48
using IMATH_NAMESPACE::divp;
49
using IMATH_NAMESPACE::modp;
50
using std::max;
51
using std::min;
52
using std::string;
53
using std::vector;
54
55
namespace
56
{
57
58
struct OutSliceInfo
59
{
60
    PixelType   type;
61
    const char* base;
62
    size_t      xStride;
63
    size_t      yStride;
64
    int         xSampling;
65
    int         ySampling;
66
    bool        zero;
67
68
    OutSliceInfo (
69
        PixelType   type      = HALF,
70
        const char* base      = 0,
71
        size_t      xStride   = 0,
72
        size_t      yStride   = 0,
73
        int         xSampling = 1,
74
        int         ySampling = 1,
75
        bool        zero      = false);
76
};
77
78
OutSliceInfo::OutSliceInfo (
79
    PixelType t, const char* b, size_t xs, size_t ys, int xsm, int ysm, bool z)
80
0
    : type (t)
81
0
    , base (b)
82
0
    , xStride (xs)
83
0
    , yStride (ys)
84
0
    , xSampling (xsm)
85
0
    , ySampling (ysm)
86
0
    , zero (z)
87
0
{
88
    // empty
89
0
}
90
91
struct LineBuffer
92
{
93
    Array<char> buffer;
94
    const char* dataPtr;
95
    int         dataSize;
96
    char*       endOfLineBufferData;
97
    int         minY;
98
    int         maxY;
99
    int         scanLineMin;
100
    int         scanLineMax;
101
    Compressor* compressor;
102
    bool        partiallyFull; // has incomplete data
103
    bool        hasException;
104
    string      exception;
105
106
    LineBuffer (Compressor* comp);
107
    ~LineBuffer ();
108
109
0
    void wait () { _sem.wait (); }
110
0
    void post () { _sem.post (); }
111
112
private:
113
    Semaphore _sem;
114
};
115
116
LineBuffer::LineBuffer (Compressor* comp)
117
0
    : dataPtr (0)
118
0
    , dataSize (0)
119
0
    , compressor (comp)
120
0
    , partiallyFull (false)
121
0
    , hasException (false)
122
0
    , exception ()
123
0
    , _sem (1)
124
0
{
125
    // empty
126
0
}
127
128
LineBuffer::~LineBuffer ()
129
0
{
130
0
    delete compressor;
131
0
}
132
133
} // namespace
134
135
struct OutputFile::Data
136
{
137
    Header      header;    // the image header
138
    bool        multiPart; // is the file multipart?
139
    int         version;   // version attribute \todo NOT BEING WRITTEN PROPERLY
140
    uint64_t    previewPosition;              // file position for preview
141
    FrameBuffer frameBuffer;                  // framebuffer to write into
142
    int         currentScanLine;              // next scanline to be written
143
    int         missingScanLines;             // number of lines to write
144
    LineOrder   lineOrder;                    // the file's lineorder
145
    int         minX;                         // data window's min x coord
146
    int         maxX;                         // data window's max x coord
147
    int         minY;                         // data window's min y coord
148
    int         maxY;                         // data window's max x coord
149
    vector<uint64_t> lineOffsets;             // stores offsets in file for
150
                                              // each scanline
151
    vector<size_t> bytesPerLine;              // combined size of a line over
152
                                              // all channels
153
    vector<size_t> offsetInLineBuffer;        // offset for each scanline in
154
                                              // its linebuffer
155
    Compressor::Format   format;              // compressor's data format
156
    vector<OutSliceInfo> slices;              // info about channels in file
157
    uint64_t             lineOffsetsPosition; // file position for line
158
                                              // offset table
159
160
    vector<LineBuffer*> lineBuffers;   // each holds one line buffer
161
    int                 linesInBuffer; // number of scanlines each
162
                                       // buffer holds
163
    size_t lineBufferSize;             // size of the line buffer
164
165
    int                partNumber; // the output part number
166
    OutputStreamMutex* _streamData;
167
    bool               _deleteStream;
168
    Data (int numThreads);
169
    ~Data ();
170
171
    Data (const Data& other)            = delete;
172
    Data& operator= (const Data& other) = delete;
173
    Data (Data&& other)                 = delete;
174
    Data& operator= (Data&& other)      = delete;
175
176
    inline LineBuffer* getLineBuffer (int number); // hash function from line
177
                                                   // buffer indices into our
178
                                                   // vector of line buffers
179
};
180
181
OutputFile::Data::Data (int numThreads)
182
0
    : lineOffsetsPosition (0)
183
0
    , partNumber (-1)
184
0
    , _streamData (0)
185
0
    , _deleteStream (false)
186
0
{
187
    //
188
    // We need at least one lineBuffer, but if threading is used,
189
    // to keep n threads busy we need 2*n lineBuffers.
190
    //
191
192
0
    lineBuffers.resize (max (1, 2 * numThreads));
193
0
}
194
195
OutputFile::Data::~Data ()
196
0
{
197
0
    for (size_t i = 0; i < lineBuffers.size (); i++)
198
0
        delete lineBuffers[i];
199
0
}
200
201
LineBuffer*
202
OutputFile::Data::getLineBuffer (int number)
203
0
{
204
0
    return lineBuffers[number % lineBuffers.size ()];
205
0
}
206
207
namespace
208
{
209
210
uint64_t
211
writeLineOffsets (
212
    OPENEXR_IMF_INTERNAL_NAMESPACE::OStream& os,
213
    const vector<uint64_t>&                  lineOffsets)
214
0
{
215
0
    uint64_t pos = os.tellp ();
216
217
0
    if (pos == static_cast<uint64_t> (-1))
218
0
        IEX_NAMESPACE::throwErrnoExc (
219
0
            "Cannot determine current file position (%T).");
220
221
0
    for (unsigned int i = 0; i < lineOffsets.size (); i++)
222
0
        Xdr::write<StreamIO> (os, lineOffsets[i]);
223
224
0
    return pos;
225
0
}
226
227
void
228
writePixelData (
229
    OutputStreamMutex* filedata,
230
    OutputFile::Data*  partdata,
231
    int                lineBufferMinY,
232
    const char         pixelData[],
233
    int                pixelDataSize)
234
0
{
235
    //
236
    // Store a block of pixel data in the output file, and try
237
    // to keep track of the current writing position the file
238
    // without calling tellp() (tellp() can be fairly expensive).
239
    //
240
241
0
    uint64_t currentPosition  = filedata->currentPosition;
242
0
    filedata->currentPosition = 0;
243
244
0
    if (currentPosition == 0) currentPosition = filedata->os->tellp ();
245
246
0
    partdata->lineOffsets
247
0
        [(partdata->currentScanLine - partdata->minY) /
248
0
         partdata->linesInBuffer] = currentPosition;
249
250
#ifdef DEBUG
251
252
    assert (filedata->os->tellp () == currentPosition);
253
254
#endif
255
256
0
    if (partdata->multiPart)
257
0
    {
258
0
        OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write<
259
0
            OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (
260
0
            *filedata->os, partdata->partNumber);
261
0
    }
262
263
0
    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write<
264
0
        OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (
265
0
        *filedata->os, lineBufferMinY);
266
0
    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write<
267
0
        OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (
268
0
        *filedata->os, pixelDataSize);
269
0
    filedata->os->write (pixelData, pixelDataSize);
270
271
0
    filedata->currentPosition =
272
0
        currentPosition + Xdr::size<int> () + Xdr::size<int> () + pixelDataSize;
273
274
0
    if (partdata->multiPart) { filedata->currentPosition += Xdr::size<int> (); }
275
0
}
276
277
inline void
278
writePixelData (
279
    OutputStreamMutex* filedata,
280
    OutputFile::Data*  partdata,
281
    const LineBuffer*  lineBuffer)
282
0
{
283
0
    writePixelData (
284
0
        filedata,
285
0
        partdata,
286
0
        lineBuffer->minY,
287
0
        lineBuffer->dataPtr,
288
0
        lineBuffer->dataSize);
289
0
}
290
291
void
292
convertToXdr (
293
    OutputFile::Data* ofd,
294
    Array<char>&      lineBuffer,
295
    int               lineBufferMinY,
296
    int               lineBufferMaxY,
297
    int               inSize)
298
0
{
299
    //
300
    // Convert the contents of a lineBuffer from the machine's native
301
    // representation to Xdr format.  This function is called by
302
    // CompressLineBuffer::execute(), below, if the compressor wanted
303
    // its input pixel data in the machine's native format, but then
304
    // failed to compress the data (most compressors will expand rather
305
    // than compress random input data).
306
    //
307
    // Note that this routine assumes that the machine's native
308
    // representation of the pixel data has the same size as the
309
    // Xdr representation.  This makes it possible to convert the
310
    // pixel data in place, without an intermediate temporary buffer.
311
    //
312
313
    //
314
    // Iterate over all scanlines in the lineBuffer to convert.
315
    //
316
317
0
    char* writePtr = &lineBuffer[0];
318
0
    for (int y = lineBufferMinY; y <= lineBufferMaxY; y++)
319
0
    {
320
        //
321
        // Set these to point to the start of line y.
322
        // We will write to writePtr from readPtr.
323
        //
324
325
0
        const char* readPtr = writePtr;
326
327
        //
328
        // Iterate over all slices in the file.
329
        //
330
331
0
        for (unsigned int i = 0; i < ofd->slices.size (); ++i)
332
0
        {
333
            //
334
            // Test if scan line y of this channel is
335
            // contains any data (the scan line contains
336
            // data only if y % ySampling == 0).
337
            //
338
339
0
            const OutSliceInfo& slice = ofd->slices[i];
340
341
0
            if (modp (y, slice.ySampling) != 0) continue;
342
343
            //
344
            // Find the number of sampled pixels, dMaxX-dMinX+1, for
345
            // slice i in scan line y (i.e. pixels within the data window
346
            // for which x % xSampling == 0).
347
            //
348
349
0
            int dMinX = divp (ofd->minX, slice.xSampling);
350
0
            int dMaxX = divp (ofd->maxX, slice.xSampling);
351
352
            //
353
            // Convert the samples in place.
354
            //
355
356
0
            convertInPlace (writePtr, readPtr, slice.type, dMaxX - dMinX + 1);
357
0
        }
358
0
    }
359
0
}
360
361
//
362
// A LineBufferTask encapsulates the task of copying a set of scanlines
363
// from the user's frame buffer into a LineBuffer object, compressing
364
// the data if necessary.
365
//
366
367
class LineBufferTask : public Task
368
{
369
public:
370
    LineBufferTask (
371
        TaskGroup*        group,
372
        OutputFile::Data* ofd,
373
        int               number,
374
        int               scanLineMin,
375
        int               scanLineMax);
376
377
    virtual ~LineBufferTask ();
378
379
    virtual void execute ();
380
381
private:
382
    OutputFile::Data* _ofd;
383
    LineBuffer*       _lineBuffer;
384
};
385
386
LineBufferTask::LineBufferTask (
387
    TaskGroup*        group,
388
    OutputFile::Data* ofd,
389
    int               number,
390
    int               scanLineMin,
391
    int               scanLineMax)
392
0
    : Task (group), _ofd (ofd), _lineBuffer (_ofd->getLineBuffer (number))
393
0
{
394
    //
395
    // Wait for the lineBuffer to become available
396
    //
397
398
0
    _lineBuffer->wait ();
399
400
    //
401
    // Initialize the lineBuffer data if necessary
402
    //
403
404
0
    if (!_lineBuffer->partiallyFull)
405
0
    {
406
0
        _lineBuffer->endOfLineBufferData = _lineBuffer->buffer;
407
408
0
        _lineBuffer->minY = _ofd->minY + number * _ofd->linesInBuffer;
409
410
0
        _lineBuffer->maxY =
411
0
            min (_lineBuffer->minY + _ofd->linesInBuffer - 1, _ofd->maxY);
412
413
0
        _lineBuffer->partiallyFull = true;
414
0
    }
415
416
0
    _lineBuffer->scanLineMin = max (_lineBuffer->minY, scanLineMin);
417
0
    _lineBuffer->scanLineMax = min (_lineBuffer->maxY, scanLineMax);
418
0
}
419
420
LineBufferTask::~LineBufferTask ()
421
0
{
422
    //
423
    // Signal that the line buffer is now free
424
    //
425
426
0
    _lineBuffer->post ();
427
0
}
428
429
void
430
LineBufferTask::execute ()
431
0
{
432
0
    try
433
0
    {
434
        //
435
        // First copy the pixel data from the
436
        // frame buffer into the line buffer
437
        //
438
439
0
        int yStart, yStop, dy;
440
441
0
        if (_ofd->lineOrder == INCREASING_Y)
442
0
        {
443
0
            yStart = _lineBuffer->scanLineMin;
444
0
            yStop  = _lineBuffer->scanLineMax + 1;
445
0
            dy     = 1;
446
0
        }
447
0
        else
448
0
        {
449
0
            yStart = _lineBuffer->scanLineMax;
450
0
            yStop  = _lineBuffer->scanLineMin - 1;
451
0
            dy     = -1;
452
0
        }
453
454
0
        int y;
455
456
0
        for (y = yStart; y != yStop; y += dy)
457
0
        {
458
            //
459
            // Gather one scan line's worth of pixel data and store
460
            // them in _ofd->lineBuffer.
461
            //
462
463
0
            char* writePtr =
464
0
                _lineBuffer->buffer + _ofd->offsetInLineBuffer[y - _ofd->minY];
465
            //
466
            // Iterate over all image channels.
467
            //
468
469
0
            for (unsigned int i = 0; i < _ofd->slices.size (); ++i)
470
0
            {
471
                //
472
                // Test if scan line y of this channel contains any data
473
                // (the scan line contains data only if y % ySampling == 0).
474
                //
475
476
0
                const OutSliceInfo& slice = _ofd->slices[i];
477
478
0
                if (modp (y, slice.ySampling) != 0) continue;
479
480
                //
481
                // Find the x coordinates of the leftmost and rightmost
482
                // sampled pixels (i.e. pixels within the data window
483
                // for which x % xSampling == 0).
484
                //
485
486
0
                int dMinX = divp (_ofd->minX, slice.xSampling);
487
0
                int dMaxX = divp (_ofd->maxX, slice.xSampling);
488
489
                //
490
                // Fill the line buffer with with pixel data.
491
                //
492
493
0
                if (slice.zero)
494
0
                {
495
                    //
496
                    // The frame buffer contains no data for this channel.
497
                    // Store zeroes in _lineBuffer->buffer.
498
                    //
499
500
0
                    fillChannelWithZeroes (
501
0
                        writePtr, _ofd->format, slice.type, dMaxX - dMinX + 1);
502
0
                }
503
0
                else
504
0
                {
505
                    //
506
                    // If necessary, convert the pixel data to Xdr format.
507
                    // Then store the pixel data in _ofd->lineBuffer.
508
                    //
509
                    // slice.base may be 'negative' but
510
                    // pointer arithmetic is not allowed to overflow, so
511
                    // perform computation with the non-pointer 'intptr_t' instead
512
                    //
513
0
                    intptr_t base = reinterpret_cast<intptr_t> (slice.base);
514
0
                    intptr_t linePtr =
515
0
                        base + divp (y, slice.ySampling) * slice.yStride;
516
517
0
                    const char* readPtr = reinterpret_cast<const char*> (
518
0
                        linePtr + dMinX * slice.xStride);
519
0
                    const char* endPtr = reinterpret_cast<const char*> (
520
0
                        linePtr + dMaxX * slice.xStride);
521
522
0
                    copyFromFrameBuffer (
523
0
                        writePtr,
524
0
                        readPtr,
525
0
                        endPtr,
526
0
                        slice.xStride,
527
0
                        _ofd->format,
528
0
                        slice.type);
529
0
                }
530
0
            }
531
532
0
            if (_lineBuffer->endOfLineBufferData < writePtr)
533
0
                _lineBuffer->endOfLineBufferData = writePtr;
534
535
#ifdef DEBUG
536
537
            assert (
538
                writePtr - (_lineBuffer->buffer +
539
                            _ofd->offsetInLineBuffer[y - _ofd->minY]) ==
540
                (int) _ofd->bytesPerLine[y - _ofd->minY]);
541
542
#endif
543
0
        }
544
545
        //
546
        // If the next scanline isn't past the bounds of the lineBuffer
547
        // then we are done, otherwise compress the linebuffer
548
        //
549
550
0
        if (y >= _lineBuffer->minY && y <= _lineBuffer->maxY) return;
551
552
0
        _lineBuffer->dataPtr = _lineBuffer->buffer;
553
554
0
        _lineBuffer->dataSize =
555
0
            _lineBuffer->endOfLineBufferData - _lineBuffer->buffer;
556
557
        //
558
        // Compress the data
559
        //
560
561
0
        Compressor* compressor = _lineBuffer->compressor;
562
563
0
        if (compressor)
564
0
        {
565
0
            const char* compPtr;
566
567
0
            int compSize = compressor->compress (
568
0
                _lineBuffer->dataPtr,
569
0
                _lineBuffer->dataSize,
570
0
                _lineBuffer->minY,
571
0
                compPtr);
572
573
0
            if (compSize < _lineBuffer->dataSize)
574
0
            {
575
0
                _lineBuffer->dataSize = compSize;
576
0
                _lineBuffer->dataPtr  = compPtr;
577
0
            }
578
0
            else if (_ofd->format == Compressor::NATIVE)
579
0
            {
580
                //
581
                // The data did not shrink during compression, but
582
                // we cannot write to the file using the machine's
583
                // native format, so we need to convert the lineBuffer
584
                // to Xdr.
585
                //
586
587
0
                convertToXdr (
588
0
                    _ofd,
589
0
                    _lineBuffer->buffer,
590
0
                    _lineBuffer->minY,
591
0
                    _lineBuffer->maxY,
592
0
                    _lineBuffer->dataSize);
593
0
            }
594
0
        }
595
596
0
        _lineBuffer->partiallyFull = false;
597
0
    }
598
0
    catch (std::exception& e)
599
0
    {
600
0
        if (!_lineBuffer->hasException)
601
0
        {
602
0
            _lineBuffer->exception    = e.what ();
603
0
            _lineBuffer->hasException = true;
604
0
        }
605
0
    }
606
0
    catch (...)
607
0
    {
608
0
        if (!_lineBuffer->hasException)
609
0
        {
610
0
            _lineBuffer->exception    = "unrecognized exception";
611
0
            _lineBuffer->hasException = true;
612
0
        }
613
0
    }
614
0
}
615
616
} // namespace
617
618
OutputFile::OutputFile (
619
    const char fileName[], const Header& header, int numThreads)
620
0
    : _data (new Data (numThreads))
621
0
{
622
0
    _data->_streamData   = new OutputStreamMutex ();
623
0
    _data->_deleteStream = true;
624
0
    try
625
0
    {
626
0
        header.sanityCheck ();
627
0
        _data->_streamData->os = new StdOFStream (fileName);
628
0
        _data->multiPart       = false; // only one header, not multipart
629
0
        initialize (header);
630
0
        _data->_streamData->currentPosition = _data->_streamData->os->tellp ();
631
632
        // Write header and empty offset table to the file.
633
0
        writeMagicNumberAndVersionField (
634
0
            *_data->_streamData->os, _data->header);
635
0
        _data->previewPosition =
636
0
            _data->header.writeTo (*_data->_streamData->os);
637
0
        _data->lineOffsetsPosition =
638
0
            writeLineOffsets (*_data->_streamData->os, _data->lineOffsets);
639
0
    }
640
0
    catch (IEX_NAMESPACE::BaseExc& e)
641
0
    {
642
        // ~OutputFile will not run, so free memory here
643
0
        if (_data)
644
0
        {
645
0
            if (_data->_streamData)
646
0
            {
647
0
                delete _data->_streamData->os;
648
0
                delete _data->_streamData;
649
0
            }
650
651
0
            delete _data;
652
0
        }
653
654
0
        REPLACE_EXC (
655
0
            e,
656
0
            "Cannot open image file "
657
0
            "\"" << fileName
658
0
                 << "\". " << e.what ());
659
0
        throw;
660
0
    }
661
0
    catch (...)
662
0
    {
663
        // ~OutputFile will not run, so free memory here
664
0
        if (_data)
665
0
        {
666
0
            if (_data->_streamData)
667
0
            {
668
0
                delete _data->_streamData->os;
669
0
                delete _data->_streamData;
670
0
            }
671
0
            delete _data;
672
0
        }
673
674
0
        throw;
675
0
    }
676
0
}
677
678
OutputFile::OutputFile (
679
    OPENEXR_IMF_INTERNAL_NAMESPACE::OStream& os,
680
    const Header&                            header,
681
    int                                      numThreads)
682
0
    : _data (new Data (numThreads))
683
0
{
684
0
    _data->_streamData   = new OutputStreamMutex ();
685
0
    _data->_deleteStream = false;
686
0
    try
687
0
    {
688
0
        header.sanityCheck ();
689
0
        _data->_streamData->os = &os;
690
0
        _data->multiPart       = false;
691
0
        initialize (header);
692
0
        _data->_streamData->currentPosition = _data->_streamData->os->tellp ();
693
694
        // Write header and empty offset table to the file.
695
0
        writeMagicNumberAndVersionField (
696
0
            *_data->_streamData->os, _data->header);
697
0
        _data->previewPosition =
698
0
            _data->header.writeTo (*_data->_streamData->os);
699
0
        _data->lineOffsetsPosition =
700
0
            writeLineOffsets (*_data->_streamData->os, _data->lineOffsets);
701
0
    }
702
0
    catch (IEX_NAMESPACE::BaseExc& e)
703
0
    {
704
        // ~OutputFile will not run, so free memory here
705
0
        if (_data)
706
0
        {
707
0
            if (_data->_streamData) delete _data->_streamData;
708
0
            delete _data;
709
0
        }
710
711
0
        REPLACE_EXC (
712
0
            e,
713
0
            "Cannot open image file "
714
0
            "\"" << os.fileName ()
715
0
                 << "\". " << e.what ());
716
0
        throw;
717
0
    }
718
0
    catch (...)
719
0
    {
720
        // ~OutputFile will not run, so free memory here
721
0
        if (_data)
722
0
        {
723
0
            if (_data->_streamData) delete _data->_streamData;
724
0
            delete _data;
725
0
        }
726
727
0
        throw;
728
0
    }
729
0
}
730
731
OutputFile::OutputFile (const OutputPartData* part) : _data (NULL)
732
0
{
733
0
    try
734
0
    {
735
0
        if (part->header.type () != SCANLINEIMAGE)
736
0
            throw IEX_NAMESPACE::ArgExc (
737
0
                "Can't build a OutputFile from a type-mismatched part.");
738
739
0
        _data                = new Data (part->numThreads);
740
0
        _data->_streamData   = part->mutex;
741
0
        _data->_deleteStream = false;
742
0
        _data->multiPart     = part->multipart;
743
744
0
        initialize (part->header);
745
0
        _data->partNumber          = part->partNumber;
746
0
        _data->lineOffsetsPosition = part->chunkOffsetTablePosition;
747
0
        _data->previewPosition     = part->previewPosition;
748
0
    }
749
0
    catch (IEX_NAMESPACE::BaseExc& e)
750
0
    {
751
0
        if (_data) delete _data;
752
753
0
        REPLACE_EXC (
754
0
            e,
755
0
            "Cannot initialize output part "
756
0
            "\"" << part->partNumber
757
0
                 << "\". " << e.what ());
758
0
        throw;
759
0
    }
760
0
    catch (...)
761
0
    {
762
0
        if (_data) delete _data;
763
764
0
        throw;
765
0
    }
766
0
}
767
768
void
769
OutputFile::initialize (const Header& header)
770
0
{
771
0
    _data->header = header;
772
773
    // "fix" the type if it happens to be set incorrectly
774
    // (attribute is optional, but ensure it is correct if it exists)
775
0
    if (_data->header.hasType ()) { _data->header.setType (SCANLINEIMAGE); }
776
777
0
    const Box2i& dataWindow = header.dataWindow ();
778
779
0
    _data->currentScanLine = (header.lineOrder () == INCREASING_Y)
780
0
                                 ? dataWindow.min.y
781
0
                                 : dataWindow.max.y;
782
783
0
    _data->missingScanLines = dataWindow.max.y - dataWindow.min.y + 1;
784
0
    _data->lineOrder        = header.lineOrder ();
785
0
    _data->minX             = dataWindow.min.x;
786
0
    _data->maxX             = dataWindow.max.x;
787
0
    _data->minY             = dataWindow.min.y;
788
0
    _data->maxY             = dataWindow.max.y;
789
790
0
    size_t maxBytesPerLine =
791
0
        bytesPerLineTable (_data->header, _data->bytesPerLine);
792
793
0
    for (size_t i = 0; i < _data->lineBuffers.size (); ++i)
794
0
    {
795
0
        _data->lineBuffers[i] = new LineBuffer (newCompressor (
796
0
            _data->header.compression (), maxBytesPerLine, _data->header));
797
0
    }
798
799
0
    LineBuffer* lineBuffer = _data->lineBuffers[0];
800
0
    _data->format          = defaultFormat (lineBuffer->compressor);
801
0
    _data->linesInBuffer   = numLinesInBuffer (lineBuffer->compressor);
802
0
    _data->lineBufferSize  = maxBytesPerLine * _data->linesInBuffer;
803
804
0
    for (size_t i = 0; i < _data->lineBuffers.size (); i++)
805
0
        _data->lineBuffers[i]->buffer.resizeErase (_data->lineBufferSize);
806
807
0
    int lineOffsetSize =
808
0
        (dataWindow.max.y - dataWindow.min.y + _data->linesInBuffer) /
809
0
        _data->linesInBuffer;
810
811
0
    _data->lineOffsets.resize (lineOffsetSize);
812
813
0
    offsetInLineBufferTable (
814
0
        _data->bytesPerLine, _data->linesInBuffer, _data->offsetInLineBuffer);
815
0
}
816
817
OutputFile::~OutputFile ()
818
0
{
819
0
    if (_data)
820
0
    {
821
0
        {
822
0
#if ILMTHREAD_THREADING_ENABLED
823
0
            std::lock_guard<std::mutex> lock (*_data->_streamData);
824
0
#endif
825
0
            uint64_t originalPosition = _data->_streamData->os->tellp ();
826
827
0
            if (_data->lineOffsetsPosition > 0)
828
0
            {
829
0
                try
830
0
                {
831
0
                    _data->_streamData->os->seekp (_data->lineOffsetsPosition);
832
0
                    writeLineOffsets (
833
0
                        *_data->_streamData->os, _data->lineOffsets);
834
835
                    //
836
                    // Restore the original position.
837
                    //
838
0
                    _data->_streamData->os->seekp (originalPosition);
839
0
                }
840
0
                catch (
841
0
                    ...) //NOSONAR - suppress vulnerability reports from SonarCloud.
842
0
                {
843
                    //
844
                    // We cannot safely throw any exceptions from here.
845
                    // This destructor may have been called because the
846
                    // stack is currently being unwound for another
847
                    // exception.
848
                    //
849
0
                }
850
0
            }
851
0
        }
852
853
0
        if (_data->_deleteStream && _data->_streamData)
854
0
            delete _data->_streamData->os;
855
856
0
        if (_data->partNumber == -1 && _data->_streamData)
857
0
            delete _data->_streamData;
858
859
0
        delete _data;
860
0
    }
861
0
}
862
863
const char*
864
OutputFile::fileName () const
865
0
{
866
0
    return _data->_streamData->os->fileName ();
867
0
}
868
869
const Header&
870
OutputFile::header () const
871
0
{
872
0
    return _data->header;
873
0
}
874
875
void
876
OutputFile::setFrameBuffer (const FrameBuffer& frameBuffer)
877
0
{
878
0
#if ILMTHREAD_THREADING_ENABLED
879
0
    std::lock_guard<std::mutex> lock (*_data->_streamData);
880
0
#endif
881
    //
882
    // Check if the new frame buffer descriptor
883
    // is compatible with the image file header.
884
    //
885
886
0
    const ChannelList& channels = _data->header.channels ();
887
888
0
    for (ChannelList::ConstIterator i = channels.begin (); i != channels.end ();
889
0
         ++i)
890
0
    {
891
0
        FrameBuffer::ConstIterator j = frameBuffer.find (i.name ());
892
893
0
        if (j == frameBuffer.end ()) continue;
894
895
0
        if (i.channel ().type != j.slice ().type)
896
0
        {
897
0
            THROW (
898
0
                IEX_NAMESPACE::ArgExc,
899
0
                "Pixel type of \"" << i.name ()
900
0
                                   << "\" channel "
901
0
                                      "of output file \""
902
0
                                   << fileName ()
903
0
                                   << "\" is "
904
0
                                      "not compatible with the frame buffer's "
905
0
                                      "pixel type.");
906
0
        }
907
908
0
        if (i.channel ().xSampling != j.slice ().xSampling ||
909
0
            i.channel ().ySampling != j.slice ().ySampling)
910
0
        {
911
0
            THROW (
912
0
                IEX_NAMESPACE::ArgExc,
913
0
                "X and/or y subsampling factors "
914
0
                "of \""
915
0
                    << i.name ()
916
0
                    << "\" channel "
917
0
                       "of output file \""
918
0
                    << fileName ()
919
0
                    << "\" are "
920
0
                       "not compatible with the frame buffer's "
921
0
                       "subsampling factors.");
922
0
        }
923
0
    }
924
925
    //
926
    // Initialize slice table for writePixels().
927
    //
928
929
0
    vector<OutSliceInfo> slices;
930
931
0
    for (ChannelList::ConstIterator i = channels.begin (); i != channels.end ();
932
0
         ++i)
933
0
    {
934
0
        FrameBuffer::ConstIterator j = frameBuffer.find (i.name ());
935
936
0
        if (j == frameBuffer.end ())
937
0
        {
938
            //
939
            // Channel i is not present in the frame buffer.
940
            // In the file, channel i will contain only zeroes.
941
            //
942
943
0
            slices.push_back (OutSliceInfo (
944
0
                i.channel ().type,
945
0
                0, // base
946
0
                0, // xStride,
947
0
                0, // yStride,
948
0
                i.channel ().xSampling,
949
0
                i.channel ().ySampling,
950
0
                true)); // zero
951
0
        }
952
0
        else
953
0
        {
954
            //
955
            // Channel i is present in the frame buffer.
956
            //
957
958
0
            slices.push_back (OutSliceInfo (
959
0
                j.slice ().type,
960
0
                j.slice ().base,
961
0
                j.slice ().xStride,
962
0
                j.slice ().yStride,
963
0
                j.slice ().xSampling,
964
0
                j.slice ().ySampling,
965
0
                false)); // zero
966
0
        }
967
0
    }
968
969
    //
970
    // Store the new frame buffer.
971
    //
972
973
0
    _data->frameBuffer = frameBuffer;
974
0
    _data->slices      = slices;
975
0
}
976
977
const FrameBuffer&
978
OutputFile::frameBuffer () const
979
0
{
980
0
#if ILMTHREAD_THREADING_ENABLED
981
0
    std::lock_guard<std::mutex> lock (*_data->_streamData);
982
0
#endif
983
0
    return _data->frameBuffer;
984
0
}
985
986
void
987
OutputFile::writePixels (int numScanLines)
988
0
{
989
0
    try
990
0
    {
991
0
#if ILMTHREAD_THREADING_ENABLED
992
0
        std::lock_guard<std::mutex> lock (*_data->_streamData);
993
0
#endif
994
0
        if (_data->slices.size () == 0)
995
0
            throw IEX_NAMESPACE::ArgExc (
996
0
                "No frame buffer specified as pixel data source.");
997
998
        //
999
        // Maintain two iterators:
1000
        //     nextWriteBuffer: next linebuffer to be written to the file
1001
        //     nextCompressBuffer: next linebuffer to compress
1002
        //
1003
1004
0
        int first =
1005
0
            (_data->currentScanLine - _data->minY) / _data->linesInBuffer;
1006
1007
0
        int nextWriteBuffer = first;
1008
0
        int nextCompressBuffer;
1009
0
        int stop;
1010
0
        int step;
1011
0
        int scanLineMin;
1012
0
        int scanLineMax;
1013
1014
0
        {
1015
            //
1016
            // Create a task group for all line buffer tasks. When the
1017
            // taskgroup goes out of scope, the destructor waits until
1018
            // all tasks are complete.
1019
            //
1020
1021
0
            TaskGroup taskGroup;
1022
1023
            //
1024
            // Determine the range of lineBuffers that intersect the scan
1025
            // line range.  Then add the initial compression tasks to the
1026
            // thread pool.  We always add in at least one task but the
1027
            // individual task might not do anything if numScanLines == 0.
1028
            //
1029
1030
0
            if (_data->lineOrder == INCREASING_Y)
1031
0
            {
1032
0
                int last = (_data->currentScanLine + (numScanLines - 1) -
1033
0
                            _data->minY) /
1034
0
                           _data->linesInBuffer;
1035
1036
0
                scanLineMin = _data->currentScanLine;
1037
0
                scanLineMax = _data->currentScanLine + numScanLines - 1;
1038
1039
0
                int numTasks = max (
1040
0
                    min ((int) _data->lineBuffers.size (), last - first + 1),
1041
0
                    1);
1042
1043
0
                for (int i = 0; i < numTasks; i++)
1044
0
                {
1045
0
                    ThreadPool::addGlobalTask (new LineBufferTask (
1046
0
                        &taskGroup,
1047
0
                        _data,
1048
0
                        first + i,
1049
0
                        scanLineMin,
1050
0
                        scanLineMax));
1051
0
                }
1052
1053
0
                nextCompressBuffer = first + numTasks;
1054
0
                stop               = last + 1;
1055
0
                step               = 1;
1056
0
            }
1057
0
            else
1058
0
            {
1059
0
                int last = (_data->currentScanLine - (numScanLines - 1) -
1060
0
                            _data->minY) /
1061
0
                           _data->linesInBuffer;
1062
1063
0
                scanLineMax = _data->currentScanLine;
1064
0
                scanLineMin = _data->currentScanLine - numScanLines + 1;
1065
1066
0
                int numTasks = max (
1067
0
                    min ((int) _data->lineBuffers.size (), first - last + 1),
1068
0
                    1);
1069
1070
0
                for (int i = 0; i < numTasks; i++)
1071
0
                {
1072
0
                    ThreadPool::addGlobalTask (new LineBufferTask (
1073
0
                        &taskGroup,
1074
0
                        _data,
1075
0
                        first - i,
1076
0
                        scanLineMin,
1077
0
                        scanLineMax));
1078
0
                }
1079
1080
0
                nextCompressBuffer = first - numTasks;
1081
0
                stop               = last - 1;
1082
0
                step               = -1;
1083
0
            }
1084
1085
0
            while (true)
1086
0
            {
1087
0
                if (_data->missingScanLines <= 0)
1088
0
                {
1089
0
                    throw IEX_NAMESPACE::ArgExc (
1090
0
                        "Tried to write more scan lines "
1091
0
                        "than specified by the data window.");
1092
0
                }
1093
1094
                //
1095
                // Wait until the next line buffer is ready to be written
1096
                //
1097
1098
0
                LineBuffer* writeBuffer =
1099
0
                    _data->getLineBuffer (nextWriteBuffer);
1100
1101
0
                writeBuffer->wait ();
1102
1103
0
                int numLines =
1104
0
                    writeBuffer->scanLineMax - writeBuffer->scanLineMin + 1;
1105
1106
0
                _data->missingScanLines -= numLines;
1107
1108
                //
1109
                // If the line buffer is only partially full, then it is
1110
                // not complete and we cannot write it to disk yet.
1111
                //
1112
1113
0
                if (writeBuffer->partiallyFull)
1114
0
                {
1115
0
                    _data->currentScanLine =
1116
0
                        _data->currentScanLine + step * numLines;
1117
0
                    writeBuffer->post ();
1118
1119
0
                    return;
1120
0
                }
1121
1122
                //
1123
                // Write the line buffer
1124
                //
1125
1126
0
                writePixelData (_data->_streamData, _data, writeBuffer);
1127
0
                nextWriteBuffer += step;
1128
1129
0
                _data->currentScanLine =
1130
0
                    _data->currentScanLine + step * numLines;
1131
1132
#ifdef DEBUG
1133
1134
                assert (
1135
                    _data->currentScanLine ==
1136
                    ((_data->lineOrder == INCREASING_Y)
1137
                         ? writeBuffer->scanLineMax + 1
1138
                         : writeBuffer->scanLineMin - 1));
1139
1140
#endif
1141
1142
                //
1143
                // Release the lock on the line buffer
1144
                //
1145
1146
0
                writeBuffer->post ();
1147
1148
                //
1149
                // If this was the last line buffer in the scanline range
1150
                //
1151
1152
0
                if (nextWriteBuffer == stop) break;
1153
1154
                //
1155
                // If there are no more line buffers to compress,
1156
                // then only continue to write out remaining lineBuffers
1157
                //
1158
1159
0
                if (nextCompressBuffer == stop) continue;
1160
1161
                //
1162
                // Add nextCompressBuffer as a compression task
1163
                //
1164
1165
0
                ThreadPool::addGlobalTask (new LineBufferTask (
1166
0
                    &taskGroup,
1167
0
                    _data,
1168
0
                    nextCompressBuffer,
1169
0
                    scanLineMin,
1170
0
                    scanLineMax));
1171
1172
                //
1173
                // Update the next line buffer we need to compress
1174
                //
1175
1176
0
                nextCompressBuffer += step;
1177
0
            }
1178
1179
            //
1180
            // Finish all tasks
1181
            //
1182
0
        }
1183
1184
        //
1185
        // Exception handling:
1186
        //
1187
        // LineBufferTask::execute() may have encountered exceptions, but
1188
        // those exceptions occurred in another thread, not in the thread
1189
        // that is executing this call to OutputFile::writePixels().
1190
        // LineBufferTask::execute() has caught all exceptions and stored
1191
        // the exceptions' what() strings in the line buffers.
1192
        // Now we check if any line buffer contains a stored exception; if
1193
        // this is the case then we re-throw the exception in this thread.
1194
        // (It is possible that multiple line buffers contain stored
1195
        // exceptions.  We re-throw the first exception we find and
1196
        // ignore all others.)
1197
        //
1198
1199
0
        const string* exception = 0;
1200
1201
0
        for (size_t i = 0; i < _data->lineBuffers.size (); ++i)
1202
0
        {
1203
0
            LineBuffer* lineBuffer = _data->lineBuffers[i];
1204
1205
0
            if (lineBuffer->hasException && !exception)
1206
0
                exception = &lineBuffer->exception;
1207
1208
0
            lineBuffer->hasException = false;
1209
0
        }
1210
1211
0
        if (exception) throw IEX_NAMESPACE::IoExc (*exception);
1212
0
    }
1213
0
    catch (IEX_NAMESPACE::BaseExc& e)
1214
0
    {
1215
0
        REPLACE_EXC (
1216
0
            e,
1217
0
            "Failed to write pixel data to image "
1218
0
            "file \""
1219
0
                << fileName () << "\". " << e.what ());
1220
0
        throw;
1221
0
    }
1222
0
}
1223
1224
int
1225
OutputFile::currentScanLine () const
1226
0
{
1227
0
#if ILMTHREAD_THREADING_ENABLED
1228
0
    std::lock_guard<std::mutex> lock (*_data->_streamData);
1229
0
#endif
1230
0
    return _data->currentScanLine;
1231
0
}
1232
1233
void
1234
OutputFile::copyPixels (InputFile& in)
1235
0
{
1236
0
#if ILMTHREAD_THREADING_ENABLED
1237
0
    std::lock_guard<std::mutex> lock (*_data->_streamData);
1238
0
#endif
1239
    //
1240
    // Check if this file's and and the InputFile's
1241
    // headers are compatible.
1242
    //
1243
1244
0
    const Header& hdr   = _data->header;
1245
0
    const Header& inHdr = in.header ();
1246
1247
0
    if (inHdr.find ("tiles") != inHdr.end ())
1248
0
        THROW (
1249
0
            IEX_NAMESPACE::ArgExc,
1250
0
            "Cannot copy pixels from image "
1251
0
            "file \""
1252
0
                << in.fileName ()
1253
0
                << "\" to image "
1254
0
                   "file \""
1255
0
                << fileName ()
1256
0
                << "\". "
1257
0
                   "The input file is tiled, but the output file is "
1258
0
                   "not. Try using TiledOutputFile::copyPixels "
1259
0
                   "instead.");
1260
1261
0
    if (!(hdr.dataWindow () == inHdr.dataWindow ()))
1262
0
        THROW (
1263
0
            IEX_NAMESPACE::ArgExc,
1264
0
            "Cannot copy pixels from image "
1265
0
            "file \""
1266
0
                << in.fileName ()
1267
0
                << "\" to image "
1268
0
                   "file \""
1269
0
                << fileName ()
1270
0
                << "\". "
1271
0
                   "The files have different data windows.");
1272
1273
0
    if (!(hdr.lineOrder () == inHdr.lineOrder ()))
1274
0
        THROW (
1275
0
            IEX_NAMESPACE::ArgExc,
1276
0
            "Quick pixel copy from image "
1277
0
            "file \""
1278
0
                << in.fileName ()
1279
0
                << "\" to image "
1280
0
                   "file \""
1281
0
                << fileName ()
1282
0
                << "\" failed. "
1283
0
                   "The files have different line orders.");
1284
1285
0
    if (!(hdr.compression () == inHdr.compression ()))
1286
0
        THROW (
1287
0
            IEX_NAMESPACE::ArgExc,
1288
0
            "Quick pixel copy from image "
1289
0
            "file \""
1290
0
                << in.fileName ()
1291
0
                << "\" to image "
1292
0
                   "file \""
1293
0
                << fileName ()
1294
0
                << "\" failed. "
1295
0
                   "The files use different compression methods.");
1296
1297
0
    if (!(hdr.channels () == inHdr.channels ()))
1298
0
        THROW (
1299
0
            IEX_NAMESPACE::ArgExc,
1300
0
            "Quick pixel copy from image "
1301
0
            "file \""
1302
0
                << in.fileName ()
1303
0
                << "\" to image "
1304
0
                   "file \""
1305
0
                << fileName ()
1306
0
                << "\" failed.  "
1307
0
                   "The files have different channel lists.");
1308
1309
    //
1310
    // Verify that no pixel data have been written to this file yet.
1311
    //
1312
1313
0
    const Box2i& dataWindow = hdr.dataWindow ();
1314
1315
0
    if (_data->missingScanLines != dataWindow.max.y - dataWindow.min.y + 1)
1316
0
        THROW (
1317
0
            IEX_NAMESPACE::LogicExc,
1318
0
            "Quick pixel copy from image "
1319
0
            "file \""
1320
0
                << in.fileName ()
1321
0
                << "\" to image "
1322
0
                   "file \""
1323
0
                << fileName ()
1324
0
                << "\" failed. "
1325
0
                   "\""
1326
0
                << fileName ()
1327
0
                << "\" already contains "
1328
0
                   "pixel data.");
1329
1330
    //
1331
    // Copy the pixel data.
1332
    //
1333
1334
0
    while (_data->missingScanLines > 0)
1335
0
    {
1336
0
        const char* pixelData;
1337
0
        int         pixelDataSize;
1338
1339
0
        in.rawPixelData (_data->currentScanLine, pixelData, pixelDataSize);
1340
1341
0
        writePixelData (
1342
0
            _data->_streamData,
1343
0
            _data,
1344
0
            lineBufferMinY (
1345
0
                _data->currentScanLine, _data->minY, _data->linesInBuffer),
1346
0
            pixelData,
1347
0
            pixelDataSize);
1348
1349
0
        _data->currentScanLine += (_data->lineOrder == INCREASING_Y)
1350
0
                                      ? _data->linesInBuffer
1351
0
                                      : -_data->linesInBuffer;
1352
1353
0
        _data->missingScanLines -= _data->linesInBuffer;
1354
0
    }
1355
0
}
1356
1357
void
1358
OutputFile::copyPixels (InputPart& in)
1359
0
{
1360
0
    copyPixels (*in.file);
1361
0
}
1362
1363
void
1364
OutputFile::updatePreviewImage (const PreviewRgba newPixels[])
1365
0
{
1366
0
#if ILMTHREAD_THREADING_ENABLED
1367
0
    std::lock_guard<std::mutex> lock (*_data->_streamData);
1368
0
#endif
1369
0
    if (_data->previewPosition <= 0)
1370
0
        THROW (
1371
0
            IEX_NAMESPACE::LogicExc,
1372
0
            "Cannot update preview image pixels. "
1373
0
            "File \""
1374
0
                << fileName ()
1375
0
                << "\" does not "
1376
0
                   "contain a preview image.");
1377
1378
    //
1379
    // Store the new pixels in the header's preview image attribute.
1380
    //
1381
1382
0
    PreviewImageAttribute& pia =
1383
0
        _data->header.typedAttribute<PreviewImageAttribute> ("preview");
1384
1385
0
    PreviewImage& pi        = pia.value ();
1386
0
    PreviewRgba*  pixels    = pi.pixels ();
1387
0
    int           numPixels = pi.width () * pi.height ();
1388
1389
0
    for (int i = 0; i < numPixels; ++i)
1390
0
        pixels[i] = newPixels[i];
1391
1392
    //
1393
    // Save the current file position, jump to the position in
1394
    // the file where the preview image starts, store the new
1395
    // preview image, and jump back to the saved file position.
1396
    //
1397
1398
0
    uint64_t savedPosition = _data->_streamData->os->tellp ();
1399
1400
0
    try
1401
0
    {
1402
0
        _data->_streamData->os->seekp (_data->previewPosition);
1403
0
        pia.writeValueTo (*_data->_streamData->os, _data->version);
1404
0
        _data->_streamData->os->seekp (savedPosition);
1405
0
    }
1406
0
    catch (IEX_NAMESPACE::BaseExc& e)
1407
0
    {
1408
0
        REPLACE_EXC (
1409
0
            e,
1410
0
            "Cannot update preview image pixels for "
1411
0
            "file \""
1412
0
                << fileName () << "\". " << e.what ());
1413
0
        throw;
1414
0
    }
1415
0
}
1416
1417
void
1418
OutputFile::breakScanLine (int y, int offset, int length, char c)
1419
0
{
1420
0
#if ILMTHREAD_THREADING_ENABLED
1421
0
    std::lock_guard<std::mutex> lock (*_data->_streamData);
1422
0
#endif
1423
0
    uint64_t position =
1424
0
        _data->lineOffsets[(y - _data->minY) / _data->linesInBuffer];
1425
1426
0
    if (!position)
1427
0
        THROW (
1428
0
            IEX_NAMESPACE::ArgExc,
1429
0
            "Cannot overwrite scan line "
1430
0
                << y
1431
0
                << ". "
1432
0
                   "The scan line has not yet been stored in "
1433
0
                   "file \""
1434
0
                << fileName () << "\".");
1435
1436
0
    _data->_streamData->currentPosition = 0;
1437
0
    _data->_streamData->os->seekp (position + offset);
1438
1439
0
    for (int i = 0; i < length; ++i)
1440
0
        _data->_streamData->os->write (&c, 1);
1441
0
}
1442
1443
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT