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