/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 |