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