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