1 ///////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004, Industrial Light & Magic, a division of Lucas
4 // Digital Ltd. LLC
5 //
6 // All rights reserved.
7 //
8 // Redistribution and use in source and binary forms, with or without
9 // modification, are permitted provided that the following conditions are
10 // met:
11 // * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 // * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 // * Neither the name of Industrial Light & Magic nor the names of
18 // its contributors may be used to endorse or promote products derived
19 // from this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 ///////////////////////////////////////////////////////////////////////////
34
35 //-----------------------------------------------------------------------------
36 //
37 // class TiledOutputFile
38 //
39 //-----------------------------------------------------------------------------
40
41 #include <ImfTiledOutputFile.h>
42 #include <ImfTiledInputFile.h>
43 #include <ImfTiledInputPart.h>
44 #include <ImfInputFile.h>
45 #include <ImfInputPart.h>
46 #include <ImfTileDescriptionAttribute.h>
47 #include <ImfPreviewImageAttribute.h>
48 #include <ImfChannelList.h>
49 #include <ImfMisc.h>
50 #include <ImfTiledMisc.h>
51 #include <ImfStdIO.h>
52 #include <ImfCompressor.h>
53 #include "ImathBox.h"
54 #include <ImfArray.h>
55 #include <ImfXdr.h>
56 #include <ImfVersion.h>
57 #include <ImfTileOffsets.h>
58 #include <ImfThreading.h>
59 #include <ImfPartType.h>
60 #include "IlmThreadPool.h"
61 #include "IlmThreadSemaphore.h"
62 #include "IlmThreadMutex.h"
63 #include "ImfOutputStreamMutex.h"
64 #include "ImfOutputPartData.h"
65 #include "Iex.h"
66 #include <string>
67 #include <vector>
68 #include <fstream>
69 #include <assert.h>
70 #include <map>
71 #include <algorithm>
72
73 #include "ImfNamespace.h"
74
75
76 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
77
78 using IMATH_NAMESPACE::Box2i;
79 using IMATH_NAMESPACE::V2i;
80 using std::string;
81 using std::vector;
82 using std::ofstream;
83 using std::map;
84 using std::min;
85 using std::max;
86 using std::swap;
87 using ILMTHREAD_NAMESPACE::Mutex;
88 using ILMTHREAD_NAMESPACE::Lock;
89 using ILMTHREAD_NAMESPACE::Semaphore;
90 using ILMTHREAD_NAMESPACE::Task;
91 using ILMTHREAD_NAMESPACE::TaskGroup;
92 using ILMTHREAD_NAMESPACE::ThreadPool;
93
94 namespace {
95
96 struct TOutSliceInfo
97 {
98 PixelType type;
99 const char * base;
100 size_t xStride;
101 size_t yStride;
102 bool zero;
103 int xTileCoords;
104 int yTileCoords;
105
106 TOutSliceInfo (PixelType type = HALF,
107 const char *base = 0,
108 size_t xStride = 0,
109 size_t yStride = 0,
110 bool zero = false,
111 int xTileCoords = 0,
112 int yTileCoords = 0);
113 };
114
115
TOutSliceInfo(PixelType t,const char * b,size_t xs,size_t ys,bool z,int xtc,int ytc)116 TOutSliceInfo::TOutSliceInfo (PixelType t,
117 const char *b,
118 size_t xs, size_t ys,
119 bool z,
120 int xtc,
121 int ytc)
122 :
123 type (t),
124 base (b),
125 xStride (xs),
126 yStride (ys),
127 zero (z),
128 xTileCoords (xtc),
129 yTileCoords (ytc)
130 {
131 // empty
132 }
133
134
135 struct TileCoord
136 {
137 int dx;
138 int dy;
139 int lx;
140 int ly;
141
142
TileCoord__anon4a5da71f0111::TileCoord143 TileCoord (int xTile = 0, int yTile = 0,
144 int xLevel = 0, int yLevel = 0)
145 :
146 dx (xTile), dy (yTile),
147 lx (xLevel), ly (yLevel)
148 {
149 // empty
150 }
151
152
153 bool
operator <__anon4a5da71f0111::TileCoord154 operator < (const TileCoord &other) const
155 {
156 return (ly < other.ly) ||
157 (ly == other.ly && lx < other.lx) ||
158 ((ly == other.ly && lx == other.lx) &&
159 ((dy < other.dy) || (dy == other.dy && dx < other.dx)));
160 }
161
162
163 bool
operator ==__anon4a5da71f0111::TileCoord164 operator == (const TileCoord &other) const
165 {
166 return lx == other.lx &&
167 ly == other.ly &&
168 dx == other.dx &&
169 dy == other.dy;
170 }
171 };
172
173
174 struct BufferedTile
175 {
176 char * pixelData;
177 int pixelDataSize;
178
BufferedTile__anon4a5da71f0111::BufferedTile179 BufferedTile (const char *data, int size):
180 pixelData (0),
181 pixelDataSize(size)
182 {
183 pixelData = new char[pixelDataSize];
184 memcpy (pixelData, data, pixelDataSize);
185 }
186
~BufferedTile__anon4a5da71f0111::BufferedTile187 ~BufferedTile()
188 {
189 delete [] pixelData;
190 }
191 };
192
193
194 typedef map <TileCoord, BufferedTile *> TileMap;
195
196
197 struct TileBuffer
198 {
199 Array<char> buffer;
200 const char * dataPtr;
201 int dataSize;
202 Compressor * compressor;
203 TileCoord tileCoord;
204 bool hasException;
205 string exception;
206
207 TileBuffer (Compressor *comp);
208 ~TileBuffer ();
209
wait__anon4a5da71f0111::TileBuffer210 inline void wait () {_sem.wait();}
post__anon4a5da71f0111::TileBuffer211 inline void post () {_sem.post();}
212
213 protected:
214
215 Semaphore _sem;
216 };
217
218
TileBuffer(Compressor * comp)219 TileBuffer::TileBuffer (Compressor *comp):
220 dataPtr (0),
221 dataSize (0),
222 compressor (comp),
223 hasException (false),
224 exception (),
225 _sem (1)
226 {
227 // empty
228 }
229
230
~TileBuffer()231 TileBuffer::~TileBuffer ()
232 {
233 delete compressor;
234 }
235
236
237 } // namespace
238
239
240 struct TiledOutputFile::Data
241 {
242 Header header; // the image header
243 int version; // file format version
244 bool multipart; // part came from a multipart file
245 TileDescription tileDesc; // describes the tile layout
246 FrameBuffer frameBuffer; // framebuffer to write into
247 Int64 previewPosition;
248 LineOrder lineOrder; // the file's lineorder
249 int minX; // data window's min x coord
250 int maxX; // data window's max x coord
251 int minY; // data window's min y coord
252 int maxY; // data window's max x coord
253
254 int numXLevels; // number of x levels
255 int numYLevels; // number of y levels
256 int * numXTiles; // number of x tiles at a level
257 int * numYTiles; // number of y tiles at a level
258
259 TileOffsets tileOffsets; // stores offsets in file for
260 // each tile
261
262 Compressor::Format format; // compressor's data format
263 vector<TOutSliceInfo> slices; // info about channels in file
264
265 size_t maxBytesPerTileLine; // combined size of a tile line
266 // over all channels
267
268
269 vector<TileBuffer*> tileBuffers;
270 size_t tileBufferSize; // size of a tile buffer
271
272 Int64 tileOffsetsPosition; // position of the tile index
273
274 TileMap tileMap;
275 TileCoord nextTileToWrite;
276
277 int partNumber; // the output part number
278
279 Data (int numThreads);
280 ~Data ();
281
282 inline TileBuffer * getTileBuffer (int number);
283 // hash function from tile
284 // buffer coords into our
285 // vector of tile buffers
286
287 TileCoord nextTileCoord (const TileCoord &a);
288 };
289
290
Data(int numThreads)291 TiledOutputFile::Data::Data (int numThreads):
292 multipart(false),
293 numXTiles(0),
294 numYTiles(0),
295 tileOffsetsPosition (0),
296 partNumber(-1)
297 {
298 //
299 // We need at least one tileBuffer, but if threading is used,
300 // to keep n threads busy we need 2*n tileBuffers
301 //
302
303 tileBuffers.resize (max (1, 2 * numThreads));
304 }
305
306
~Data()307 TiledOutputFile::Data::~Data ()
308 {
309 delete [] numXTiles;
310 delete [] numYTiles;
311
312 //
313 // Delete all the tile buffers, if any still happen to exist
314 //
315
316 for (TileMap::iterator i = tileMap.begin(); i != tileMap.end(); ++i)
317 delete i->second;
318
319 for (size_t i = 0; i < tileBuffers.size(); i++)
320 delete tileBuffers[i];
321 }
322
323
324 TileBuffer*
getTileBuffer(int number)325 TiledOutputFile::Data::getTileBuffer (int number)
326 {
327 return tileBuffers[number % tileBuffers.size()];
328 }
329
330
331 TileCoord
nextTileCoord(const TileCoord & a)332 TiledOutputFile::Data::nextTileCoord (const TileCoord &a)
333 {
334 TileCoord b = a;
335
336 if (lineOrder == INCREASING_Y)
337 {
338 b.dx++;
339
340 if (b.dx >= numXTiles[b.lx])
341 {
342 b.dx = 0;
343 b.dy++;
344
345 if (b.dy >= numYTiles[b.ly])
346 {
347 //
348 // the next tile is in the next level
349 //
350
351 b.dy = 0;
352
353 switch (tileDesc.mode)
354 {
355 case ONE_LEVEL:
356 case MIPMAP_LEVELS:
357
358 b.lx++;
359 b.ly++;
360 break;
361
362 case RIPMAP_LEVELS:
363
364 b.lx++;
365
366 if (b.lx >= numXLevels)
367 {
368 b.lx = 0;
369 b.ly++;
370
371 #ifdef DEBUG
372 assert (b.ly <= numYLevels);
373 #endif
374 }
375 break;
376 case NUM_LEVELMODES:
377 throw(IEX_NAMESPACE::ArgExc("Invalid tile description"));
378
379 }
380 }
381 }
382 }
383 else if (lineOrder == DECREASING_Y)
384 {
385 b.dx++;
386
387 if (b.dx >= numXTiles[b.lx])
388 {
389 b.dx = 0;
390 b.dy--;
391
392 if (b.dy < 0)
393 {
394 //
395 // the next tile is in the next level
396 //
397
398 switch (tileDesc.mode)
399 {
400 case ONE_LEVEL:
401 case MIPMAP_LEVELS:
402
403 b.lx++;
404 b.ly++;
405 break;
406
407 case RIPMAP_LEVELS:
408
409 b.lx++;
410
411 if (b.lx >= numXLevels)
412 {
413 b.lx = 0;
414 b.ly++;
415
416 #ifdef DEBUG
417 assert (b.ly <= numYLevels);
418 #endif
419 }
420 break;
421 case NUM_LEVELMODES:
422 throw(IEX_NAMESPACE::ArgExc("Invalid tile description"));
423
424 }
425
426 if (b.ly < numYLevels)
427 b.dy = numYTiles[b.ly] - 1;
428 }
429 }
430 }
431
432 return b;
433 }
434
435
436 namespace {
437
438 void
writeTileData(OutputStreamMutex * streamData,TiledOutputFile::Data * ofd,int dx,int dy,int lx,int ly,const char pixelData[],int pixelDataSize)439 writeTileData (OutputStreamMutex *streamData,
440 TiledOutputFile::Data *ofd,
441 int dx, int dy,
442 int lx, int ly,
443 const char pixelData[],
444 int pixelDataSize)
445 {
446 //
447 // Store a block of pixel data in the output file, and try
448 // to keep track of the current writing position the file,
449 // without calling tellp() (tellp() can be fairly expensive).
450 //
451
452 Int64 currentPosition = streamData->currentPosition;
453 streamData->currentPosition = 0;
454
455 if (currentPosition == 0)
456 currentPosition = streamData->os->tellp();
457
458 ofd->tileOffsets (dx, dy, lx, ly) = currentPosition;
459
460 #ifdef DEBUG
461 assert (streamData->os->tellp() == currentPosition);
462 #endif
463
464 //
465 // Write the tile header.
466 //
467
468 if (ofd->multipart)
469 {
470 Xdr::write <StreamIO> (*streamData->os, ofd->partNumber);
471 }
472 Xdr::write <StreamIO> (*streamData->os, dx);
473 Xdr::write <StreamIO> (*streamData->os, dy);
474 Xdr::write <StreamIO> (*streamData->os, lx);
475 Xdr::write <StreamIO> (*streamData->os, ly);
476 Xdr::write <StreamIO> (*streamData->os, pixelDataSize);
477
478 streamData->os->write (pixelData, pixelDataSize);
479
480 //
481 // Keep current position in the file so that we can avoid
482 // redundant seekg() operations (seekg() can be fairly expensive).
483 //
484
485 streamData->currentPosition = currentPosition +
486 5 * Xdr::size<int>() +
487 pixelDataSize;
488
489 if (ofd->multipart)
490 {
491 streamData->currentPosition += Xdr::size<int>();
492 }
493 }
494
495
496
497 void
bufferedTileWrite(OutputStreamMutex * streamData,TiledOutputFile::Data * ofd,int dx,int dy,int lx,int ly,const char pixelData[],int pixelDataSize)498 bufferedTileWrite (OutputStreamMutex *streamData,
499 TiledOutputFile::Data *ofd,
500 int dx, int dy,
501 int lx, int ly,
502 const char pixelData[],
503 int pixelDataSize)
504 {
505 //
506 // Check if a tile with coordinates (dx,dy,lx,ly) has already been written.
507 //
508
509 if (ofd->tileOffsets (dx, dy, lx, ly))
510 {
511 THROW (IEX_NAMESPACE::ArgExc,
512 "Attempt to write tile "
513 "(" << dx << ", " << dy << ", " << lx << ", " << ly << ") "
514 "more than once.");
515 }
516
517 //
518 // If tiles can be written in random order, then don't buffer anything.
519 //
520
521 if (ofd->lineOrder == RANDOM_Y)
522 {
523 writeTileData (streamData, ofd, dx, dy, lx, ly, pixelData, pixelDataSize);
524 return;
525 }
526
527 //
528 // If the tiles cannot be written in random order, then check if a
529 // tile with coordinates (dx,dy,lx,ly) has already been buffered.
530 //
531
532 TileCoord currentTile = TileCoord(dx, dy, lx, ly);
533
534 if (ofd->tileMap.find (currentTile) != ofd->tileMap.end())
535 {
536 THROW (IEX_NAMESPACE::ArgExc,
537 "Attempt to write tile "
538 "(" << dx << ", " << dy << ", " << lx << ", " << ly << ") "
539 "more than once.");
540 }
541
542 //
543 // If all the tiles before this one have already been written to the file,
544 // then write this tile immediately and check if we have buffered tiles
545 // that can be written after this tile.
546 //
547 // Otherwise, buffer the tile so it can be written to file later.
548 //
549
550 if (ofd->nextTileToWrite == currentTile)
551 {
552 writeTileData (streamData, ofd, dx, dy, lx, ly, pixelData, pixelDataSize);
553 ofd->nextTileToWrite = ofd->nextTileCoord (ofd->nextTileToWrite);
554
555 TileMap::iterator i = ofd->tileMap.find (ofd->nextTileToWrite);
556
557 //
558 // Step through the tiles and write all successive buffered tiles after
559 // the current one.
560 //
561
562 while(i != ofd->tileMap.end())
563 {
564 //
565 // Write the tile, and then delete the tile's buffered data
566 //
567
568 writeTileData (streamData,
569 ofd,
570 i->first.dx, i->first.dy,
571 i->first.lx, i->first.ly,
572 i->second->pixelData,
573 i->second->pixelDataSize);
574
575 delete i->second;
576 ofd->tileMap.erase (i);
577
578 //
579 // Proceed to the next tile
580 //
581
582 ofd->nextTileToWrite = ofd->nextTileCoord (ofd->nextTileToWrite);
583 i = ofd->tileMap.find (ofd->nextTileToWrite);
584 }
585 }
586 else
587 {
588 //
589 // Create a new BufferedTile, copy the pixelData into it, and
590 // insert it into the tileMap.
591 //
592
593 ofd->tileMap[currentTile] =
594 new BufferedTile ((const char *)pixelData, pixelDataSize);
595 }
596 }
597
598
599 void
convertToXdr(TiledOutputFile::Data * ofd,Array<char> & tileBuffer,int numScanLines,int numPixelsPerScanLine)600 convertToXdr (TiledOutputFile::Data *ofd,
601 Array<char>& tileBuffer,
602 int numScanLines,
603 int numPixelsPerScanLine)
604 {
605 //
606 // Convert the contents of a TiledOutputFile's tileBuffer from the
607 // machine's native representation to Xdr format. This function is called
608 // by writeTile(), below, if the compressor wanted its input pixel data
609 // in the machine's native format, but then failed to compress the data
610 // (most compressors will expand rather than compress random input data).
611 //
612 // Note that this routine assumes that the machine's native representation
613 // of the pixel data has the same size as the Xdr representation. This
614 // makes it possible to convert the pixel data in place, without an
615 // intermediate temporary buffer.
616 //
617
618 //
619 // Set these to point to the start of the tile.
620 // We will write to toPtr, and read from fromPtr.
621 //
622
623 char *writePtr = tileBuffer;
624 const char *readPtr = writePtr;
625
626 //
627 // Iterate over all scan lines in the tile.
628 //
629
630 for (int y = 0; y < numScanLines; ++y)
631 {
632 //
633 // Iterate over all slices in the file.
634 //
635
636 for (unsigned int i = 0; i < ofd->slices.size(); ++i)
637 {
638 const TOutSliceInfo &slice = ofd->slices[i];
639
640 //
641 // Convert the samples in place.
642 //
643
644 convertInPlace (writePtr, readPtr, slice.type,
645 numPixelsPerScanLine);
646 }
647 }
648
649 #ifdef DEBUG
650
651 assert (writePtr == readPtr);
652
653 #endif
654 }
655
656
657 //
658 // A TileBufferTask encapsulates the task of copying a tile from
659 // the user's framebuffer into a LineBuffer and compressing the data
660 // if necessary.
661 //
662
663 class TileBufferTask: public Task
664 {
665 public:
666
667 TileBufferTask (TaskGroup *group,
668 TiledOutputFile::Data *ofd,
669 int number,
670 int dx, int dy,
671 int lx, int ly);
672
673 virtual ~TileBufferTask ();
674
675 virtual void execute ();
676
677 private:
678
679 TiledOutputFile::Data * _ofd;
680 TileBuffer * _tileBuffer;
681 };
682
683
TileBufferTask(TaskGroup * group,TiledOutputFile::Data * ofd,int number,int dx,int dy,int lx,int ly)684 TileBufferTask::TileBufferTask
685 (TaskGroup *group,
686 TiledOutputFile::Data *ofd,
687 int number,
688 int dx, int dy,
689 int lx, int ly)
690 :
691 Task (group),
692 _ofd (ofd),
693 _tileBuffer (_ofd->getTileBuffer (number))
694 {
695 //
696 // Wait for the tileBuffer to become available
697 //
698
699 _tileBuffer->wait ();
700 _tileBuffer->tileCoord = TileCoord (dx, dy, lx, ly);
701 }
702
703
~TileBufferTask()704 TileBufferTask::~TileBufferTask ()
705 {
706 //
707 // Signal that the tile buffer is now free
708 //
709
710 _tileBuffer->post ();
711 }
712
713
714 void
execute()715 TileBufferTask::execute ()
716 {
717 try
718 {
719 //
720 // First copy the pixel data from the frame buffer
721 // into the tile buffer
722 //
723 // Convert one tile's worth of pixel data to
724 // a machine-independent representation, and store
725 // the result in _tileBuffer->buffer.
726 //
727
728 char *writePtr = _tileBuffer->buffer;
729
730 Box2i tileRange = dataWindowForTile (_ofd->tileDesc,
731 _ofd->minX, _ofd->maxX,
732 _ofd->minY, _ofd->maxY,
733 _tileBuffer->tileCoord.dx,
734 _tileBuffer->tileCoord.dy,
735 _tileBuffer->tileCoord.lx,
736 _tileBuffer->tileCoord.ly);
737
738 int numScanLines = tileRange.max.y - tileRange.min.y + 1;
739 int numPixelsPerScanLine = tileRange.max.x - tileRange.min.x + 1;
740
741 //
742 // Iterate over the scan lines in the tile.
743 //
744
745 for (int y = tileRange.min.y; y <= tileRange.max.y; ++y)
746 {
747 //
748 // Iterate over all image channels.
749 //
750
751 for (unsigned int i = 0; i < _ofd->slices.size(); ++i)
752 {
753 const TOutSliceInfo &slice = _ofd->slices[i];
754
755 //
756 // These offsets are used to facilitate both absolute
757 // and tile-relative pixel coordinates.
758 //
759
760 int xOffset = slice.xTileCoords * tileRange.min.x;
761 int yOffset = slice.yTileCoords * tileRange.min.y;
762
763 //
764 // Fill the tile buffer with pixel data.
765 //
766
767 if (slice.zero)
768 {
769 //
770 // The frame buffer contains no data for this channel.
771 // Store zeroes in _data->tileBuffer.
772 //
773
774 fillChannelWithZeroes (writePtr, _ofd->format, slice.type,
775 numPixelsPerScanLine);
776 }
777 else
778 {
779 //
780 // The frame buffer contains data for this channel.
781 //
782
783 const char *readPtr = slice.base +
784 (y - yOffset) * slice.yStride +
785 (tileRange.min.x - xOffset) *
786 slice.xStride;
787
788 const char *endPtr = readPtr +
789 (numPixelsPerScanLine - 1) *
790 slice.xStride;
791
792 copyFromFrameBuffer (writePtr, readPtr, endPtr,
793 slice.xStride, _ofd->format,
794 slice.type);
795 }
796 }
797 }
798
799 //
800 // Compress the contents of the tileBuffer,
801 // and store the compressed data in the output file.
802 //
803
804 _tileBuffer->dataSize = writePtr - _tileBuffer->buffer;
805 _tileBuffer->dataPtr = _tileBuffer->buffer;
806
807 if (_tileBuffer->compressor)
808 {
809 const char *compPtr;
810
811 int compSize = _tileBuffer->compressor->compressTile
812 (_tileBuffer->dataPtr,
813 _tileBuffer->dataSize,
814 tileRange, compPtr);
815
816 if (compSize < _tileBuffer->dataSize)
817 {
818 _tileBuffer->dataSize = compSize;
819 _tileBuffer->dataPtr = compPtr;
820 }
821 else if (_ofd->format == Compressor::NATIVE)
822 {
823 //
824 // The data did not shrink during compression, but
825 // we cannot write to the file using native format,
826 // so we need to convert the lineBuffer to Xdr.
827 //
828
829 convertToXdr (_ofd, _tileBuffer->buffer, numScanLines,
830 numPixelsPerScanLine);
831 }
832 }
833 }
834 catch (std::exception &e)
835 {
836 if (!_tileBuffer->hasException)
837 {
838 _tileBuffer->exception = e.what ();
839 _tileBuffer->hasException = true;
840 }
841 }
842 catch (...)
843 {
844 if (!_tileBuffer->hasException)
845 {
846 _tileBuffer->exception = "unrecognized exception";
847 _tileBuffer->hasException = true;
848 }
849 }
850 }
851
852 } // namespace
853
854
TiledOutputFile(const char fileName[],const Header & header,int numThreads)855 TiledOutputFile::TiledOutputFile
856 (const char fileName[],
857 const Header &header,
858 int numThreads)
859 :
860 _data (new Data (numThreads)),
861 _streamData (new OutputStreamMutex()),
862 _deleteStream (true)
863 {
864 try
865 {
866 header.sanityCheck (true);
867 _streamData->os = new StdOFStream (fileName);
868 _data->multipart=false; // since we opened with one header we can't be multipart
869 initialize (header);
870 _streamData->currentPosition = _streamData->os->tellp();
871
872 // Write header and empty offset table to the file.
873 writeMagicNumberAndVersionField(*_streamData->os, _data->header);
874 _data->previewPosition = _data->header.writeTo (*_streamData->os, true);
875 _data->tileOffsetsPosition = _data->tileOffsets.writeTo (*_streamData->os);
876 }
877 catch (IEX_NAMESPACE::BaseExc &e)
878 {
879 delete _streamData;
880 delete _data;
881
882 REPLACE_EXC (e, "Cannot open image file "
883 "\"" << fileName << "\". " << e);
884 throw;
885 }
886 catch (...)
887 {
888 delete _streamData;
889 delete _data;
890 throw;
891 }
892 }
893
894
TiledOutputFile(OPENEXR_IMF_INTERNAL_NAMESPACE::OStream & os,const Header & header,int numThreads)895 TiledOutputFile::TiledOutputFile
896 (OPENEXR_IMF_INTERNAL_NAMESPACE::OStream &os,
897 const Header &header,
898 int numThreads)
899 :
900 _data (new Data (numThreads)),
901 _streamData (new OutputStreamMutex()),
902 _deleteStream (false)
903 {
904 try
905 {
906 header.sanityCheck(true);
907 _streamData->os = &os;
908 _data->multipart=false; // since we opened with one header we can't be multipart
909 initialize (header);
910 _streamData->currentPosition = _streamData->os->tellp();
911
912 // Write header and empty offset table to the file.
913 writeMagicNumberAndVersionField(*_streamData->os, _data->header);
914 _data->previewPosition = _data->header.writeTo (*_streamData->os, true);
915 _data->tileOffsetsPosition = _data->tileOffsets.writeTo (*_streamData->os);
916
917 }
918 catch (IEX_NAMESPACE::BaseExc &e)
919 {
920 delete _streamData;
921 delete _data;
922
923 REPLACE_EXC (e, "Cannot open image file "
924 "\"" << os.fileName() << "\". " << e);
925 throw;
926 }
927 catch (...)
928 {
929 delete _streamData;
930 delete _data;
931 throw;
932 }
933 }
934
TiledOutputFile(const OutputPartData * part)935 TiledOutputFile::TiledOutputFile(const OutputPartData* part) :
936 _deleteStream (false)
937 {
938 try
939 {
940 if (part->header.type() != TILEDIMAGE)
941 throw IEX_NAMESPACE::ArgExc("Can't build a TiledOutputFile from a type-mismatched part.");
942
943 _streamData = part->mutex;
944 _data = new Data(part->numThreads);
945 _data->multipart=part->multipart;
946 initialize(part->header);
947 _data->partNumber = part->partNumber;
948 _data->tileOffsetsPosition = part->chunkOffsetTablePosition;
949 _data->previewPosition = part->previewPosition;
950 }
951 catch (IEX_NAMESPACE::BaseExc &e)
952 {
953 delete _data;
954
955 REPLACE_EXC (e, "Cannot initialize output part "
956 "\"" << part->partNumber << "\". " << e);
957 throw;
958 }
959 catch (...)
960 {
961 delete _data;
962 throw;
963 }
964 }
965
966 void
initialize(const Header & header)967 TiledOutputFile::initialize (const Header &header)
968 {
969 _data->header = header;
970 _data->lineOrder = _data->header.lineOrder();
971
972
973
974 //
975 // Check that the file is indeed tiled
976 //
977
978 _data->tileDesc = _data->header.tileDescription();
979
980
981 //
982 // 'Fix' the type attribute if it exists but is incorrectly set
983 // (attribute is optional, but ensure it is correct if it exists)
984 //
985 if(_data->header.hasType())
986 {
987 _data->header.setType(TILEDIMAGE);
988 }
989
990
991 //
992 // Save the dataWindow information
993 //
994
995 const Box2i &dataWindow = _data->header.dataWindow();
996 _data->minX = dataWindow.min.x;
997 _data->maxX = dataWindow.max.x;
998 _data->minY = dataWindow.min.y;
999 _data->maxY = dataWindow.max.y;
1000
1001 //
1002 // Precompute level and tile information to speed up utility functions
1003 //
1004
1005 precalculateTileInfo (_data->tileDesc,
1006 _data->minX, _data->maxX,
1007 _data->minY, _data->maxY,
1008 _data->numXTiles, _data->numYTiles,
1009 _data->numXLevels, _data->numYLevels);
1010
1011 //
1012 // Determine the first tile coordinate that we will be writing
1013 // if the file is not RANDOM_Y.
1014 //
1015
1016 _data->nextTileToWrite = (_data->lineOrder == INCREASING_Y)?
1017 TileCoord (0, 0, 0, 0):
1018 TileCoord (0, _data->numYTiles[0] - 1, 0, 0);
1019
1020 _data->maxBytesPerTileLine =
1021 calculateBytesPerPixel (_data->header) * _data->tileDesc.xSize;
1022
1023 _data->tileBufferSize = _data->maxBytesPerTileLine * _data->tileDesc.ySize;
1024
1025 //
1026 // Create all the TileBuffers and allocate their internal buffers
1027 //
1028
1029 for (size_t i = 0; i < _data->tileBuffers.size(); i++)
1030 {
1031 _data->tileBuffers[i] = new TileBuffer (newTileCompressor
1032 (_data->header.compression(),
1033 _data->maxBytesPerTileLine,
1034 _data->tileDesc.ySize,
1035 _data->header));
1036
1037 _data->tileBuffers[i]->buffer.resizeErase(_data->tileBufferSize);
1038 }
1039
1040 _data->format = defaultFormat (_data->tileBuffers[0]->compressor);
1041
1042 _data->tileOffsets = TileOffsets (_data->tileDesc.mode,
1043 _data->numXLevels,
1044 _data->numYLevels,
1045 _data->numXTiles,
1046 _data->numYTiles);
1047 }
1048
1049
~TiledOutputFile()1050 TiledOutputFile::~TiledOutputFile ()
1051 {
1052 if (_data)
1053 {
1054 {
1055 Lock lock(*_streamData);
1056 Int64 originalPosition = _streamData->os->tellp();
1057
1058 if (_data->tileOffsetsPosition > 0)
1059 {
1060 try
1061 {
1062 _streamData->os->seekp (_data->tileOffsetsPosition);
1063 _data->tileOffsets.writeTo (*_streamData->os);
1064
1065 //
1066 // Restore the original position.
1067 //
1068 _streamData->os->seekp (originalPosition);
1069 }
1070 catch (...)
1071 {
1072 //
1073 // We cannot safely throw any exceptions from here.
1074 // This destructor may have been called because the
1075 // stack is currently being unwound for another
1076 // exception.
1077 //
1078 }
1079 }
1080 }
1081
1082 if (_deleteStream && _streamData)
1083 delete _streamData->os;
1084
1085 if (_data->partNumber == -1)
1086 delete _streamData;
1087
1088 delete _data;
1089 }
1090 }
1091
1092
1093 const char *
fileName() const1094 TiledOutputFile::fileName () const
1095 {
1096 return _streamData->os->fileName();
1097 }
1098
1099
1100 const Header &
header() const1101 TiledOutputFile::header () const
1102 {
1103 return _data->header;
1104 }
1105
1106
1107 void
setFrameBuffer(const FrameBuffer & frameBuffer)1108 TiledOutputFile::setFrameBuffer (const FrameBuffer &frameBuffer)
1109 {
1110 Lock lock (*_streamData);
1111
1112 //
1113 // Check if the new frame buffer descriptor
1114 // is compatible with the image file header.
1115 //
1116
1117 const ChannelList &channels = _data->header.channels();
1118
1119 for (ChannelList::ConstIterator i = channels.begin();
1120 i != channels.end();
1121 ++i)
1122 {
1123 FrameBuffer::ConstIterator j = frameBuffer.find (i.name());
1124
1125 if (j == frameBuffer.end())
1126 continue;
1127
1128 if (i.channel().type != j.slice().type)
1129 THROW (IEX_NAMESPACE::ArgExc, "Pixel type of \"" << i.name() << "\" channel "
1130 "of output file \"" << fileName() << "\" is "
1131 "not compatible with the frame buffer's "
1132 "pixel type.");
1133
1134 if (j.slice().xSampling != 1 || j.slice().ySampling != 1)
1135 THROW (IEX_NAMESPACE::ArgExc, "All channels in a tiled file must have"
1136 "sampling (1,1).");
1137 }
1138
1139 //
1140 // Initialize slice table for writePixels().
1141 //
1142
1143 vector<TOutSliceInfo> slices;
1144
1145 for (ChannelList::ConstIterator i = channels.begin();
1146 i != channels.end();
1147 ++i)
1148 {
1149 FrameBuffer::ConstIterator j = frameBuffer.find (i.name());
1150
1151 if (j == frameBuffer.end())
1152 {
1153 //
1154 // Channel i is not present in the frame buffer.
1155 // In the file, channel i will contain only zeroes.
1156 //
1157
1158 slices.push_back (TOutSliceInfo (i.channel().type,
1159 0, // base
1160 0, // xStride,
1161 0, // yStride,
1162 true)); // zero
1163 }
1164 else
1165 {
1166 //
1167 // Channel i is present in the frame buffer.
1168 //
1169
1170 slices.push_back (TOutSliceInfo (j.slice().type,
1171 j.slice().base,
1172 j.slice().xStride,
1173 j.slice().yStride,
1174 false, // zero
1175 (j.slice().xTileCoords)? 1: 0,
1176 (j.slice().yTileCoords)? 1: 0));
1177 }
1178 }
1179
1180 //
1181 // Store the new frame buffer.
1182 //
1183
1184 _data->frameBuffer = frameBuffer;
1185 _data->slices = slices;
1186 }
1187
1188
1189 const FrameBuffer &
frameBuffer() const1190 TiledOutputFile::frameBuffer () const
1191 {
1192 Lock lock (*_streamData);
1193 return _data->frameBuffer;
1194 }
1195
1196
1197 void
writeTiles(int dx1,int dx2,int dy1,int dy2,int lx,int ly)1198 TiledOutputFile::writeTiles (int dx1, int dx2, int dy1, int dy2,
1199 int lx, int ly)
1200 {
1201 try
1202 {
1203 Lock lock (*_streamData);
1204
1205 if (_data->slices.size() == 0)
1206 throw IEX_NAMESPACE::ArgExc ("No frame buffer specified "
1207 "as pixel data source.");
1208
1209 if (!isValidTile (dx1, dy1, lx, ly) || !isValidTile (dx2, dy2, lx, ly))
1210 throw IEX_NAMESPACE::ArgExc ("Tile coordinates are invalid.");
1211
1212 if (!isValidLevel (lx, ly))
1213 THROW (IEX_NAMESPACE::ArgExc,
1214 "Level coordinate "
1215 "(" << lx << ", " << ly << ") "
1216 "is invalid.");
1217 //
1218 // Determine the first and last tile coordinates in both dimensions
1219 // based on the file's lineOrder
1220 //
1221
1222 if (dx1 > dx2)
1223 swap (dx1, dx2);
1224
1225 if (dy1 > dy2)
1226 swap (dy1, dy2);
1227
1228 int dyStart = dy1;
1229 int dyStop = dy2 + 1;
1230 int dY = 1;
1231
1232 if (_data->lineOrder == DECREASING_Y)
1233 {
1234 dyStart = dy2;
1235 dyStop = dy1 - 1;
1236 dY = -1;
1237 }
1238
1239 int numTiles = (dx2 - dx1 + 1) * (dy2 - dy1 + 1);
1240 int numTasks = min ((int)_data->tileBuffers.size(), numTiles);
1241
1242 //
1243 // Create a task group for all tile buffer tasks. When the
1244 // task group goes out of scope, the destructor waits until
1245 // all tasks are complete.
1246 //
1247
1248 {
1249 TaskGroup taskGroup;
1250
1251 //
1252 // Add in the initial compression tasks to the thread pool
1253 //
1254
1255 int nextCompBuffer = 0;
1256 int dxComp = dx1;
1257 int dyComp = dyStart;
1258
1259 while (nextCompBuffer < numTasks)
1260 {
1261 ThreadPool::addGlobalTask (new TileBufferTask (&taskGroup,
1262 _data,
1263 nextCompBuffer++,
1264 dxComp, dyComp,
1265 lx, ly));
1266 dxComp++;
1267
1268 if (dxComp > dx2)
1269 {
1270 dxComp = dx1;
1271 dyComp += dY;
1272 }
1273 }
1274
1275 //
1276 // Write the compressed buffers and add in more compression
1277 // tasks until done
1278 //
1279
1280 int nextWriteBuffer = 0;
1281 int dxWrite = dx1;
1282 int dyWrite = dyStart;
1283
1284 while (nextWriteBuffer < numTiles)
1285 {
1286 //
1287 // Wait until the nextWriteBuffer is ready to be written
1288 //
1289
1290 TileBuffer* writeBuffer =
1291 _data->getTileBuffer (nextWriteBuffer);
1292
1293 writeBuffer->wait();
1294
1295 //
1296 // Write the tilebuffer
1297 //
1298
1299 bufferedTileWrite (_streamData, _data, dxWrite, dyWrite, lx, ly,
1300 writeBuffer->dataPtr,
1301 writeBuffer->dataSize);
1302
1303 //
1304 // Release the lock on nextWriteBuffer
1305 //
1306
1307 writeBuffer->post();
1308
1309 //
1310 // If there are no more tileBuffers to compress, then
1311 // only continue to write out remaining tileBuffers,
1312 // otherwise keep adding compression tasks.
1313 //
1314
1315 if (nextCompBuffer < numTiles)
1316 {
1317 //
1318 // add nextCompBuffer as a compression Task
1319 //
1320
1321 ThreadPool::addGlobalTask
1322 (new TileBufferTask (&taskGroup,
1323 _data,
1324 nextCompBuffer,
1325 dxComp, dyComp,
1326 lx, ly));
1327 }
1328
1329 nextWriteBuffer++;
1330 dxWrite++;
1331
1332 if (dxWrite > dx2)
1333 {
1334 dxWrite = dx1;
1335 dyWrite += dY;
1336 }
1337
1338 nextCompBuffer++;
1339 dxComp++;
1340
1341 if (dxComp > dx2)
1342 {
1343 dxComp = dx1;
1344 dyComp += dY;
1345 }
1346 }
1347
1348 //
1349 // finish all tasks
1350 //
1351 }
1352
1353 //
1354 // Exeption handling:
1355 //
1356 // TileBufferTask::execute() may have encountered exceptions, but
1357 // those exceptions occurred in another thread, not in the thread
1358 // that is executing this call to TiledOutputFile::writeTiles().
1359 // TileBufferTask::execute() has caught all exceptions and stored
1360 // the exceptions' what() strings in the tile buffers.
1361 // Now we check if any tile buffer contains a stored exception; if
1362 // this is the case then we re-throw the exception in this thread.
1363 // (It is possible that multiple tile buffers contain stored
1364 // exceptions. We re-throw the first exception we find and
1365 // ignore all others.)
1366 //
1367
1368 const string *exception = 0;
1369
1370 for (size_t i = 0; i < _data->tileBuffers.size(); ++i)
1371 {
1372 TileBuffer *tileBuffer = _data->tileBuffers[i];
1373
1374 if (tileBuffer->hasException && !exception)
1375 exception = &tileBuffer->exception;
1376
1377 tileBuffer->hasException = false;
1378 }
1379
1380 if (exception)
1381 throw IEX_NAMESPACE::IoExc (*exception);
1382 }
1383 catch (IEX_NAMESPACE::BaseExc &e)
1384 {
1385 REPLACE_EXC (e, "Failed to write pixel data to image "
1386 "file \"" << fileName() << "\". " << e);
1387 throw;
1388 }
1389 }
1390
1391
1392 void
writeTiles(int dx1,int dxMax,int dyMin,int dyMax,int l)1393 TiledOutputFile::writeTiles (int dx1, int dxMax, int dyMin, int dyMax, int l)
1394 {
1395 writeTiles (dx1, dxMax, dyMin, dyMax, l, l);
1396 }
1397
1398
1399 void
writeTile(int dx,int dy,int lx,int ly)1400 TiledOutputFile::writeTile (int dx, int dy, int lx, int ly)
1401 {
1402 writeTiles (dx, dx, dy, dy, lx, ly);
1403 }
1404
1405
1406 void
writeTile(int dx,int dy,int l)1407 TiledOutputFile::writeTile (int dx, int dy, int l)
1408 {
1409 writeTile(dx, dy, l, l);
1410 }
1411
1412
1413 void
copyPixels(TiledInputFile & in)1414 TiledOutputFile::copyPixels (TiledInputFile &in)
1415 {
1416 Lock lock (*_streamData);
1417
1418 //
1419 // Check if this file's and and the InputFile's
1420 // headers are compatible.
1421 //
1422
1423 const Header &hdr = _data->header;
1424 const Header &inHdr = in.header();
1425
1426 if (!hdr.hasTileDescription() || !inHdr.hasTileDescription())
1427 THROW (IEX_NAMESPACE::ArgExc, "Cannot perform a quick pixel copy from image "
1428 "file \"" << in.fileName() << "\" to image "
1429 "file \"" << fileName() << "\". The "
1430 "output file is tiled, but the input file is not. "
1431 "Try using OutputFile::copyPixels() instead.");
1432
1433 if (!(hdr.tileDescription() == inHdr.tileDescription()))
1434 THROW (IEX_NAMESPACE::ArgExc, "Quick pixel copy from image "
1435 "file \"" << in.fileName() << "\" to image "
1436 "file \"" << fileName() << "\" failed. "
1437 "The files have different tile descriptions.");
1438
1439 if (!(hdr.dataWindow() == inHdr.dataWindow()))
1440 THROW (IEX_NAMESPACE::ArgExc, "Cannot copy pixels from image "
1441 "file \"" << in.fileName() << "\" to image "
1442 "file \"" << fileName() << "\". The "
1443 "files have different data windows.");
1444
1445 if (!(hdr.lineOrder() == inHdr.lineOrder()))
1446 THROW (IEX_NAMESPACE::ArgExc, "Quick pixel copy from image "
1447 "file \"" << in.fileName() << "\" to image "
1448 "file \"" << fileName() << "\" failed. "
1449 "The files have different line orders.");
1450
1451 if (!(hdr.compression() == inHdr.compression()))
1452 THROW (IEX_NAMESPACE::ArgExc, "Quick pixel copy from image "
1453 "file \"" << in.fileName() << "\" to image "
1454 "file \"" << fileName() << "\" failed. "
1455 "The files use different compression methods.");
1456
1457 if (!(hdr.channels() == inHdr.channels()))
1458 THROW (IEX_NAMESPACE::ArgExc, "Quick pixel copy from image "
1459 "file \"" << in.fileName() << "\" to image "
1460 "file \"" << fileName() << "\" "
1461 "failed. The files have different channel "
1462 "lists.");
1463
1464 //
1465 // Verify that no pixel data have been written to this file yet.
1466 //
1467
1468 if (!_data->tileOffsets.isEmpty())
1469 THROW (IEX_NAMESPACE::LogicExc, "Quick pixel copy from image "
1470 "file \"" << in.fileName() << "\" to image "
1471 "file \"" << _streamData->os->fileName() << "\" "
1472 "failed. \"" << fileName() << "\" "
1473 "already contains pixel data.");
1474
1475 //
1476 // Calculate the total number of tiles in the file
1477 //
1478
1479 int numAllTiles = 0;
1480
1481 switch (levelMode ())
1482 {
1483 case ONE_LEVEL:
1484 case MIPMAP_LEVELS:
1485
1486 for (int i_l = 0; i_l < numLevels (); ++i_l)
1487 numAllTiles += numXTiles (i_l) * numYTiles (i_l);
1488
1489 break;
1490
1491 case RIPMAP_LEVELS:
1492
1493 for (int i_ly = 0; i_ly < numYLevels (); ++i_ly)
1494 for (int i_lx = 0; i_lx < numXLevels (); ++i_lx)
1495 numAllTiles += numXTiles (i_lx) * numYTiles (i_ly);
1496
1497 break;
1498
1499 default:
1500
1501 throw IEX_NAMESPACE::ArgExc ("Unknown LevelMode format.");
1502 }
1503
1504 bool random_y = _data->lineOrder==RANDOM_Y;
1505
1506 std::vector<int> dx_table(random_y ? numAllTiles : 1);
1507 std::vector<int> dy_table(random_y ? numAllTiles : 1);
1508 std::vector<int> lx_table(random_y ? numAllTiles : 1);
1509 std::vector<int> ly_table(random_y ? numAllTiles : 1);
1510
1511 if(random_y)
1512 {
1513 in.tileOrder(&dx_table[0],&dy_table[0],&lx_table[0],&ly_table[0]);
1514 _data->nextTileToWrite.dx=dx_table[0];
1515 _data->nextTileToWrite.dy=dy_table[0];
1516 _data->nextTileToWrite.lx=lx_table[0];
1517 _data->nextTileToWrite.ly=ly_table[0];
1518 }
1519
1520 for (int i = 0; i < numAllTiles; ++i)
1521 {
1522 const char *pixelData;
1523 int pixelDataSize;
1524
1525 int dx = _data->nextTileToWrite.dx;
1526 int dy = _data->nextTileToWrite.dy;
1527 int lx = _data->nextTileToWrite.lx;
1528 int ly = _data->nextTileToWrite.ly;
1529
1530
1531 in.rawTileData (dx, dy, lx, ly, pixelData, pixelDataSize);
1532 writeTileData (_streamData, _data, dx, dy, lx, ly, pixelData, pixelDataSize);
1533
1534 if(random_y)
1535 {
1536 if(i<numAllTiles-1)
1537 {
1538 _data->nextTileToWrite.dx=dx_table[i+1];
1539 _data->nextTileToWrite.dy=dy_table[i+1];
1540 _data->nextTileToWrite.lx=lx_table[i+1];
1541 _data->nextTileToWrite.ly=ly_table[i+1];
1542 }
1543 }else{
1544 _data->nextTileToWrite=_data->nextTileCoord(_data->nextTileToWrite);
1545 }
1546 }
1547 }
1548
1549
1550 void
copyPixels(InputFile & in)1551 TiledOutputFile::copyPixels (InputFile &in)
1552 {
1553 copyPixels (*in.tFile());
1554 }
1555
1556
1557 void
copyPixels(InputPart & in)1558 TiledOutputFile::copyPixels (InputPart &in)
1559 {
1560 copyPixels (*in.file);
1561 }
1562
1563 void
copyPixels(TiledInputPart & in)1564 TiledOutputFile::copyPixels (TiledInputPart &in)
1565 {
1566 copyPixels (*in.file);
1567 }
1568
1569
1570
1571 unsigned int
tileXSize() const1572 TiledOutputFile::tileXSize () const
1573 {
1574 return _data->tileDesc.xSize;
1575 }
1576
1577
1578 unsigned int
tileYSize() const1579 TiledOutputFile::tileYSize () const
1580 {
1581 return _data->tileDesc.ySize;
1582 }
1583
1584
1585 LevelMode
levelMode() const1586 TiledOutputFile::levelMode () const
1587 {
1588 return _data->tileDesc.mode;
1589 }
1590
1591
1592 LevelRoundingMode
levelRoundingMode() const1593 TiledOutputFile::levelRoundingMode () const
1594 {
1595 return _data->tileDesc.roundingMode;
1596 }
1597
1598
1599 int
numLevels() const1600 TiledOutputFile::numLevels () const
1601 {
1602 if (levelMode() == RIPMAP_LEVELS)
1603 THROW (IEX_NAMESPACE::LogicExc, "Error calling numLevels() on image "
1604 "file \"" << fileName() << "\" "
1605 "(numLevels() is not defined for RIPMAPs).");
1606 return _data->numXLevels;
1607 }
1608
1609
1610 int
numXLevels() const1611 TiledOutputFile::numXLevels () const
1612 {
1613 return _data->numXLevels;
1614 }
1615
1616
1617 int
numYLevels() const1618 TiledOutputFile::numYLevels () const
1619 {
1620 return _data->numYLevels;
1621 }
1622
1623
1624 bool
isValidLevel(int lx,int ly) const1625 TiledOutputFile::isValidLevel (int lx, int ly) const
1626 {
1627 if (lx < 0 || ly < 0)
1628 return false;
1629
1630 if (levelMode() == MIPMAP_LEVELS && lx != ly)
1631 return false;
1632
1633 if (lx >= numXLevels() || ly >= numYLevels())
1634 return false;
1635
1636 return true;
1637 }
1638
1639
1640 int
levelWidth(int lx) const1641 TiledOutputFile::levelWidth (int lx) const
1642 {
1643 try
1644 {
1645 int retVal = levelSize (_data->minX, _data->maxX, lx,
1646 _data->tileDesc.roundingMode);
1647
1648 return retVal;
1649 }
1650 catch (IEX_NAMESPACE::BaseExc &e)
1651 {
1652 REPLACE_EXC (e, "Error calling levelWidth() on image "
1653 "file \"" << fileName() << "\". " << e);
1654 throw;
1655 }
1656 }
1657
1658
1659 int
levelHeight(int ly) const1660 TiledOutputFile::levelHeight (int ly) const
1661 {
1662 try
1663 {
1664 return levelSize (_data->minY, _data->maxY, ly,
1665 _data->tileDesc.roundingMode);
1666 }
1667 catch (IEX_NAMESPACE::BaseExc &e)
1668 {
1669 REPLACE_EXC (e, "Error calling levelHeight() on image "
1670 "file \"" << fileName() << "\". " << e);
1671 throw;
1672 }
1673 }
1674
1675
1676 int
numXTiles(int lx) const1677 TiledOutputFile::numXTiles (int lx) const
1678 {
1679 if (lx < 0 || lx >= _data->numXLevels)
1680 THROW (IEX_NAMESPACE::LogicExc, "Error calling numXTiles() on image "
1681 "file \"" << _streamData->os->fileName() << "\" "
1682 "(Argument is not in valid range).");
1683
1684 return _data->numXTiles[lx];
1685 }
1686
1687
1688 int
numYTiles(int ly) const1689 TiledOutputFile::numYTiles (int ly) const
1690 {
1691 if (ly < 0 || ly >= _data->numYLevels)
1692 THROW (IEX_NAMESPACE::LogicExc, "Error calling numXTiles() on image "
1693 "file \"" << _streamData->os->fileName() << "\" "
1694 "(Argument is not in valid range).");
1695
1696 return _data->numYTiles[ly];
1697 }
1698
1699
1700 Box2i
dataWindowForLevel(int l) const1701 TiledOutputFile::dataWindowForLevel (int l) const
1702 {
1703 return dataWindowForLevel (l, l);
1704 }
1705
1706
1707 Box2i
dataWindowForLevel(int lx,int ly) const1708 TiledOutputFile::dataWindowForLevel (int lx, int ly) const
1709 {
1710 try
1711 {
1712 return OPENEXR_IMF_INTERNAL_NAMESPACE::dataWindowForLevel (
1713 _data->tileDesc,
1714 _data->minX, _data->maxX,
1715 _data->minY, _data->maxY,
1716 lx, ly);
1717 }
1718 catch (IEX_NAMESPACE::BaseExc &e)
1719 {
1720 REPLACE_EXC (e, "Error calling dataWindowForLevel() on image "
1721 "file \"" << fileName() << "\". " << e);
1722 throw;
1723 }
1724 }
1725
1726
1727 Box2i
dataWindowForTile(int dx,int dy,int l) const1728 TiledOutputFile::dataWindowForTile (int dx, int dy, int l) const
1729 {
1730 return dataWindowForTile (dx, dy, l, l);
1731 }
1732
1733
1734 Box2i
dataWindowForTile(int dx,int dy,int lx,int ly) const1735 TiledOutputFile::dataWindowForTile (int dx, int dy, int lx, int ly) const
1736 {
1737 try
1738 {
1739 if (!isValidTile (dx, dy, lx, ly))
1740 throw IEX_NAMESPACE::ArgExc ("Arguments not in valid range.");
1741
1742 return OPENEXR_IMF_INTERNAL_NAMESPACE::dataWindowForTile (
1743 _data->tileDesc,
1744 _data->minX, _data->maxX,
1745 _data->minY, _data->maxY,
1746 dx, dy,
1747 lx, ly);
1748 }
1749 catch (IEX_NAMESPACE::BaseExc &e)
1750 {
1751 REPLACE_EXC (e, "Error calling dataWindowForTile() on image "
1752 "file \"" << fileName() << "\". " << e);
1753 throw;
1754 }
1755 }
1756
1757
1758 bool
isValidTile(int dx,int dy,int lx,int ly) const1759 TiledOutputFile::isValidTile (int dx, int dy, int lx, int ly) const
1760 {
1761 return ((lx < _data->numXLevels && lx >= 0) &&
1762 (ly < _data->numYLevels && ly >= 0) &&
1763 (dx < _data->numXTiles[lx] && dx >= 0) &&
1764 (dy < _data->numYTiles[ly] && dy >= 0));
1765 }
1766
1767
1768 void
updatePreviewImage(const PreviewRgba newPixels[])1769 TiledOutputFile::updatePreviewImage (const PreviewRgba newPixels[])
1770 {
1771 Lock lock (*_streamData);
1772
1773 if (_data->previewPosition <= 0)
1774 THROW (IEX_NAMESPACE::LogicExc, "Cannot update preview image pixels. "
1775 "File \"" << fileName() << "\" does not "
1776 "contain a preview image.");
1777
1778 //
1779 // Store the new pixels in the header's preview image attribute.
1780 //
1781
1782 PreviewImageAttribute &pia =
1783 _data->header.typedAttribute <PreviewImageAttribute> ("preview");
1784
1785 PreviewImage &pi = pia.value();
1786 PreviewRgba *pixels = pi.pixels();
1787 int numPixels = pi.width() * pi.height();
1788
1789 for (int i = 0; i < numPixels; ++i)
1790 pixels[i] = newPixels[i];
1791
1792 //
1793 // Save the current file position, jump to the position in
1794 // the file where the preview image starts, store the new
1795 // preview image, and jump back to the saved file position.
1796 //
1797
1798 Int64 savedPosition = _streamData->os->tellp();
1799
1800 try
1801 {
1802 _streamData->os->seekp (_data->previewPosition);
1803 pia.writeValueTo (*_streamData->os, _data->version);
1804 _streamData->os->seekp (savedPosition);
1805 }
1806 catch (IEX_NAMESPACE::BaseExc &e)
1807 {
1808 REPLACE_EXC (e, "Cannot update preview image pixels for "
1809 "file \"" << fileName() << "\". " << e);
1810 throw;
1811 }
1812 }
1813
1814
1815 void
breakTile(int dx,int dy,int lx,int ly,int offset,int length,char c)1816 TiledOutputFile::breakTile
1817 (int dx, int dy,
1818 int lx, int ly,
1819 int offset,
1820 int length,
1821 char c)
1822 {
1823 Lock lock (*_streamData);
1824
1825 Int64 position = _data->tileOffsets (dx, dy, lx, ly);
1826
1827 if (!position)
1828 THROW (IEX_NAMESPACE::ArgExc,
1829 "Cannot overwrite tile "
1830 "(" << dx << ", " << dy << ", " << lx << "," << ly << "). "
1831 "The tile has not yet been stored in "
1832 "file \"" << fileName() << "\".");
1833
1834 _streamData->currentPosition = 0;
1835 _streamData->os->seekp (position + offset);
1836
1837 for (int i = 0; i < length; ++i)
1838 _streamData->os->write (&c, 1);
1839 }
1840
1841 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
1842