PageRenderTime 85ms CodeModel.GetById 20ms app.highlight 59ms RepoModel.GetById 2ms app.codeStats 0ms

/src/core/FileFormat3DL.cpp

http://github.com/imageworks/OpenColorIO
C++ | 638 lines | 391 code | 101 blank | 146 comment | 56 complexity | fe67625ce00da6a4f9170952147d97d3 MD5 | raw file
  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#include <OpenColorIO/OpenColorIO.h>
 30
 31#include "FileTransform.h"
 32#include "Lut1DOp.h"
 33#include "Lut3DOp.h"
 34#include "MathUtils.h"
 35#include "ParseUtils.h"
 36#include "pystring/pystring.h"
 37
 38#include <algorithm>
 39#include <cmath>
 40#include <cstdio>
 41#include <sstream>
 42
 43/*
 44// Discreet's Flame Lut Format
 45// Use a loose interpretation of the format to allow other 3d luts that look
 46// similar, but dont strictly adhere to the real definition.
 47
 48// If line starts with text or # skip it
 49// If line is a bunch of ints (more than 3) , it's the 1d shaper lut
 50
 51// All remaining lines of size 3 int are data
 52// cube size is determined from num entries
 53// The bit depth of the shaper lut and the 3d lut need not be the same.
 54
 55Example 1, FLAME
 56# Comment here
 570 64 128 192 256 320 384 448 512 576 640 704 768 832 896 960 1023
 58
 590 0 0
 600 0 100
 610 0 200
 62
 63
 64Example 2, LUSTRE
 65#Tokens required by applications - do not edit
 663DMESH
 67Mesh 4 12
 680 64 128 192 256 320 384 448 512 576 640 704 768 832 896 960 1023
 69
 70
 71
 720 17 17
 730 0 88
 740 0 157
 759 101 197
 760 118 308
 77...
 78
 794092 4094 4094
 80
 81#Tokens required by applications - do not edit
 82
 83LUT8
 84gamma 1.0
 85
 86In this example, the 3D LUT has an input bit depth of 4 bits and an output 
 87bit depth of 12 bits. You use the input value to calculate the RGB triplet 
 88to be 17*17*17 (where 17=(2 to the power of 4)+1, and 4 is the input bit 
 89depth). The first triplet is the output value at (0,0,0);(0,0,1);...;
 90(0,0,16) r,g,b coordinates; the second triplet is the output value at 
 91(0,1,0);(0,1,1);...;(0,1,16) r,g,b coordinates; and so on. You use the output 
 92bit depth to set the output bit depth range (12 bits or 0-4095).
 93NoteLustre supports an input and output depth of 16 bits for 3D LUTs; however, 
 94in the processing pipeline, the BLACK_LEVEL to WHITE_LEVEL range is only 14 
 95bits. This means that even if the 3D LUT is 16 bits, it is normalized to 
 96fit the BLACK_LEVEL to WHITE_LEVEL range of Lustre.
 97In Lustre, 3D LUT files can contain grids of 17 cubed, 33 cubed, and 65 cubed; 
 98however, Lustre converts 17 cubed and 65 cubed grids to 33 cubed for internal 
 99processing on the output (for rendering and calibration), but not on the input 
1003D LUT.
101*/
102
103
104OCIO_NAMESPACE_ENTER
105{
106    ////////////////////////////////////////////////////////////////
107    
108    namespace
109    {
110        class LocalCachedFile : public CachedFile
111        {
112        public:
113            LocalCachedFile () : 
114                has1D(false),
115                has3D(false)
116            {
117                lut1D = Lut1D::Create();
118                lut3D = Lut3D::Create();
119            };
120            ~LocalCachedFile() {};
121            
122            bool has1D;
123            bool has3D;
124            Lut1DRcPtr lut1D;
125            Lut3DRcPtr lut3D;
126        };
127        
128        typedef OCIO_SHARED_PTR<LocalCachedFile> LocalCachedFileRcPtr;
129        
130        
131        
132        class LocalFileFormat : public FileFormat
133        {
134        public:
135            
136            ~LocalFileFormat() {};
137            
138            virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const;
139            
140            virtual CachedFileRcPtr Read(std::istream & istream) const;
141            
142            virtual void Write(const Baker & baker,
143                               const std::string & formatName,
144                               std::ostream & ostream) const;
145            
146            virtual void BuildFileOps(OpRcPtrVec & ops,
147                                      const Config& config,
148                                      const ConstContextRcPtr & context,
149                                      CachedFileRcPtr untypedCachedFile,
150                                      const FileTransform& fileTransform,
151                                      TransformDirection dir) const;
152        };
153        
154        
155        
156        
157        
158        
159        // We use the maximum value found in the lut to infer
160        // the bit depth.  While this is fugly. We dont believe
161        // there is a better way, looking at the file, to
162        // determine this.
163        //
164        // Note:  We allow for 2x overshoot in the luts.
165        // As we dont allow for odd bit depths, this isnt a big deal.
166        // So sizes from 1/2 max - 2x max are valid 
167        //
168        // FILE      EXPECTED MAX    CORRECTLY DECODED IF MAX IN THIS RANGE 
169        // 8-bit     255             [0, 511]      
170        // 10-bit    1023            [512, 2047]
171        // 12-bit    4095            [2048, 8191]
172        // 14-bit    16383           [8192, 32767]
173        // 16-bit    65535           [32768, 131071+]
174        
175        int GetLikelyLutBitDepth(int testval)
176        {
177            const int MIN_BIT_DEPTH = 8;
178            const int MAX_BIT_DEPTH = 16;
179            
180            if(testval < 0) return -1;
181            
182            // Only test even bit depths
183            for(int bitDepth = MIN_BIT_DEPTH;
184                bitDepth <= MAX_BIT_DEPTH; bitDepth+=2)
185            {
186                int maxcode = static_cast<int>(pow(2.0,bitDepth));
187                int adjustedMax = maxcode * 2 - 1;
188                if(testval<=adjustedMax) return bitDepth;
189            }
190            
191            return MAX_BIT_DEPTH;
192        }
193        
194        int GetMaxValueFromIntegerBitDepth(int bitDepth)
195        {
196            return static_cast<int>( pow(2.0, bitDepth) ) - 1;
197        }
198        
199        int GetClampedIntFromNormFloat(float val, float scale)
200        {
201            val = std::min(std::max(0.0f, val), 1.0f) * scale;
202            return static_cast<int>(roundf(val));
203        }
204        
205        void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const
206        {
207            FormatInfo info;
208            info.name = "flame";
209            info.extension = "3dl";
210            info.capabilities = (FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE);
211            formatInfoVec.push_back(info);
212            
213            FormatInfo info2 = info;
214            info2.name = "lustre";
215            formatInfoVec.push_back(info2);
216        }
217        
218        // Try and load the format
219        // Raise an exception if it can't be loaded.
220        
221        CachedFileRcPtr LocalFileFormat::Read(std::istream & istream) const
222        {
223            std::vector<int> rawshaper;
224            std::vector<int> raw3d;
225            
226            // Parse the file 3d lut data to an int array
227            {
228                const int MAX_LINE_SIZE = 4096;
229                char lineBuffer[MAX_LINE_SIZE];
230                
231                std::vector<std::string> lineParts;
232                std::vector<int> tmpData;
233                
234                while(istream.good())
235                {
236                    istream.getline(lineBuffer, MAX_LINE_SIZE);
237                    
238                    // Strip and split the line
239                    pystring::split(pystring::strip(lineBuffer), lineParts);
240                    
241                    if(lineParts.empty()) continue;
242                    if((lineParts.size() > 0) && pystring::startswith(lineParts[0],"#")) continue;
243                    
244                    // If we havent found a list of ints, continue
245                    if(!StringVecToIntVec(tmpData, lineParts)) continue;
246                    
247                    // If we've found more than 3 ints, and dont have
248                    // a shaper lut yet, we've got it!
249                    if(tmpData.size()>3 && rawshaper.empty())
250                    {
251                        for(unsigned int i=0; i<tmpData.size(); ++i)
252                        {
253                            rawshaper.push_back(tmpData[i]);
254                        }
255                    }
256                    
257                    // If we've found 3 ints, add it to our 3dlut.
258                    if(tmpData.size() == 3)
259                    {
260                        raw3d.push_back(tmpData[0]);
261                        raw3d.push_back(tmpData[1]);
262                        raw3d.push_back(tmpData[2]);
263                    }
264                }
265            }
266            
267            if(raw3d.empty() && rawshaper.empty())
268            {
269                std::ostringstream os;
270                os << "Error parsing .3dl file.";
271                os << "Does not appear to contain a valid shaper lut or a 3D lut.";
272                throw Exception(os.str().c_str());
273            }
274            
275            LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile());
276            
277            // If all we're doing to parse the format is to read in sets of 3 numbers,
278            // it's possible that other formats will accidentally be able to be read
279            // mistakenly as .3dl files.  We can exclude a huge segement of these mis-reads
280            // by screening for files that use float represenations.  I.e., if the MAX
281            // value of the lut is a small number (such as <128.0) it's likely not an integer
282            // format, and thus not a likely 3DL file.
283            
284            const int FORMAT3DL_CODEVALUE_LOWEST_PLAUSIBLE_MAXINT = 128;
285            
286            // Interpret the shaper lut
287            if(!rawshaper.empty())
288            {
289                cachedFile->has1D = true;
290                
291                // Find the maximum shaper lut value to infer bit-depth
292                int shapermax = 0;
293                for(unsigned int i=0; i<rawshaper.size(); ++i)
294                {
295                    shapermax = std::max(shapermax, rawshaper[i]);
296                }
297                
298                if(shapermax<FORMAT3DL_CODEVALUE_LOWEST_PLAUSIBLE_MAXINT)
299                {
300                    std::ostringstream os;
301                    os << "Error parsing .3dl file.";
302                    os << "The maximum shaper lut value, " << shapermax;
303                    os << ", is unreasonably low. This lut is probably not a .3dl ";
304                    os << "file, but instead a related format that shares a similar ";
305                    os << "structure.";
306                    
307                    throw Exception(os.str().c_str());
308                }
309                
310                int shaperbitdepth = GetLikelyLutBitDepth(shapermax);
311                if(shaperbitdepth<0)
312                {
313                    std::ostringstream os;
314                    os << "Error parsing .3dl file.";
315                    os << "The maximum shaper lut value, " << shapermax;
316                    os << ", does not correspond to any likely bit depth. ";
317                    os << "Please confirm source file is valid.";
318                    throw Exception(os.str().c_str());
319                }
320                
321                int bitdepthmax = GetMaxValueFromIntegerBitDepth(shaperbitdepth);
322                float scale = 1.0f / static_cast<float>(bitdepthmax);
323                
324                for(int channel=0; channel<3; ++channel)
325                {
326                    cachedFile->lut1D->luts[channel].resize(rawshaper.size());
327                    
328                    for(unsigned int i=0; i<rawshaper.size(); ++i)
329                    {
330                        cachedFile->lut1D->luts[channel][i] = static_cast<float>(rawshaper[i])*scale;
331                    }
332                }
333                
334                // The error threshold will be 2 code values. This will allow
335                // shaper luts which use different int conversions (round vs. floor)
336                // to both be optimized.
337                // Required: Abs Tolerance
338                
339                const int FORMAT3DL_SHAPER_CODEVALUE_TOLERANCE = 2;
340                cachedFile->lut1D->maxerror = FORMAT3DL_SHAPER_CODEVALUE_TOLERANCE*scale;
341                cachedFile->lut1D->errortype = ERROR_ABSOLUTE;
342            }
343            
344            
345            
346            // Interpret the parsed data.
347            if(!raw3d.empty())
348            {
349                cachedFile->has3D = true;
350                
351                // Find the maximum shaper lut value to infer bit-depth
352                int lut3dmax = 0;
353                for(unsigned int i=0; i<raw3d.size(); ++i)
354                {
355                    lut3dmax = std::max(lut3dmax, raw3d[i]);
356                }
357                
358                if(lut3dmax<FORMAT3DL_CODEVALUE_LOWEST_PLAUSIBLE_MAXINT)
359                {
360                    std::ostringstream os;
361                    os << "Error parsing .3dl file.";
362                    os << "The maximum 3d lut value, " << lut3dmax;
363                    os << ", is unreasonably low. This lut is probably not a .3dl ";
364                    os << "file, but instead a related format that shares a similar ";
365                    os << "structure.";
366                    
367                    throw Exception(os.str().c_str());
368                }
369                
370                int lut3dbitdepth = GetLikelyLutBitDepth(lut3dmax);
371                if(lut3dbitdepth<0)
372                {
373                    std::ostringstream os;
374                    os << "Error parsing .3dl file.";
375                    os << "The maximum 3d lut value, " << lut3dmax;
376                    os << ", does not correspond to any likely bit depth. ";
377                    os << "Please confirm source file is valid.";
378                    throw Exception(os.str().c_str());
379                }
380                
381                int bitdepthmax = GetMaxValueFromIntegerBitDepth(lut3dbitdepth);
382                float scale = 1.0f / static_cast<float>(bitdepthmax);
383                
384                // Interpret the int array as a 3dlut
385                int lutEdgeLen = Get3DLutEdgeLenFromNumPixels((int)raw3d.size()/3);
386                
387                // Reformat 3D data
388                cachedFile->lut3D->size[0] = lutEdgeLen;
389                cachedFile->lut3D->size[1] = lutEdgeLen;
390                cachedFile->lut3D->size[2] = lutEdgeLen;
391                cachedFile->lut3D->lut.reserve(lutEdgeLen * lutEdgeLen * lutEdgeLen * 3);
392                
393                for(int rIndex=0; rIndex<lutEdgeLen; ++rIndex)
394                {
395                    for(int gIndex=0; gIndex<lutEdgeLen; ++gIndex)
396                    {
397                        for(int bIndex=0; bIndex<lutEdgeLen; ++bIndex)
398                        {
399                            int i = GetLut3DIndex_B(rIndex, gIndex, bIndex,
400                                                    lutEdgeLen, lutEdgeLen, lutEdgeLen);
401                            
402                            cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+0]) * scale);
403                            cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+1]) * scale);
404                            cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+2]) * scale);
405                        }
406                    }
407                }
408            }
409            
410            return cachedFile;
411        }
412        
413        // 65 -> 6
414        // 33 -> 5
415        // 17 -> 4
416        
417        int CubeDimensionLenToLustreBitDepth(int size)
418        {
419            float logval = logf(static_cast<float>(size-1)) / logf(2.0);
420            return static_cast<int>(logval);
421        }
422        
423        void LocalFileFormat::Write(const Baker & baker,
424                                    const std::string & formatName,
425                                    std::ostream & ostream) const
426        {
427            int DEFAULT_CUBE_SIZE = 0;
428            int SHAPER_BIT_DEPTH = 10;
429            int CUBE_BIT_DEPTH = 12;
430            
431            if(formatName == "lustre")
432            {
433                DEFAULT_CUBE_SIZE = 33;
434            }
435            else if(formatName == "flame")
436            {
437                DEFAULT_CUBE_SIZE = 17;
438            }
439            else
440            {
441                std::ostringstream os;
442                os << "Unknown 3dl format name, '";
443                os << formatName << "'.";
444                throw Exception(os.str().c_str());
445            }
446            
447            ConstConfigRcPtr config = baker.getConfig();
448            
449            int cubeSize = baker.getCubeSize();
450            if(cubeSize==-1) cubeSize = DEFAULT_CUBE_SIZE;
451            cubeSize = std::max(2, cubeSize); // smallest cube is 2x2x2
452            
453            int shaperSize = baker.getShaperSize();
454            if(shaperSize==-1) shaperSize = cubeSize;
455            
456            std::vector<float> cubeData;
457            cubeData.resize(cubeSize*cubeSize*cubeSize*3);
458            GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_BLUE);
459            PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3);
460            
461            // Apply our conversion from the input space to the output space.
462            ConstProcessorRcPtr inputToTarget;
463            std::string looks = baker.getLooks();
464            if (!looks.empty())
465            {
466                LookTransformRcPtr transform = LookTransform::Create();
467                transform->setLooks(looks.c_str());
468                transform->setSrc(baker.getInputSpace());
469                transform->setDst(baker.getTargetSpace());
470                inputToTarget = config->getProcessor(transform,
471                    TRANSFORM_DIR_FORWARD);
472            }
473            else
474            {
475              inputToTarget = config->getProcessor(baker.getInputSpace(),
476                  baker.getTargetSpace());
477            }
478            inputToTarget->apply(cubeImg);
479            
480            // Write out the file.
481            // For for maximum compatibility with other apps, we will
482            // not utilize the shaper or output any metadata
483            
484            if(formatName == "lustre")
485            {
486                int meshInputBitDepth = CubeDimensionLenToLustreBitDepth(cubeSize);
487                ostream << "3DMESH\n";
488                ostream << "Mesh " << meshInputBitDepth << " " << CUBE_BIT_DEPTH << "\n";
489            }
490            
491            std::vector<float> shaperData(shaperSize);
492            GenerateIdentityLut1D(&shaperData[0], shaperSize, 1);
493            
494            float shaperScale = static_cast<float>(
495                GetMaxValueFromIntegerBitDepth(SHAPER_BIT_DEPTH));
496            
497            for(unsigned int i=0; i<shaperData.size(); ++i)
498            {
499                if(i != 0) ostream << " ";
500                int val = GetClampedIntFromNormFloat(shaperData[i], shaperScale);
501                ostream << val;
502            }
503            ostream << "\n";
504            
505            // Write out the 3D Cube
506            float cubeScale = static_cast<float>(
507                GetMaxValueFromIntegerBitDepth(CUBE_BIT_DEPTH));
508            if(cubeSize < 2)
509            {
510                throw Exception("Internal cube size exception.");
511            }
512            for(int i=0; i<cubeSize*cubeSize*cubeSize; ++i)
513            {
514                int r = GetClampedIntFromNormFloat(cubeData[3*i+0], cubeScale);
515                int g = GetClampedIntFromNormFloat(cubeData[3*i+1], cubeScale);
516                int b = GetClampedIntFromNormFloat(cubeData[3*i+2], cubeScale);
517                ostream << r << " " << g << " " << b << "\n";
518            }
519            ostream << "\n";
520            
521            if(formatName == "lustre")
522            {
523                ostream << "LUT8\n";
524                ostream << "gamma 1.0\n";
525            }
526        }
527        
528        void
529        LocalFileFormat::BuildFileOps(OpRcPtrVec & ops,
530                                      const Config& /*config*/,
531                                      const ConstContextRcPtr & /*context*/,
532                                      CachedFileRcPtr untypedCachedFile,
533                                      const FileTransform& fileTransform,
534                                      TransformDirection dir) const
535        {
536            LocalCachedFileRcPtr cachedFile = DynamicPtrCast<LocalCachedFile>(untypedCachedFile);
537            
538            // This should never happen.
539            if(!cachedFile)
540            {
541                std::ostringstream os;
542                os << "Cannot build .3dl Op. Invalid cache type.";
543                throw Exception(os.str().c_str());
544            }
545            
546            TransformDirection newDir = CombineTransformDirections(dir,
547                fileTransform.getDirection());
548            if(newDir == TRANSFORM_DIR_UNKNOWN)
549            {
550                std::ostringstream os;
551                os << "Cannot build file format transform,";
552                os << " unspecified transform direction.";
553                throw Exception(os.str().c_str());
554            }
555            
556            // TODO: INTERP_LINEAR should not be hard-coded.
557            // Instead query 'highest' interpolation?
558            // (right now, it's linear). If cubic is added, consider
559            // using it
560            
561            if(newDir == TRANSFORM_DIR_FORWARD)
562            {
563                if(cachedFile->has1D)
564                {
565                    CreateLut1DOp(ops, cachedFile->lut1D,
566                                  INTERP_LINEAR, newDir);
567                }
568                if(cachedFile->has3D)
569                {
570                    CreateLut3DOp(ops, cachedFile->lut3D,
571                                  fileTransform.getInterpolation(), newDir);
572                }
573            }
574            else if(newDir == TRANSFORM_DIR_INVERSE)
575            {
576                if(cachedFile->has3D)
577                {
578                    CreateLut3DOp(ops, cachedFile->lut3D,
579                                  fileTransform.getInterpolation(), newDir);
580                }
581                if(cachedFile->has1D)
582                {
583                    CreateLut1DOp(ops, cachedFile->lut1D,
584                                  INTERP_LINEAR, newDir);
585                }
586            }
587        }
588    }
589    
590    FileFormat * CreateFileFormat3DL()
591    {
592        return new LocalFileFormat();
593    }
594}
595OCIO_NAMESPACE_EXIT
596
597#ifdef OCIO_UNIT_TEST
598
599namespace OCIO = OCIO_NAMESPACE;
600#include "UnitTest.h"
601
602// FILE      EXPECTED MAX    CORRECTLY DECODED IF MAX IN THIS RANGE 
603// 8-bit     255             [0, 511]      
604// 10-bit    1023            [512, 2047]
605// 12-bit    4095            [2048, 8191]
606// 14-bit    16383           [8192, 32767]
607// 16-bit    65535           [32768, 131071]
608
609OIIO_ADD_TEST(FileFormat3DL, GetLikelyLutBitDepth)
610{
611    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(-1), -1);
612    
613    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(0), 8);
614    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(1), 8);
615    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(255), 8);
616    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(256), 8);
617    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(511), 8);
618    
619    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(512), 10);
620    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(1023), 10);
621    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(1024), 10);
622    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(2047), 10);
623    
624    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(2048), 12);
625    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(4095), 12);
626    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(4096), 12);
627    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(8191), 12);
628    
629    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(16383), 14);
630    
631    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(65535), 16);
632    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(65536), 16);
633    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(131071), 16);
634    
635    OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(131072), 16);
636}
637
638#endif // OCIO_UNIT_TEST