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