/src/core/FileFormatHDL.cpp
C++ | 1481 lines | 1081 code | 204 blank | 196 comment | 148 complexity | 3a21d222f96fc2aa4a87335dbfd052f5 MD5 | raw file
Possible License(s): BSD-3-Clause
1/* 2Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al. 3All Rights Reserved. 4 5Redistribution and use in source and binary forms, with or without 6modification, are permitted provided that the following conditions are 7met: 8* Redistributions of source code must retain the above copyright 9 notice, this list of conditions and the following disclaimer. 10* Redistributions in binary form must reproduce the above copyright 11 notice, this list of conditions and the following disclaimer in the 12 documentation and/or other materials provided with the distribution. 13* Neither the name of Sony Pictures Imageworks nor the names of its 14 contributors may be used to endorse or promote products derived from 15 this software without specific prior written permission. 16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27*/ 28 29/* 30 31 Houdini LUTs 32 http://www.sidefx.com/docs/hdk11.0/hdk_io_lut.html 33 34 Types: 35 - 1D Lut (partial support) 36 - 3D Lut 37 - 3D Lut with 1D Prelut 38 39 TODO: 40 - Add support for other 1D types (R, G, B, A, RGB, RGBA, All) 41 we only support type 'C' atm. 42 - Add support for 'Sampling' tag 43 44*/ 45 46#include <cstdio> 47#include <iostream> 48#include <iterator> 49#include <cmath> 50#include <vector> 51#include <string> 52#include <algorithm> 53#include <map> 54 55#include <OpenColorIO/OpenColorIO.h> 56 57#include "FileTransform.h" 58#include "Lut1DOp.h" 59#include "Lut3DOp.h" 60#include "ParseUtils.h" 61#include "MathUtils.h" 62#include "pystring/pystring.h" 63 64OCIO_NAMESPACE_ENTER 65{ 66 namespace 67 { 68 // HDL parser helpers 69 70 // HDL headers/LUT's are shoved into these datatypes 71 typedef std::map<std::string, std::vector<std::string> > StringToStringVecMap; 72 typedef std::map<std::string, std::vector<float> > StringToFloatVecMap; 73 74 void 75 readHeaders(StringToStringVecMap& headers, 76 std::istream& istream) 77 { 78 std::string line; 79 while(nextline(istream, line)) 80 { 81 std::vector<std::string> chunks; 82 83 // Remove trailing/leading whitespace, lower-case and 84 // split into words 85 pystring::split(pystring::lower(pystring::strip(line)), chunks); 86 87 // Skip empty lines 88 if(chunks.empty()) continue; 89 90 // Stop looking for headers at the "LUT:" line 91 if(chunks[0] == "lut:") break; 92 93 // Use first index as key, and remove it from the value 94 std::string key = chunks[0]; 95 chunks.erase(chunks.begin()); 96 97 headers[key] = chunks; 98 } 99 } 100 101 // Try to grab key (e.g "version") from headers. Throws 102 // exception if not found, or if number of chunks in value is 103 // not between min_vals and max_vals (e.g the "length" key 104 // must exist, and must have either 1 or 2 values) 105 std::vector<std::string> 106 findHeaderItem(StringToStringVecMap& headers, 107 const std::string key, 108 const unsigned int min_vals, 109 const unsigned int max_vals) 110 { 111 StringToStringVecMap::iterator iter; 112 iter = headers.find(key); 113 114 // Error if key is not found 115 if(iter == headers.end()) 116 { 117 std::ostringstream os; 118 os << "'" << key << "' line not found"; 119 throw Exception(os.str().c_str()); 120 } 121 122 // Error if incorrect number of values is found 123 if(iter->second.size() < min_vals || 124 iter->second.size() > max_vals) 125 { 126 std::ostringstream os; 127 os << "Incorrect number of chunks (" << iter->second.size() << ")"; 128 os << " after '" << key << "' line, expected "; 129 130 if(min_vals == max_vals) 131 { 132 os << min_vals; 133 } 134 else 135 { 136 os << "between " << min_vals << " and " << max_vals; 137 } 138 139 throw Exception(os.str().c_str()); 140 } 141 142 return iter->second; 143 } 144 145 // Simple wrapper to call findHeaderItem with a fixed number 146 // of values (e.g "version" should have a single value) 147 std::vector<std::string> 148 findHeaderItem(StringToStringVecMap& chunks, 149 const std::string key, 150 const unsigned int numvals) 151 { 152 return findHeaderItem(chunks, key, numvals, numvals); 153 } 154 155 // Crudely parse LUT's - doesn't do any length checking etc, 156 // just grabs a series of floats for Pre{...}, 3d{...} etc 157 // Does some basic error checking, but there are situations 158 // were it could incorrectly accept broken data (like 159 // "Pre{0.0\n1.0}blah"), but hopefully none where it misses 160 // data 161 void 162 readLuts(std::istream& istream, 163 StringToFloatVecMap& lutValues) 164 { 165 // State variables 166 bool inlut = false; 167 std::string lutname; 168 169 std::string word; 170 171 while(istream >> word) 172 { 173 if(!inlut) 174 { 175 if(word == "{") 176 { 177 // Lone "{" is for a 3D 178 inlut = true; 179 lutname = "3d"; 180 } 181 else 182 { 183 // Named lut, e.g "Pre {" 184 inlut = true; 185 lutname = pystring::lower(word); 186 187 // Ensure next word is "{" 188 std::string nextword; 189 istream >> nextword; 190 if(nextword != "{") 191 { 192 std::ostringstream os; 193 os << "Malformed LUT - Unknown word '"; 194 os << word << "' after LUT name '"; 195 os << nextword << "'"; 196 throw Exception(os.str().c_str()); 197 } 198 } 199 } 200 else if(word == "}") 201 { 202 // end of LUT 203 inlut = false; 204 lutname = ""; 205 } 206 else if(inlut) 207 { 208 // StringToFloat was far slower, for 787456 values: 209 // - StringToFloat took 3879 (avg nanoseconds per value) 210 // - stdtod took 169 nanoseconds 211 char* endptr = 0; 212 float v = static_cast<float>(strtod(word.c_str(), &endptr)); 213 214 if(!*endptr) 215 { 216 // Since each word should contain a single 217 // float value, the pointer should be null 218 lutValues[lutname].push_back(v); 219 } 220 else 221 { 222 // stdtod endptr still contained stuff, 223 // meaning an invalid float value 224 std::ostringstream os; 225 os << "Invalid float value in " << lutname; 226 os << " LUT, '" << word << "'"; 227 throw Exception(os.str().c_str()); 228 } 229 } 230 else 231 { 232 std::ostringstream os; 233 os << "Unexpected word, possibly a value outside"; 234 os <<" a LUT {} block. Word was '" << word << "'"; 235 throw Exception(os.str().c_str()); 236 237 } 238 } 239 } 240 241 } // end anonymous "HDL parser helpers" namespace 242 243 namespace 244 { 245 class CachedFileHDL : public CachedFile 246 { 247 public: 248 CachedFileHDL () 249 { 250 hdlversion = "unknown"; 251 hdlformat = "unknown"; 252 hdltype = "unknown"; 253 hdlblack = 0.0; 254 hdlwhite = 1.0; 255 lut1D = Lut1D::Create(); 256 lut3D = Lut3D::Create(); 257 }; 258 ~CachedFileHDL() {}; 259 std::string hdlversion; 260 std::string hdlformat; 261 std::string hdltype; 262 float to_min; // TODO: maybe add this to Lut1DOp? 263 float to_max; // TODO: maybe add this to Lut1DOp? 264 float hdlblack; 265 float hdlwhite; 266 Lut1DRcPtr lut1D; 267 Lut3DRcPtr lut3D; 268 }; 269 typedef OCIO_SHARED_PTR<CachedFileHDL> CachedFileHDLRcPtr; 270 271 class LocalFileFormat : public FileFormat 272 { 273 public: 274 275 ~LocalFileFormat() {}; 276 277 virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const; 278 279 virtual CachedFileRcPtr Read(std::istream & istream) const; 280 281 virtual void Write(const Baker & baker, 282 const std::string & formatName, 283 std::ostream & ostream) const; 284 285 virtual void BuildFileOps(OpRcPtrVec & ops, 286 const Config& config, 287 const ConstContextRcPtr & context, 288 CachedFileRcPtr untypedCachedFile, 289 const FileTransform& fileTransform, 290 TransformDirection dir) const; 291 }; 292 293 void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const 294 { 295 FormatInfo info; 296 info.name = "houdini"; 297 info.extension = "lut"; 298 info.capabilities = FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE; 299 formatInfoVec.push_back(info); 300 } 301 302 CachedFileRcPtr 303 LocalFileFormat::Read(std::istream & istream) const 304 { 305 306 // this shouldn't happen 307 if (!istream) 308 throw Exception ("file stream empty when trying to read Houdini lut"); 309 310 // 311 CachedFileHDLRcPtr cachedFile = CachedFileHDLRcPtr (new CachedFileHDL ()); 312 Lut1DRcPtr lut1d_ptr = Lut1D::Create(); 313 Lut3DRcPtr lut3d_ptr = Lut3D::Create(); 314 315 // Parse headers into key-value pairs 316 StringToStringVecMap header_chunks; 317 StringToStringVecMap::iterator iter; 318 319 // Read headers, ending after the "LUT:" line 320 readHeaders(header_chunks, istream); 321 322 // Grab useful values from headers 323 std::vector<std::string> value; 324 325 // "Version 3" - format version (currently one version 326 // number per LUT type) 327 value = findHeaderItem(header_chunks, "version", 1); 328 cachedFile->hdlversion = value[0]; 329 330 // "Format any" - bit depth of image the LUT should be 331 // applied to (this is basically ignored) 332 value = findHeaderItem(header_chunks, "format", 1); 333 cachedFile->hdlformat = value[0]; 334 335 // "Type 3d" - type of LUT 336 { 337 value = findHeaderItem(header_chunks, "type", 1); 338 339 cachedFile->hdltype = value[0]; 340 } 341 342 // "From 0.0 1.0" - range of input values 343 { 344 float from_min, from_max; 345 346 value = findHeaderItem(header_chunks, "from", 2); 347 348 if(!StringToFloat(&from_min, value[0].c_str()) || 349 !StringToFloat(&from_max, value[1].c_str())) 350 { 351 std::ostringstream os; 352 os << "Invalid float value(s) on 'From' line, '"; 353 os << value[0] << "' and '" << value[1] << "'"; 354 throw Exception(os.str().c_str()); 355 } 356 357 for(int i = 0; i < 3; ++i) 358 { 359 lut1d_ptr->from_min[i] = from_min; 360 lut1d_ptr->from_max[i] = from_max; 361 } 362 } 363 364 365 // "To 0.0 1.0" - range of values in LUT (e.g "0 255" 366 // to specify values as 8-bit numbers, usually "0 1") 367 { 368 float to_min, to_max; 369 370 value = findHeaderItem(header_chunks, "to", 2); 371 372 if(!StringToFloat(&to_min, value[0].c_str()) || 373 !StringToFloat(&to_max, value[1].c_str())) 374 { 375 std::ostringstream os; 376 os << "Invalid float value(s) on 'To' line, '"; 377 os << value[0] << "' and '" << value[1] << "'"; 378 throw Exception(os.str().c_str()); 379 } 380 cachedFile->to_min = to_min; 381 cachedFile->to_max = to_max; 382 } 383 384 // "Black 0" and "White 1" - obsolete options, should be 0 385 // and 1 386 387 { 388 value = findHeaderItem(header_chunks, "black", 1); 389 390 float black; 391 392 if(!StringToFloat(&black, value[0].c_str())) 393 { 394 std::ostringstream os; 395 os << "Invalid float value on 'Black' line, '"; 396 os << value[0] << "'"; 397 throw Exception(os.str().c_str()); 398 } 399 cachedFile->hdlblack = black; 400 } 401 402 { 403 value = findHeaderItem(header_chunks, "white", 1); 404 405 float white; 406 407 if(!StringToFloat(&white, value[0].c_str())) 408 { 409 std::ostringstream os; 410 os << "Invalid float value on 'White' line, '"; 411 os << value[0] << "'"; 412 throw Exception(os.str().c_str()); 413 } 414 cachedFile->hdlwhite = white; 415 } 416 417 418 // Verify type is valid and supported - used to handle 419 // length sensibly, and checking the LUT later 420 { 421 std::string ltype = cachedFile->hdltype; 422 if(ltype != "3d" && ltype != "3d+1d" && ltype != "c") 423 { 424 std::ostringstream os; 425 os << "Unsupported Houdini LUT type: '" << ltype << "'"; 426 throw Exception(os.str().c_str()); 427 } 428 } 429 430 431 // "Length 2" or "Length 2 5" - either "[cube size]", or "[cube 432 // size] [prelut size]" 433 int size_3d = -1; 434 int size_prelut = -1; 435 int size_1d = -1; 436 437 { 438 std::vector<int> lut_sizes; 439 440 value = findHeaderItem(header_chunks, "length", 1, 2); 441 for(unsigned int i = 0; i < value.size(); ++i) 442 { 443 int tmpsize = -1; 444 if(!StringToInt(&tmpsize, value[i].c_str())) 445 { 446 std::ostringstream os; 447 os << "Invalid integer on 'Length' line: "; 448 os << "'" << value[0] << "'"; 449 throw Exception(os.str().c_str()); 450 } 451 lut_sizes.push_back(tmpsize); 452 } 453 454 if(cachedFile->hdltype == "3d" || cachedFile->hdltype == "3d+1d") 455 { 456 // Set cube size 457 size_3d = lut_sizes[0]; 458 459 lut3d_ptr->size[0] = lut_sizes[0]; 460 lut3d_ptr->size[1] = lut_sizes[0]; 461 lut3d_ptr->size[2] = lut_sizes[0]; 462 } 463 464 if(cachedFile->hdltype == "c") 465 { 466 size_1d = lut_sizes[0]; 467 } 468 469 if(cachedFile->hdltype == "3d+1d") 470 { 471 size_prelut = lut_sizes[1]; 472 } 473 } 474 475 // Read stuff after "LUT:" 476 StringToFloatVecMap lut_data; 477 readLuts(istream, lut_data); 478 479 // 480 StringToFloatVecMap::iterator lut_iter; 481 482 if(cachedFile->hdltype == "3d+1d") 483 { 484 // Read prelut, and bind onto cachedFile 485 lut_iter = lut_data.find("pre"); 486 if(lut_iter == lut_data.end()) 487 { 488 std::ostringstream os; 489 os << "3D+1D LUT should contain Pre{} LUT section"; 490 throw Exception(os.str().c_str()); 491 } 492 493 if(size_prelut != static_cast<int>(lut_iter->second.size())) 494 { 495 std::ostringstream os; 496 os << "Pre{} LUT was " << lut_iter->second.size(); 497 os << " values long, expected " << size_prelut << " values"; 498 throw Exception(os.str().c_str()); 499 } 500 501 lut1d_ptr->luts[0] = lut_iter->second; 502 lut1d_ptr->luts[1] = lut_iter->second; 503 lut1d_ptr->luts[2] = lut_iter->second; 504 lut1d_ptr->maxerror = 0.0f; 505 lut1d_ptr->errortype = ERROR_RELATIVE; 506 cachedFile->lut1D = lut1d_ptr; 507 } 508 509 if(cachedFile->hdltype == "3d" || 510 cachedFile->hdltype == "3d+1d") 511 { 512 // Bind 3D LUT to lut3d_ptr, along with some 513 // slightly-elabourate error messages 514 515 lut_iter = lut_data.find("3d"); 516 if(lut_iter == lut_data.end()) 517 { 518 std::ostringstream os; 519 os << "3D LUT section not found"; 520 throw Exception(os.str().c_str()); 521 } 522 523 int size_3d_cubed = size_3d * size_3d * size_3d; 524 525 if(size_3d_cubed * 3 != static_cast<int>(lut_iter->second.size())) 526 { 527 int foundsize = static_cast<int>(lut_iter->second.size()); 528 int foundlines = foundsize / 3; 529 530 std::ostringstream os; 531 os << "3D LUT contains incorrect number of values. "; 532 os << "Contained " << foundsize << " values "; 533 os << "(" << foundlines << " lines), "; 534 os << "expected " << (size_3d_cubed*3) << " values "; 535 os << "(" << size_3d_cubed << " lines)"; 536 throw Exception(os.str().c_str()); 537 } 538 539 lut3d_ptr->lut = lut_iter->second; 540 541 // Bind to cachedFile 542 cachedFile->lut3D = lut3d_ptr; 543 } 544 545 if(cachedFile->hdltype == "c") 546 { 547 // Bind simple 1D RGB LUT 548 lut_iter = lut_data.find("rgb"); 549 if(lut_iter == lut_data.end()) 550 { 551 std::ostringstream os; 552 os << "3D+1D LUT should contain Pre{} LUT section"; 553 throw Exception(os.str().c_str()); 554 } 555 556 if(size_1d != static_cast<int>(lut_iter->second.size())) 557 { 558 std::ostringstream os; 559 os << "RGB{} LUT was " << lut_iter->second.size(); 560 os << " values long, expected " << size_1d << " values"; 561 throw Exception(os.str().c_str()); 562 } 563 564 lut1d_ptr->luts[0] = lut_iter->second; 565 lut1d_ptr->luts[1] = lut_iter->second; 566 lut1d_ptr->luts[2] = lut_iter->second; 567 lut1d_ptr->maxerror = 0.0f; 568 lut1d_ptr->errortype = ERROR_RELATIVE; 569 cachedFile->lut1D = lut1d_ptr; 570 } 571 572 return cachedFile; 573 } 574 575 void LocalFileFormat::Write(const Baker & baker, 576 const std::string & formatName, 577 std::ostream & ostream) const 578 { 579 580 if(formatName != "houdini") 581 { 582 std::ostringstream os; 583 os << "Unknown hdl format name, '"; 584 os << formatName << "'."; 585 throw Exception(os.str().c_str()); 586 } 587 588 // Get config 589 ConstConfigRcPtr config = baker.getConfig(); 590 591 // setup the floating point precision 592 ostream.setf(std::ios::fixed, std::ios::floatfield); 593 ostream.precision(6); 594 595 // Default sizes 596 const int DEFAULT_SHAPER_SIZE = 1024; 597 // MPlay produces bad results with 32^3 cube (in a way 598 // that looks more quantised than even "nearest" 599 // interpolation in OCIOFileTransform) 600 const int DEFAULT_CUBE_SIZE = 64; 601 const int DEFAULT_1D_SIZE = 1024; 602 603 // Get configured sizes 604 int cubeSize = baker.getCubeSize(); 605 int shaperSize = baker.getShaperSize(); 606 // FIXME: Misusing cube size to set 1D LUT size, as it seemed 607 // slightly less confusing than using the shaper LUT size 608 int onedSize = baker.getCubeSize(); 609 610 // Defaults and sanity check on cube size 611 if(cubeSize == -1) cubeSize = DEFAULT_CUBE_SIZE; 612 if(cubeSize < 0) cubeSize = DEFAULT_CUBE_SIZE; 613 if(cubeSize<2) 614 { 615 std::ostringstream os; 616 os << "Cube size must be 2 or larger (was " << cubeSize << ")"; 617 throw Exception(os.str().c_str()); 618 } 619 620 // ..and same for shaper size 621 if(shaperSize<0) shaperSize = DEFAULT_SHAPER_SIZE; 622 if(shaperSize<2) 623 { 624 std::ostringstream os; 625 os << "A shaper space ('" << baker.getShaperSpace() << "') has"; 626 os << " been specified, so the shaper size must be 2 or larger"; 627 throw Exception(os.str().c_str()); 628 } 629 630 // ..and finally, for the 1D LUT size 631 if(onedSize == -1) onedSize = DEFAULT_1D_SIZE; 632 if(onedSize < 2) 633 { 634 std::ostringstream os; 635 os << "1D LUT size must be higher than 2 (was " << onedSize << ")"; 636 throw Exception(os.str().c_str()); 637 } 638 639 // Version numbers 640 const int HDL_1D = 1; // 1D LUT version number 641 const int HDL_3D = 2; // 3D LUT version number 642 const int HDL_3D1D = 3; // 3D LUT with 1D prelut 643 644 // Get spaces from baker 645 const std::string shaperSpace = baker.getShaperSpace(); 646 const std::string inputSpace = baker.getInputSpace(); 647 const std::string targetSpace = baker.getTargetSpace(); 648 const std::string looks = baker.getLooks(); 649 650 // Determine required LUT type 651 ConstProcessorRcPtr inputToTargetProc; 652 if (!looks.empty()) 653 { 654 LookTransformRcPtr transform = LookTransform::Create(); 655 transform->setLooks(looks.c_str()); 656 transform->setSrc(inputSpace.c_str()); 657 transform->setDst(targetSpace.c_str()); 658 inputToTargetProc = config->getProcessor(transform, 659 TRANSFORM_DIR_FORWARD); 660 } 661 else 662 { 663 inputToTargetProc = config->getProcessor( 664 inputSpace.c_str(), 665 targetSpace.c_str()); 666 } 667 668 int required_lut = -1; 669 670 if(inputToTargetProc->hasChannelCrosstalk()) 671 { 672 if(shaperSpace.empty()) 673 { 674 // Has crosstalk, but no prelut, so need 3D LUT 675 required_lut = HDL_3D; 676 } 677 else 678 { 679 // Crosstalk with shaper-space 680 required_lut = HDL_3D1D; 681 } 682 } 683 else 684 { 685 // No crosstalk 686 required_lut = HDL_1D; 687 } 688 689 if(required_lut == -1) 690 { 691 // Unnecessary paranoia 692 throw Exception( 693 "Internal logic error, LUT type was not determined"); 694 } 695 696 // Make prelut 697 std::vector<float> prelutData; 698 699 float fromInStart = 0; // for "From:" part of header 700 float fromInEnd = 1; 701 702 if(required_lut == HDL_3D1D) 703 { 704 // TODO: Later we only grab the green channel for the prelut, 705 // should ensure the prelut is monochromatic somehow? 706 707 ConstProcessorRcPtr inputToShaperProc = config->getProcessor( 708 inputSpace.c_str(), 709 shaperSpace.c_str()); 710 711 if(inputToShaperProc->hasChannelCrosstalk()) 712 { 713 // TODO: Automatically turn shaper into 714 // non-crosstalked version? 715 std::ostringstream os; 716 os << "The specified shaperSpace, '" << baker.getShaperSpace(); 717 os << "' has channel crosstalk, which is not appropriate for"; 718 os << " shapers. Please select an alternate shaper space or"; 719 os << " omit this option."; 720 throw Exception(os.str().c_str()); 721 } 722 723 // Calculate min/max value 724 { 725 // Get input value of 1.0 in shaper space, as this 726 // is the higest value that is transformed by the 727 // cube (e.g for a generic lin-to-log trasnform, 728 // what the log value 1.0 is in linear). 729 ConstProcessorRcPtr shaperToInputProc = config->getProcessor( 730 shaperSpace.c_str(), 731 inputSpace.c_str()); 732 733 float minval[3] = {0.0f, 0.0f, 0.0f}; 734 float maxval[3] = {1.0f, 1.0f, 1.0f}; 735 736 shaperToInputProc->applyRGB(minval); 737 shaperToInputProc->applyRGB(maxval); 738 739 // Grab green channel, as this is the one used later 740 fromInStart = minval[1]; 741 fromInEnd = maxval[1]; 742 } 743 744 // Generate the identity prelut values, then apply the transform. 745 // Prelut is linearly sampled from fromInStart to fromInEnd 746 prelutData.resize(shaperSize*3); 747 748 for (int i = 0; i < shaperSize; ++i) 749 { 750 const float x = (float)(double(i) / double(shaperSize - 1)); 751 float cur_value = lerpf(fromInStart, fromInEnd, x); 752 753 prelutData[3*i+0] = cur_value; 754 prelutData[3*i+1] = cur_value; 755 prelutData[3*i+2] = cur_value; 756 } 757 758 PackedImageDesc prelutImg(&prelutData[0], shaperSize, 1, 3); 759 inputToShaperProc->apply(prelutImg); 760 } 761 762 // TODO: Do same "auto prelut" input-space allocation as FileFormatCSP? 763 764 // Make 3D LUT 765 std::vector<float> cubeData; 766 if(required_lut == HDL_3D || required_lut == HDL_3D1D) 767 { 768 cubeData.resize(cubeSize*cubeSize*cubeSize*3); 769 770 GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_RED); 771 PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3); 772 773 ConstProcessorRcPtr cubeProc; 774 if(required_lut == HDL_3D1D) 775 { 776 // Prelut goes from input-to-shaper, so cube goes from shaper-to-target 777 if (!looks.empty()) 778 { 779 LookTransformRcPtr transform = LookTransform::Create(); 780 transform->setLooks(looks.c_str()); 781 transform->setSrc(inputSpace.c_str()); 782 transform->setDst(targetSpace.c_str()); 783 cubeProc = config->getProcessor(transform, 784 TRANSFORM_DIR_FORWARD); 785 } 786 else 787 { 788 cubeProc = config->getProcessor(shaperSpace.c_str(), 789 targetSpace.c_str()); 790 } 791 } 792 else 793 { 794 // No prelut, so cube goes from input-to-target 795 cubeProc = inputToTargetProc; 796 } 797 798 799 cubeProc->apply(cubeImg); 800 } 801 802 803 // Make 1D LUT 804 std::vector<float> onedData; 805 if(required_lut == HDL_1D) 806 { 807 onedData.resize(onedSize * 3); 808 809 GenerateIdentityLut1D(&onedData[0], onedSize, 3); 810 PackedImageDesc onedImg(&onedData[0], onedSize, 1, 3); 811 812 inputToTargetProc->apply(onedImg); 813 } 814 815 816 // Write the file contents 817 ostream << "Version\t\t" << required_lut << "\n"; 818 ostream << "Format\t\t" << "any" << "\n"; 819 820 ostream << "Type\t\t"; 821 if(required_lut == HDL_1D) 822 ostream << "RGB"; 823 if(required_lut == HDL_3D) 824 ostream << "3D"; 825 if(required_lut == HDL_3D1D) 826 ostream << "3D+1D"; 827 ostream << "\n"; 828 829 ostream << "From\t\t" << fromInStart << " " << fromInEnd << "\n"; 830 ostream << "To\t\t" << 0.0f << " " << 1.0f << "\n"; 831 ostream << "Black\t\t" << 0.0f << "\n"; 832 ostream << "White\t\t" << 1.0f << "\n"; 833 834 if(required_lut == HDL_3D1D) 835 ostream << "Length\t\t" << cubeSize << " " << shaperSize << "\n"; 836 if(required_lut == HDL_3D) 837 ostream << "Length\t\t" << cubeSize << "\n"; 838 if(required_lut == HDL_1D) 839 ostream << "Length\t\t" << onedSize << "\n"; 840 841 ostream << "LUT:\n"; 842 843 // Write prelut 844 if(required_lut == HDL_3D1D) 845 { 846 ostream << "Pre {\n"; 847 for(int i=0; i < shaperSize; ++i) 848 { 849 // Grab green channel from RGB prelut 850 ostream << "\t" << prelutData[i*3+1] << "\n"; 851 } 852 ostream << "}\n"; 853 } 854 855 // Write "3D {" part of output of 3D+1D LUT 856 if(required_lut == HDL_3D1D) 857 { 858 ostream << "3D {\n"; 859 } 860 861 // Write the slightly-different "{" without line for the 3D-only LUT 862 if(required_lut == HDL_3D) 863 { 864 ostream << " {\n"; 865 } 866 867 // Write the cube data after the "{" 868 if(required_lut == HDL_3D || required_lut == HDL_3D1D) 869 { 870 for(int i=0; i < cubeSize*cubeSize*cubeSize; ++i) 871 { 872 // TODO: Original baker code clamped values to 873 // 1.0, was this necessary/desirable? 874 875 ostream << "\t" << cubeData[3*i+0]; 876 ostream << " " << cubeData[3*i+1]; 877 ostream << " " << cubeData[3*i+2] << "\n"; 878 } 879 880 // Write closing "}" 881 ostream << " }\n"; 882 } 883 884 // Write out channels for 1D LUT 885 if(required_lut == HDL_1D) 886 { 887 ostream << "R {\n"; 888 for(int i=0; i < onedSize; ++i) 889 ostream << "\t" << onedData[i*3+0] << "\n"; 890 ostream << "}\n"; 891 892 ostream << "G {\n"; 893 for(int i=0; i < onedSize; ++i) 894 ostream << "\t" << onedData[i*3+1] << "\n"; 895 ostream << "}\n"; 896 897 ostream << "B {\n"; 898 for(int i=0; i < onedSize; ++i) 899 ostream << "\t" << onedData[i*3+2] << "\n"; 900 ostream << "}\n"; 901 } 902 } 903 904 void 905 LocalFileFormat::BuildFileOps(OpRcPtrVec & ops, 906 const Config& /*config*/, 907 const ConstContextRcPtr & /*context*/, 908 CachedFileRcPtr untypedCachedFile, 909 const FileTransform& fileTransform, 910 TransformDirection dir) const 911 { 912 913 CachedFileHDLRcPtr cachedFile = DynamicPtrCast<CachedFileHDL>(untypedCachedFile); 914 915 // This should never happen. 916 if(!cachedFile) 917 { 918 std::ostringstream os; 919 os << "Cannot build Houdini Op. Invalid cache type."; 920 throw Exception(os.str().c_str()); 921 } 922 923 TransformDirection newDir = CombineTransformDirections(dir, 924 fileTransform.getDirection()); 925 926 if(newDir == TRANSFORM_DIR_FORWARD) { 927 if(cachedFile->hdltype == "c") 928 { 929 CreateLut1DOp(ops, cachedFile->lut1D, 930 fileTransform.getInterpolation(), newDir); 931 } 932 else if(cachedFile->hdltype == "3d") 933 { 934 CreateLut3DOp(ops, cachedFile->lut3D, 935 fileTransform.getInterpolation(), newDir); 936 } 937 else if(cachedFile->hdltype == "3d+1d") 938 { 939 CreateLut1DOp(ops, cachedFile->lut1D, 940 fileTransform.getInterpolation(), newDir); 941 CreateLut3DOp(ops, cachedFile->lut3D, 942 fileTransform.getInterpolation(), newDir); 943 } 944 else 945 { 946 throw Exception("Unhandled hdltype while creating forward ops"); 947 } 948 } else if(newDir == TRANSFORM_DIR_INVERSE) { 949 if(cachedFile->hdltype == "c") 950 { 951 CreateLut1DOp(ops, cachedFile->lut1D, 952 fileTransform.getInterpolation(), newDir); 953 } 954 else if(cachedFile->hdltype == "3d") 955 { 956 CreateLut3DOp(ops, cachedFile->lut3D, 957 fileTransform.getInterpolation(), newDir); 958 } 959 else if(cachedFile->hdltype == "3d+1d") 960 { 961 CreateLut3DOp(ops, cachedFile->lut3D, 962 fileTransform.getInterpolation(), newDir); 963 CreateLut1DOp(ops, cachedFile->lut1D, 964 fileTransform.getInterpolation(), newDir); 965 } 966 else 967 { 968 throw Exception("Unhandled hdltype while creating reverse ops"); 969 } 970 } 971 return; 972 } 973 } 974 975 FileFormat * CreateFileFormatHDL() 976 { 977 return new LocalFileFormat(); 978 } 979} 980OCIO_NAMESPACE_EXIT 981 982/////////////////////////////////////////////////////////////////////////////// 983 984#ifdef OCIO_UNIT_TEST 985 986namespace OCIO = OCIO_NAMESPACE; 987#include "UnitTest.h" 988 989OIIO_ADD_TEST(FileFormatHDL, Read1D) 990{ 991 std::ostringstream strebuf; 992 strebuf << "Version\t\t1" << "\n"; 993 strebuf << "Format\t\tany" << "\n"; 994 strebuf << "Type\t\tC" << "\n"; 995 strebuf << "From\t\t0.1 3.2" << "\n"; 996 strebuf << "To\t\t0 1" << "\n"; 997 strebuf << "Black\t\t0" << "\n"; 998 strebuf << "White\t\t0.99" << "\n"; 999 strebuf << "Length\t\t9" << "\n"; 1000 strebuf << "LUT:" << "\n"; 1001 strebuf << "RGB {" << "\n"; 1002 strebuf << "\t0" << "\n"; 1003 strebuf << "\t0.000977517" << "\n"; 1004 strebuf << "\t0.00195503" << "\n"; 1005 strebuf << "\t0.00293255" << "\n"; 1006 strebuf << "\t0.00391007" << "\n"; 1007 strebuf << "\t0.00488759" << "\n"; 1008 strebuf << "\t0.0058651" << "\n"; 1009 strebuf << "\t0.999022" << "\n"; 1010 strebuf << "\t1.67 }" << "\n"; 1011 1012 // 1013 float from_min = 0.1f; 1014 float from_max = 3.2f; 1015 float to_min = 0.0f; 1016 float to_max = 1.0f; 1017 float black = 0.0f; 1018 float white = 0.99f; 1019 float lut1d[9] = { 0.0f, 0.000977517f, 0.00195503f, 0.00293255f, 1020 0.00391007f, 0.00488759f, 0.0058651f, 0.999022f, 1.67f }; 1021 1022 std::istringstream simple3D1D; 1023 simple3D1D.str(strebuf.str()); 1024 1025 // Load file 1026 OCIO::LocalFileFormat tester; 1027 OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D); 1028 OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile); 1029 1030 // 1031 OIIO_CHECK_EQUAL(to_min, lut->to_min); 1032 OIIO_CHECK_EQUAL(to_max, lut->to_max); 1033 OIIO_CHECK_EQUAL(black, lut->hdlblack); 1034 OIIO_CHECK_EQUAL(white, lut->hdlwhite); 1035 1036 // check 1D data (each channel has the same data) 1037 for(int c = 0; c < 3; ++c) { 1038 OIIO_CHECK_EQUAL(from_min, lut->lut1D->from_min[c]); 1039 OIIO_CHECK_EQUAL(from_max, lut->lut1D->from_max[c]); 1040 1041 OIIO_CHECK_EQUAL(9, lut->lut1D->luts[c].size()); 1042 1043 for(unsigned int i = 0; i < lut->lut1D->luts[c].size(); ++i) { 1044 OIIO_CHECK_EQUAL(lut1d[i], lut->lut1D->luts[c][i]); 1045 } 1046 } 1047} 1048 1049OIIO_ADD_TEST(FileFormatHDL, Bake1D) 1050{ 1051 1052 OCIO::ConfigRcPtr config = OCIO::Config::Create(); 1053 1054 // Add lnf space 1055 { 1056 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1057 cs->setName("lnf"); 1058 cs->setFamily("lnf"); 1059 config->addColorSpace(cs); 1060 config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); 1061 } 1062 1063 // Add target space 1064 { 1065 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1066 cs->setName("target"); 1067 cs->setFamily("target"); 1068 OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create(); 1069 1070 float rgb[3] = {0.1f, 0.1f, 0.1f}; 1071 transform1->setOffset(rgb); 1072 1073 cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); 1074 config->addColorSpace(cs); 1075 } 1076 1077 std::string bout = 1078 "Version\t\t1\n" 1079 "Format\t\tany\n" 1080 "Type\t\tRGB\n" 1081 "From\t\t0.000000 1.000000\n" 1082 "To\t\t0.000000 1.000000\n" 1083 "Black\t\t0.000000\n" 1084 "White\t\t1.000000\n" 1085 "Length\t\t10\n" 1086 "LUT:\n" 1087 "R {\n" 1088 "\t0.100000\n" 1089 "\t0.211111\n" 1090 "\t0.322222\n" 1091 "\t0.433333\n" 1092 "\t0.544444\n" 1093 "\t0.655556\n" 1094 "\t0.766667\n" 1095 "\t0.877778\n" 1096 "\t0.988889\n" 1097 "\t1.100000\n" 1098 " }\n" 1099 "G {\n" 1100 "\t0.100000\n" 1101 "\t0.211111\n" 1102 "\t0.322222\n" 1103 "\t0.433333\n" 1104 "\t0.544444\n" 1105 "\t0.655556\n" 1106 "\t0.766667\n" 1107 "\t0.877778\n" 1108 "\t0.988889\n" 1109 "\t1.100000\n" 1110 " }\n" 1111 "B {\n" 1112 "\t0.100000\n" 1113 "\t0.211111\n" 1114 "\t0.322222\n" 1115 "\t0.433333\n" 1116 "\t0.544444\n" 1117 "\t0.655556\n" 1118 "\t0.766667\n" 1119 "\t0.877778\n" 1120 "\t0.988889\n" 1121 "\t1.100000\n" 1122 " }\n"; 1123 1124 // 1125 OCIO::BakerRcPtr baker = OCIO::Baker::Create(); 1126 baker->setConfig(config); 1127 baker->setFormat("houdini"); 1128 baker->setInputSpace("lnf"); 1129 baker->setTargetSpace("target"); 1130 baker->setCubeSize(10); // FIXME: Misusing the cube size to set the 1D LUT size 1131 std::ostringstream output; 1132 baker->bake(output); 1133 1134 //std::cerr << "The LUT: " << std::endl << output.str() << std::endl; 1135 //std::cerr << "Expected:" << std::endl << bout << std::endl; 1136 1137 // 1138 std::vector<std::string> osvec; 1139 OCIO::pystring::splitlines(output.str(), osvec); 1140 std::vector<std::string> resvec; 1141 OCIO::pystring::splitlines(bout, resvec); 1142 OIIO_CHECK_EQUAL(osvec.size(), resvec.size()); 1143 for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i) 1144 OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i])); 1145 1146} 1147 1148OIIO_ADD_TEST(FileFormatHDL, Read3D) 1149{ 1150 std::ostringstream strebuf; 1151 strebuf << "Version 2" << "\n"; 1152 strebuf << "Format any" << "\n"; 1153 strebuf << "Type 3D" << "\n"; 1154 strebuf << "From 0.2 0.9" << "\n"; 1155 strebuf << "To 0.001 0.999" << "\n"; 1156 strebuf << "Black 0.002" << "\n"; 1157 strebuf << "White 0.98" << "\n"; 1158 strebuf << "Length 2" << "\n"; 1159 strebuf << "LUT:" << "\n"; 1160 strebuf << " {" << "\n"; 1161 strebuf << " 0 0 0" << "\n"; 1162 strebuf << " 0 0 0" << "\n"; 1163 strebuf << " 0 0.390735 2.68116e-28" << "\n"; 1164 strebuf << " 0 0.390735 0" << "\n"; 1165 strebuf << " 0 0 0" << "\n"; 1166 strebuf << " 0 0 0.599397" << "\n"; 1167 strebuf << " 0 0.601016 0" << "\n"; 1168 strebuf << " 0 0.601016 0.917034" << "\n"; 1169 strebuf << " }" << "\n"; 1170 1171 std::istringstream simple3D1D; 1172 simple3D1D.str(strebuf.str()); 1173 // Load file 1174 OCIO::LocalFileFormat tester; 1175 OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D); 1176 OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile); 1177 1178 // 1179 //float from_min = 0.2; 1180 //float from_max = 0.9; 1181 float to_min = 0.001f; 1182 float to_max = 0.999f; 1183 float black = 0.002f; 1184 float white = 0.98f; 1185 float cube[2 * 2 * 2 * 3 ] = { 1186 0.f, 0.f, 0.f, 1187 0.f, 0.f, 0.f, 1188 0.f, 0.390735f, 2.68116e-28f, 1189 0.f, 0.390735f, 0.f, 1190 0.f, 0.f, 0.f, 1191 0.f, 0.f, 0.599397f, 1192 0.f, 0.601016f, 0.f, 1193 0.f, 0.601016f, 0.917034f }; 1194 1195 // 1196 OIIO_CHECK_EQUAL(to_min, lut->to_min); 1197 OIIO_CHECK_EQUAL(to_max, lut->to_max); 1198 OIIO_CHECK_EQUAL(black, lut->hdlblack); 1199 OIIO_CHECK_EQUAL(white, lut->hdlwhite); 1200 1201 // check cube data 1202 OIIO_CHECK_EQUAL(2*2*2*3, lut->lut3D->lut.size()); 1203 1204 for(unsigned int i = 0; i < lut->lut3D->lut.size(); ++i) { 1205 OIIO_CHECK_EQUAL(cube[i], lut->lut3D->lut[i]); 1206 } 1207} 1208 1209OIIO_ADD_TEST(FileFormatHDL, Bake3D) 1210{ 1211 OCIO::ConfigRcPtr config = OCIO::Config::Create(); 1212 1213 // Set luma coef's to simple values 1214 { 1215 float lumaCoef[3] = {0.333f, 0.333f, 0.333f}; 1216 config->setDefaultLumaCoefs(lumaCoef); 1217 } 1218 1219 // Add lnf space 1220 { 1221 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1222 cs->setName("lnf"); 1223 cs->setFamily("lnf"); 1224 config->addColorSpace(cs); 1225 config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); 1226 } 1227 1228 // Add target space 1229 { 1230 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1231 cs->setName("target"); 1232 cs->setFamily("target"); 1233 OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create(); 1234 1235 // Set saturation to cause channel crosstalk, making a 3D LUT 1236 transform1->setSat(0.5f); 1237 1238 cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); 1239 config->addColorSpace(cs); 1240 } 1241 1242 std::string bout = 1243 "Version\t\t2\n" 1244 "Format\t\tany\n" 1245 "Type\t\t3D\n" 1246 "From\t\t0.000000 1.000000\n" 1247 "To\t\t0.000000 1.000000\n" 1248 "Black\t\t0.000000\n" 1249 "White\t\t1.000000\n" 1250 "Length\t\t2\n" 1251 "LUT:\n" 1252 " {\n" 1253 "\t0.000000 0.000000 0.000000\n" 1254 "\t0.606300 0.106300 0.106300\n" 1255 "\t0.357600 0.857600 0.357600\n" 1256 "\t0.963900 0.963900 0.463900\n" 1257 "\t0.036100 0.036100 0.536100\n" 1258 "\t0.642400 0.142400 0.642400\n" 1259 "\t0.393700 0.893700 0.893700\n" 1260 "\t1.000000 1.000000 1.000000\n" 1261 " }\n"; 1262 1263 // 1264 OCIO::BakerRcPtr baker = OCIO::Baker::Create(); 1265 baker->setConfig(config); 1266 baker->setFormat("houdini"); 1267 baker->setInputSpace("lnf"); 1268 baker->setTargetSpace("target"); 1269 baker->setCubeSize(2); 1270 std::ostringstream output; 1271 baker->bake(output); 1272 1273 //std::cerr << "The LUT: " << std::endl << output.str() << std::endl; 1274 //std::cerr << "Expected:" << std::endl << bout << std::endl; 1275 1276 // 1277 std::vector<std::string> osvec; 1278 OCIO::pystring::splitlines(output.str(), osvec); 1279 std::vector<std::string> resvec; 1280 OCIO::pystring::splitlines(bout, resvec); 1281 OIIO_CHECK_EQUAL(osvec.size(), resvec.size()); 1282 for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i) 1283 OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i])); 1284} 1285 1286OIIO_ADD_TEST(FileFormatHDL, Read3D1D) 1287{ 1288 std::ostringstream strebuf; 1289 strebuf << "Version 3" << "\n"; 1290 strebuf << "Format any" << "\n"; 1291 strebuf << "Type 3D+1D" << "\n"; 1292 strebuf << "From 0.005478 14.080103" << "\n"; 1293 strebuf << "To 0 1" << "\n"; 1294 strebuf << "Black 0" << "\n"; 1295 strebuf << "White 1" << "\n"; 1296 strebuf << "Length 2 10" << "\n"; 1297 strebuf << "LUT:" << "\n"; 1298 strebuf << "Pre {" << "\n"; 1299 strebuf << " 0.994922" << "\n"; 1300 strebuf << " 0.995052" << "\n"; 1301 strebuf << " 0.995181" << "\n"; 1302 strebuf << " 0.995310" << "\n"; 1303 strebuf << " 0.995439" << "\n"; 1304 strebuf << " 0.995568" << "\n"; 1305 strebuf << " 0.995697" << "\n"; 1306 strebuf << " 0.995826" << "\n"; 1307 strebuf << " 0.995954" << "\n"; 1308 strebuf << " 0.996082" << "\n"; 1309 strebuf << "}" << "\n"; 1310 strebuf << "3D {" << "\n"; 1311 strebuf << " 0.093776 0.093776 0.093776" << "\n"; 1312 strebuf << " 0.105219 0.093776 0.093776" << "\n"; 1313 strebuf << " 0.118058 0.093776 0.093776" << "\n"; 1314 strebuf << " 0.132463 0.093776 0.093776" << "\n"; 1315 strebuf << " 0.148626 0.093776 0.093776" << "\n"; 1316 strebuf << " 0.166761 0.093776 0.093776" << "\n"; 1317 strebuf << " 0.187109 0.093776 0.093776" << "\n"; 1318 strebuf << " 0.209939 0.093776 0.093776" << "\n"; 1319 strebuf << "}" << "\n"; 1320 1321 // 1322 float from_min = 0.005478f; 1323 float from_max = 14.080103f; 1324 float to_min = 0.0f; 1325 float to_max = 1.0f; 1326 float black = 0.0f; 1327 float white = 1.0f; 1328 float prelut[10] = { 0.994922f, 0.995052f, 0.995181f, 0.995310f, 0.995439f, 1329 0.995568f, 0.995697f, 0.995826f, 0.995954f, 0.996082f }; 1330 float cube[2 * 2 * 2 * 3 ] = { 1331 0.093776f, 0.093776f, 0.093776f, 1332 0.105219f, 0.093776f, 0.093776f, 1333 0.118058f, 0.093776f, 0.093776f, 1334 0.132463f, 0.093776f, 0.093776f, 1335 0.148626f, 0.093776f, 0.093776f, 1336 0.166761f, 0.093776f, 0.093776f, 1337 0.187109f, 0.093776f, 0.093776f, 1338 0.209939f, 0.093776f, 0.093776f }; 1339 1340 std::istringstream simple3D1D; 1341 simple3D1D.str(strebuf.str()); 1342 1343 // Load file 1344 OCIO::LocalFileFormat tester; 1345 OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D); 1346 OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile); 1347 1348 // 1349 OIIO_CHECK_EQUAL(to_min, lut->to_min); 1350 OIIO_CHECK_EQUAL(to_max, lut->to_max); 1351 OIIO_CHECK_EQUAL(black, lut->hdlblack); 1352 OIIO_CHECK_EQUAL(white, lut->hdlwhite); 1353 1354 // check prelut data (each channel has the same data) 1355 for(int c = 0; c < 3; ++c) { 1356 OIIO_CHECK_EQUAL(from_min, lut->lut1D->from_min[c]); 1357 OIIO_CHECK_EQUAL(from_max, lut->lut1D->from_max[c]); 1358 OIIO_CHECK_EQUAL(10, lut->lut1D->luts[c].size()); 1359 1360 for(unsigned int i = 0; i < lut->lut1D->luts[c].size(); ++i) { 1361 OIIO_CHECK_EQUAL(prelut[i], lut->lut1D->luts[c][i]); 1362 } 1363 } 1364 1365 OIIO_CHECK_EQUAL(2*2*2*3, lut->lut3D->lut.size()); 1366 1367 // check cube data 1368 for(unsigned int i = 0; i < lut->lut3D->lut.size(); ++i) { 1369 OIIO_CHECK_EQUAL(cube[i], lut->lut3D->lut[i]); 1370 } 1371} 1372 1373OIIO_ADD_TEST(FileFormatHDL, Bake3D1D) 1374{ 1375 // check baker output 1376 OCIO::ConfigRcPtr config = OCIO::Config::Create(); 1377 1378 // Set luma coef's to simple values 1379 { 1380 float lumaCoef[3] = {0.333f, 0.333f, 0.333f}; 1381 config->setDefaultLumaCoefs(lumaCoef); 1382 } 1383 1384 // Add lnf space 1385 { 1386 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1387 cs->setName("lnf"); 1388 cs->setFamily("lnf"); 1389 config->addColorSpace(cs); 1390 config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); 1391 } 1392 1393 // Add shaper space 1394 { 1395 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1396 cs->setName("shaper"); 1397 cs->setFamily("shaper"); 1398 OCIO::ExponentTransformRcPtr transform1 = OCIO::ExponentTransform::Create(); 1399 float test[4] = {2.6f, 2.6f, 2.6f, 1.0f}; 1400 transform1->setValue(test); 1401 cs->setTransform(transform1, OCIO::COLORSPACE_DIR_TO_REFERENCE); 1402 config->addColorSpace(cs); 1403 } 1404 1405 // Add target space 1406 { 1407 OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); 1408 cs->setName("target"); 1409 cs->setFamily("target"); 1410 OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create(); 1411 1412 // Set saturation to cause channel crosstalk, making a 3D LUT 1413 transform1->setSat(0.5f); 1414 1415 cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); 1416 config->addColorSpace(cs); 1417 } 1418 1419 std::string bout = 1420 "Version\t\t3\n" 1421 "Format\t\tany\n" 1422 "Type\t\t3D+1D\n" 1423 "From\t\t0.000000 1.000000\n" 1424 "To\t\t0.000000 1.000000\n" 1425 "Black\t\t0.000000\n" 1426 "White\t\t1.000000\n" 1427 "Length\t\t2 10\n" 1428 "LUT:\n" 1429 "Pre {\n" 1430 "\t0.000000\n" 1431 "\t0.429520\n" 1432 "\t0.560744\n" 1433 "\t0.655378\n" 1434 "\t0.732057\n" 1435 "\t0.797661\n" 1436 "\t0.855604\n" 1437 "\t0.907865\n" 1438 "\t0.955710\n" 1439 "\t1.000000\n" 1440 "}\n" 1441 "3D {\n" 1442 "\t0.000000 0.000000 0.000000\n" 1443 "\t0.606300 0.106300 0.106300\n" 1444 "\t0.357600 0.857600 0.357600\n" 1445 "\t0.963900 0.963900 0.463900\n" 1446 "\t0.036100 0.036100 0.536100\n" 1447 "\t0.642400 0.142400 0.642400\n" 1448 "\t0.393700 0.893700 0.893700\n" 1449 "\t1.000000 1.000000 1.000000\n" 1450 "}\n"; 1451 1452 // 1453 OCIO::BakerRcPtr baker = OCIO::Baker::Create(); 1454 baker->setConfig(config); 1455 baker->setFormat("houdini"); 1456 baker->setInputSpace("lnf"); 1457 baker->setShaperSpace("shaper"); 1458 baker->setTargetSpace("target"); 1459 baker->setShaperSize(10); 1460 baker->setCubeSize(2); 1461 std::ostringstream output; 1462 baker->bake(output); 1463 1464 //std::cerr << "The LUT: " << std::endl << output.str() << std::endl; 1465 //std::cerr << "Expected:" << std::endl << bout << std::endl; 1466 1467 // 1468 std::vector<std::string> osvec; 1469 OCIO::pystring::splitlines(output.str(), osvec); 1470 std::vector<std::string> resvec; 1471 OCIO::pystring::splitlines(bout, resvec); 1472 OIIO_CHECK_EQUAL(osvec.size(), resvec.size()); 1473 1474 // TODO: Get this working on osx 1475 /* 1476 for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i) 1477 OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i])); 1478 */ 1479} 1480 1481#endif // OCIO_BUILD_TESTS