1 /////////////////////////////////////////////////////////////////////////// 2 // 3 // Copyright (c) 2007, 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 // ACES image file I/O. 38 // 39 //----------------------------------------------------------------------------- 40 41 #include <ImfAcesFile.h> 42 #include <ImfRgbaFile.h> 43 #include <ImfStandardAttributes.h> 44 #include <Iex.h> 45 #include <algorithm> // for std::max() 46 47 using namespace std; 48 using namespace Imath; 49 using namespace Iex; 50 51 namespace Imf { 52 53 54 const Chromaticities & 55 acesChromaticities () 56 { 57 static const Chromaticities acesChr 58 (V2f (0.73470, 0.26530), // red 59 V2f (0.00000, 1.00000), // green 60 V2f (0.00010, -0.07700), // blue 61 V2f (0.32168, 0.33767)); // white 62 63 return acesChr; 64 } 65 66 67 class AcesOutputFile::Data 68 { 69 public: 70 71 Data(); 72 ~Data(); 73 74 RgbaOutputFile * rgbaFile; 75 }; 76 77 78 AcesOutputFile::Data::Data (): 79 rgbaFile (0) 80 { 81 // empty 82 } 83 84 85 AcesOutputFile::Data::~Data () 86 { 87 delete rgbaFile; 88 } 89 90 91 namespace { 92 93 void 94 checkCompression (Compression compression) 95 { 96 // 97 // Not all compression methods are allowed in ACES files. 98 // 99 100 switch (compression) 101 { 102 case NO_COMPRESSION: 103 case PIZ_COMPRESSION: 104 case B44A_COMPRESSION: 105 break; 106 107 default: 108 throw ArgExc ("Invalid compression type for ACES file."); 109 } 110 } 111 112 } // namespace 113 114 115 AcesOutputFile::AcesOutputFile 116 (const std::string &name, 117 const Header &header, 118 RgbaChannels rgbaChannels, 119 int numThreads) 120 : 121 _data (new Data) 122 { 123 checkCompression (header.compression()); 124 125 Header newHeader = header; 126 addChromaticities (newHeader, acesChromaticities()); 127 addAdoptedNeutral (newHeader, acesChromaticities().white); 128 129 _data->rgbaFile = new RgbaOutputFile (name.c_str(), 130 newHeader, 131 rgbaChannels, 132 numThreads); 133 134 _data->rgbaFile->setYCRounding (7, 6); 135 } 136 137 138 AcesOutputFile::AcesOutputFile 139 (OStream &os, 140 const Header &header, 141 RgbaChannels rgbaChannels, 142 int numThreads) 143 : 144 _data (new Data) 145 { 146 checkCompression (header.compression()); 147 148 Header newHeader = header; 149 addChromaticities (newHeader, acesChromaticities()); 150 addAdoptedNeutral (newHeader, acesChromaticities().white); 151 152 _data->rgbaFile = new RgbaOutputFile (os, 153 header, 154 rgbaChannels, 155 numThreads); 156 157 _data->rgbaFile->setYCRounding (7, 6); 158 } 159 160 161 AcesOutputFile::AcesOutputFile 162 (const std::string &name, 163 const Imath::Box2i &displayWindow, 164 const Imath::Box2i &dataWindow, 165 RgbaChannels rgbaChannels, 166 float pixelAspectRatio, 167 const Imath::V2f screenWindowCenter, 168 float screenWindowWidth, 169 LineOrder lineOrder, 170 Compression compression, 171 int numThreads) 172 : 173 _data (new Data) 174 { 175 checkCompression (compression); 176 177 Header newHeader (displayWindow, 178 dataWindow.isEmpty()? displayWindow: dataWindow, 179 pixelAspectRatio, 180 screenWindowCenter, 181 screenWindowWidth, 182 lineOrder, 183 compression); 184 185 addChromaticities (newHeader, acesChromaticities()); 186 addAdoptedNeutral (newHeader, acesChromaticities().white); 187 188 _data->rgbaFile = new RgbaOutputFile (name.c_str(), 189 newHeader, 190 rgbaChannels, 191 numThreads); 192 193 _data->rgbaFile->setYCRounding (7, 6); 194 } 195 196 197 AcesOutputFile::AcesOutputFile 198 (const std::string &name, 199 int width, 200 int height, 201 RgbaChannels rgbaChannels, 202 float pixelAspectRatio, 203 const Imath::V2f screenWindowCenter, 204 float screenWindowWidth, 205 LineOrder lineOrder, 206 Compression compression, 207 int numThreads) 208 : 209 _data (new Data) 210 { 211 checkCompression (compression); 212 213 Header newHeader (width, 214 height, 215 pixelAspectRatio, 216 screenWindowCenter, 217 screenWindowWidth, 218 lineOrder, 219 compression); 220 221 addChromaticities (newHeader, acesChromaticities()); 222 addAdoptedNeutral (newHeader, acesChromaticities().white); 223 224 _data->rgbaFile = new RgbaOutputFile (name.c_str(), 225 newHeader, 226 rgbaChannels, 227 numThreads); 228 229 _data->rgbaFile->setYCRounding (7, 6); 230 } 231 232 233 AcesOutputFile::~AcesOutputFile () 234 { 235 delete _data; 236 } 237 238 239 void 240 AcesOutputFile::setFrameBuffer 241 (const Rgba *base, 242 size_t xStride, 243 size_t yStride) 244 { 245 _data->rgbaFile->setFrameBuffer (base, xStride, yStride); 246 } 247 248 249 void 250 AcesOutputFile::writePixels (int numScanLines) 251 { 252 _data->rgbaFile->writePixels (numScanLines); 253 } 254 255 256 int 257 AcesOutputFile::currentScanLine () const 258 { 259 return _data->rgbaFile->currentScanLine(); 260 } 261 262 263 const Header & 264 AcesOutputFile::header () const 265 { 266 return _data->rgbaFile->header(); 267 } 268 269 270 const Imath::Box2i & 271 AcesOutputFile::displayWindow () const 272 { 273 return _data->rgbaFile->displayWindow(); 274 } 275 276 277 const Imath::Box2i & 278 AcesOutputFile::dataWindow () const 279 { 280 return _data->rgbaFile->dataWindow(); 281 } 282 283 284 float 285 AcesOutputFile::pixelAspectRatio () const 286 { 287 return _data->rgbaFile->pixelAspectRatio(); 288 } 289 290 291 const Imath::V2f 292 AcesOutputFile::screenWindowCenter () const 293 { 294 return _data->rgbaFile->screenWindowCenter(); 295 } 296 297 298 float 299 AcesOutputFile::screenWindowWidth () const 300 { 301 return _data->rgbaFile->screenWindowWidth(); 302 } 303 304 305 LineOrder 306 AcesOutputFile::lineOrder () const 307 { 308 return _data->rgbaFile->lineOrder(); 309 } 310 311 312 Compression 313 AcesOutputFile::compression () const 314 { 315 return _data->rgbaFile->compression(); 316 } 317 318 319 RgbaChannels 320 AcesOutputFile::channels () const 321 { 322 return _data->rgbaFile->channels(); 323 } 324 325 326 void 327 AcesOutputFile::updatePreviewImage (const PreviewRgba pixels[]) 328 { 329 _data->rgbaFile->updatePreviewImage (pixels); 330 } 331 332 333 class AcesInputFile::Data 334 { 335 public: 336 337 Data(); 338 ~Data(); 339 340 void initColorConversion (); 341 342 RgbaInputFile * rgbaFile; 343 344 Rgba * fbBase; 345 size_t fbXStride; 346 size_t fbYStride; 347 int minX; 348 int maxX; 349 350 bool mustConvertColor; 351 M44f fileToAces; 352 }; 353 354 355 AcesInputFile::Data::Data (): 356 rgbaFile (0), 357 fbBase (0), 358 fbXStride (0), 359 fbYStride (0), 360 minX (0), 361 maxX (0), 362 mustConvertColor (false) 363 { 364 // empty 365 } 366 367 368 AcesInputFile::Data::~Data () 369 { 370 delete rgbaFile; 371 } 372 373 374 void 375 AcesInputFile::Data::initColorConversion () 376 { 377 const Header &header = rgbaFile->header(); 378 379 Chromaticities fileChr; 380 381 if (hasChromaticities (header)) 382 fileChr = chromaticities (header); 383 384 V2f fileNeutral = fileChr.white; 385 386 if (hasAdoptedNeutral (header)) 387 fileNeutral = adoptedNeutral (header); 388 389 const Chromaticities acesChr = acesChromaticities(); 390 391 V2f acesNeutral = acesChr.white; 392 393 if (fileChr.red == acesChr.red && 394 fileChr.green == acesChr.green && 395 fileChr.blue == acesChr.blue && 396 fileChr.white == acesChr.white && 397 fileNeutral == acesNeutral) 398 { 399 // 400 // The file already contains ACES data, 401 // color conversion is not necessary. 402 403 return; 404 } 405 406 mustConvertColor = true; 407 minX = header.dataWindow().min.x; 408 maxX = header.dataWindow().max.x; 409 410 // 411 // Create a matrix that transforms colors from the 412 // RGB space of the input file into the ACES space 413 // using a color adaptation transform to move the 414 // white point. 415 // 416 417 // 418 // We'll need the Bradford cone primary matrix and its inverse 419 // 420 421 static const M44f bradfordCPM 422 (0.895100, -0.750200, 0.038900, 0.000000, 423 0.266400, 1.713500, -0.068500, 0.000000, 424 -0.161400, 0.036700, 1.029600, 0.000000, 425 0.000000, 0.000000, 0.000000, 1.000000); 426 427 const static M44f inverseBradfordCPM 428 (0.986993, 0.432305, -0.008529, 0.000000, 429 -0.147054, 0.518360, 0.040043, 0.000000, 430 0.159963, 0.049291, 0.968487, 0.000000, 431 0.000000, 0.000000, 0.000000, 1.000000); 432 433 // 434 // Convert the white points of the two RGB spaces to XYZ 435 // 436 437 float fx = fileNeutral.x; 438 float fy = fileNeutral.y; 439 V3f fileNeutralXYZ (fx / fy, 1, (1 - fx - fy) / fy); 440 441 float ax = acesNeutral.x; 442 float ay = acesNeutral.y; 443 V3f acesNeutralXYZ (ax / ay, 1, (1 - ax - ay) / ay); 444 445 // 446 // Compute the Bradford transformation matrix 447 // 448 449 V3f ratio ((acesNeutralXYZ * bradfordCPM) / 450 (fileNeutralXYZ * bradfordCPM)); 451 452 M44f ratioMat (ratio[0], 0, 0, 0, 453 0, ratio[1], 0, 0, 454 0, 0, ratio[2], 0, 455 0, 0, 0, 1); 456 457 M44f bradfordTrans = bradfordCPM * 458 ratioMat * 459 inverseBradfordCPM; 460 461 // 462 // Build a combined file-RGB-to-ACES-RGB conversion matrix 463 // 464 465 fileToAces = RGBtoXYZ (fileChr, 1) * bradfordTrans * XYZtoRGB (acesChr, 1); 466 } 467 468 469 AcesInputFile::AcesInputFile (const std::string &name, int numThreads): 470 _data (new Data) 471 { 472 _data->rgbaFile = new RgbaInputFile (name.c_str(), numThreads); 473 _data->initColorConversion(); 474 } 475 476 477 AcesInputFile::AcesInputFile (IStream &is, int numThreads): 478 _data (new Data) 479 { 480 _data->rgbaFile = new RgbaInputFile (is, numThreads); 481 _data->initColorConversion(); 482 } 483 484 485 AcesInputFile::~AcesInputFile () 486 { 487 delete _data; 488 } 489 490 491 void 492 AcesInputFile::setFrameBuffer (Rgba *base, size_t xStride, size_t yStride) 493 { 494 _data->rgbaFile->setFrameBuffer (base, xStride, yStride); 495 _data->fbBase = base; 496 _data->fbXStride = xStride; 497 _data->fbYStride = yStride; 498 } 499 500 501 void 502 AcesInputFile::readPixels (int scanLine1, int scanLine2) 503 { 504 // 505 // Copy the pixels from the RgbaInputFile into the frame buffer. 506 // 507 508 _data->rgbaFile->readPixels (scanLine1, scanLine2); 509 510 // 511 // If the RGB space of the input file is not the same as the ACES 512 // RGB space, then the pixels in the frame buffer must be transformed 513 // into the ACES RGB space. 514 // 515 516 if (!_data->mustConvertColor) 517 return; 518 519 int minY = min (scanLine1, scanLine2); 520 int maxY = max (scanLine1, scanLine2); 521 522 for (int y = minY; y <= maxY; ++y) 523 { 524 Rgba *base = _data->fbBase + 525 _data->fbXStride * _data->minX + 526 _data->fbYStride * y; 527 528 for (int x = _data->minX; x <= _data->maxX; ++x) 529 { 530 V3f aces = V3f (base->r, base->g, base->b) * _data->fileToAces; 531 532 base->r = aces[0]; 533 base->g = aces[1]; 534 base->b = aces[2]; 535 536 base += _data->fbXStride; 537 } 538 } 539 } 540 541 542 void 543 AcesInputFile::readPixels (int scanLine) 544 { 545 readPixels (scanLine, scanLine); 546 } 547 548 549 const Header & 550 AcesInputFile::header () const 551 { 552 return _data->rgbaFile->header(); 553 } 554 555 556 const Imath::Box2i & 557 AcesInputFile::displayWindow () const 558 { 559 return _data->rgbaFile->displayWindow(); 560 } 561 562 563 const Imath::Box2i & 564 AcesInputFile::dataWindow () const 565 { 566 return _data->rgbaFile->dataWindow(); 567 } 568 569 570 float 571 AcesInputFile::pixelAspectRatio () const 572 { 573 return _data->rgbaFile->pixelAspectRatio(); 574 } 575 576 577 const Imath::V2f 578 AcesInputFile::screenWindowCenter () const 579 { 580 return _data->rgbaFile->screenWindowCenter(); 581 } 582 583 584 float 585 AcesInputFile::screenWindowWidth () const 586 { 587 return _data->rgbaFile->screenWindowWidth(); 588 } 589 590 591 LineOrder 592 AcesInputFile::lineOrder () const 593 { 594 return _data->rgbaFile->lineOrder(); 595 } 596 597 598 Compression 599 AcesInputFile::compression () const 600 { 601 return _data->rgbaFile->compression(); 602 } 603 604 605 RgbaChannels 606 AcesInputFile::channels () const 607 { 608 return _data->rgbaFile->channels(); 609 } 610 611 612 const char * 613 AcesInputFile::fileName () const 614 { 615 return _data->rgbaFile->fileName(); 616 } 617 618 619 bool 620 AcesInputFile::isComplete () const 621 { 622 return _data->rgbaFile->isComplete(); 623 } 624 625 626 int 627 AcesInputFile::version () const 628 { 629 return _data->rgbaFile->version(); 630 } 631 632 } // namespace Imf 633