1 //
2 // SPDX-License-Identifier: BSD-3-Clause
3 // Copyright (c) Contributors to the OpenEXR Project.
4 //
5
6 //-----------------------------------------------------------------------------
7 //
8 // class RgbaOutputFile
9 // class RgbaInputFile
10 //
11 //-----------------------------------------------------------------------------
12
13 #include <ImfRgbaFile.h>
14 #include <ImfOutputFile.h>
15 #include <ImfInputFile.h>
16 #include <ImfChannelList.h>
17 #include <ImfRgbaYca.h>
18 #include <ImfStandardAttributes.h>
19 #include <ImathFun.h>
20 #include <Iex.h>
21 #include <string.h>
22 #include <algorithm>
23 #include <mutex>
24
25 #include "ImfNamespace.h"
26
27 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
28
29 using namespace std;
30 using namespace IMATH_NAMESPACE;
31 using namespace RgbaYca;
32
33 namespace {
34
35 void
insertChannels(Header & header,RgbaChannels rgbaChannels)36 insertChannels (Header &header, RgbaChannels rgbaChannels)
37 {
38 ChannelList ch;
39
40 if (rgbaChannels & (WRITE_Y | WRITE_C))
41 {
42 if (rgbaChannels & WRITE_Y)
43 {
44 ch.insert ("Y", Channel (HALF, 1, 1));
45 }
46
47 if (rgbaChannels & WRITE_C)
48 {
49 ch.insert ("RY", Channel (HALF, 2, 2, true));
50 ch.insert ("BY", Channel (HALF, 2, 2, true));
51 }
52 }
53 else
54 {
55 if (rgbaChannels & WRITE_R)
56 ch.insert ("R", Channel (HALF, 1, 1));
57
58 if (rgbaChannels & WRITE_G)
59 ch.insert ("G", Channel (HALF, 1, 1));
60
61 if (rgbaChannels & WRITE_B)
62 ch.insert ("B", Channel (HALF, 1, 1));
63 }
64
65 if (rgbaChannels & WRITE_A)
66 ch.insert ("A", Channel (HALF, 1, 1));
67
68 header.channels() = ch;
69 }
70
71
72 RgbaChannels
rgbaChannels(const ChannelList & ch,const string & channelNamePrefix="")73 rgbaChannels (const ChannelList &ch, const string &channelNamePrefix = "")
74 {
75 int i = 0;
76
77 if (ch.findChannel (channelNamePrefix + "R"))
78 i |= WRITE_R;
79
80 if (ch.findChannel (channelNamePrefix + "G"))
81 i |= WRITE_G;
82
83 if (ch.findChannel (channelNamePrefix + "B"))
84 i |= WRITE_B;
85
86 if (ch.findChannel (channelNamePrefix + "A"))
87 i |= WRITE_A;
88
89 if (ch.findChannel (channelNamePrefix + "Y"))
90 i |= WRITE_Y;
91
92 if (ch.findChannel (channelNamePrefix + "RY") ||
93 ch.findChannel (channelNamePrefix + "BY"))
94 i |= WRITE_C;
95
96 return RgbaChannels (i);
97 }
98
99
100 string
prefixFromLayerName(const string & layerName,const Header & header)101 prefixFromLayerName (const string &layerName, const Header &header)
102 {
103 if (layerName.empty())
104 return "";
105
106 if (hasMultiView (header) && multiView(header)[0] == layerName)
107 return "";
108
109 return layerName + ".";
110 }
111
112
113 V3f
ywFromHeader(const Header & header)114 ywFromHeader (const Header &header)
115 {
116 Chromaticities cr;
117
118 if (hasChromaticities (header))
119 cr = chromaticities (header);
120
121 return computeYw (cr);
122 }
123
124
125 ptrdiff_t
cachePadding(ptrdiff_t size)126 cachePadding (ptrdiff_t size)
127 {
128 //
129 // Some of the buffers that are allocated by classes ToYca and
130 // FromYca, below, may need to be padded to avoid cache thrashing.
131 // If the difference between the buffer size and the nearest power
132 // of two is less than CACHE_LINE_SIZE, then we add an appropriate
133 // amount of padding.
134 //
135 // CACHE_LINE_SIZE must be a power of two, and it must be at
136 // least as big as the true size of a cache line on the machine
137 // we are running on. (It is ok if CACHE_LINE_SIZE is larger
138 // than a real cache line.)
139 //
140 // CACHE_LINE_SIZE = (1 << LOG2_CACHE_LINE_SIZE)
141 //
142
143 static int LOG2_CACHE_LINE_SIZE = 8;
144
145 size_t i = LOG2_CACHE_LINE_SIZE + 2;
146
147 while ((size >> i) > 1)
148 ++i;
149
150 if (size > (1ll << (i + 1)) - 64ll)
151 return 64ll + ((1ll << (i + 1ll)) - size);
152
153 if (size < (1ll << i) + 64ll)
154 return 64ll + ((1ll << i) - size);
155
156 return 0;
157 }
158
159 } // namespace
160
161
162 class RgbaOutputFile::ToYca: public std::mutex
163 {
164 public:
165
166 ToYca (OutputFile &outputFile, RgbaChannels rgbaChannels);
167 ~ToYca ();
168
169 ToYca (const ToYca& other) = delete;
170 ToYca& operator = (const ToYca& other) = delete;
171 ToYca (ToYca&& other) = delete;
172 ToYca& operator = (ToYca&& other) = delete;
173
174 void setYCRounding (unsigned int roundY,
175 unsigned int roundC);
176
177 void setFrameBuffer (const Rgba *base,
178 size_t xStride,
179 size_t yStride);
180
181 void writePixels (int numScanLines);
182 int currentScanLine () const;
183
184 private:
185
186 void padTmpBuf ();
187 void rotateBuffers ();
188 void duplicateLastBuffer ();
189 void duplicateSecondToLastBuffer ();
190 void decimateChromaVertAndWriteScanLine ();
191
192 OutputFile & _outputFile;
193 bool _writeY;
194 bool _writeC;
195 bool _writeA;
196 int _xMin;
197 int _width;
198 int _height;
199 int _linesConverted;
200 LineOrder _lineOrder;
201 int _currentScanLine;
202 V3f _yw;
203 Rgba * _bufBase;
204 Rgba * _buf[N];
205 Rgba * _tmpBuf;
206 const Rgba * _fbBase;
207 size_t _fbXStride;
208 size_t _fbYStride;
209 int _roundY;
210 int _roundC;
211 };
212
213
ToYca(OutputFile & outputFile,RgbaChannels rgbaChannels)214 RgbaOutputFile::ToYca::ToYca (OutputFile &outputFile,
215 RgbaChannels rgbaChannels)
216 :
217 _outputFile (outputFile)
218 {
219 _writeY = (rgbaChannels & WRITE_Y)? true: false;
220 _writeC = (rgbaChannels & WRITE_C)? true: false;
221 _writeA = (rgbaChannels & WRITE_A)? true: false;
222
223 const Box2i dw = _outputFile.header().dataWindow();
224
225 _xMin = dw.min.x;
226 _width = dw.max.x - dw.min.x + 1;
227 _height = dw.max.y - dw.min.y + 1;
228
229 _linesConverted = 0;
230 _lineOrder = _outputFile.header().lineOrder();
231
232 if (_lineOrder == INCREASING_Y)
233 _currentScanLine = dw.min.y;
234 else
235 _currentScanLine = dw.max.y;
236
237 _yw = ywFromHeader (_outputFile.header());
238
239 ptrdiff_t pad = cachePadding (_width * sizeof (Rgba)) / sizeof (Rgba);
240
241 _bufBase = new Rgba[(_width + pad) * N];
242
243 for (int i = 0; i < N; ++i)
244 _buf[i] = _bufBase + (i * (_width + pad));
245
246 _tmpBuf = new Rgba[_width + N - 1];
247
248 _fbBase = 0;
249 _fbXStride = 0;
250 _fbYStride = 0;
251
252 _roundY = 7;
253 _roundC = 5;
254 }
255
256
~ToYca()257 RgbaOutputFile::ToYca::~ToYca ()
258 {
259 delete [] _bufBase;
260 delete [] _tmpBuf;
261 }
262
263
264 void
setYCRounding(unsigned int roundY,unsigned int roundC)265 RgbaOutputFile::ToYca::setYCRounding (unsigned int roundY,
266 unsigned int roundC)
267 {
268 _roundY = roundY;
269 _roundC = roundC;
270 }
271
272
273 void
setFrameBuffer(const Rgba * base,size_t xStride,size_t yStride)274 RgbaOutputFile::ToYca::setFrameBuffer (const Rgba *base,
275 size_t xStride,
276 size_t yStride)
277 {
278 if (_fbBase == 0)
279 {
280 FrameBuffer fb;
281
282 if (_writeY)
283 {
284 fb.insert ("Y",
285 Slice (HALF, // type
286 (char *) &_tmpBuf[-_xMin].g, // base
287 sizeof (Rgba), // xStride
288 0, // yStride
289 1, // xSampling
290 1)); // ySampling
291 }
292
293 if (_writeC)
294 {
295 fb.insert ("RY",
296 Slice (HALF, // type
297 (char *) &_tmpBuf[-_xMin].r, // base
298 sizeof (Rgba) * 2, // xStride
299 0, // yStride
300 2, // xSampling
301 2)); // ySampling
302
303 fb.insert ("BY",
304 Slice (HALF, // type
305 (char *) &_tmpBuf[-_xMin].b, // base
306 sizeof (Rgba) * 2, // xStride
307 0, // yStride
308 2, // xSampling
309 2)); // ySampling
310 }
311
312 if (_writeA)
313 {
314 fb.insert ("A",
315 Slice (HALF, // type
316 (char *) &_tmpBuf[-_xMin].a, // base
317 sizeof (Rgba), // xStride
318 0, // yStride
319 1, // xSampling
320 1)); // ySampling
321 }
322
323 _outputFile.setFrameBuffer (fb);
324 }
325
326 _fbBase = base;
327 _fbXStride = xStride;
328 _fbYStride = yStride;
329 }
330
331
332 void
writePixels(int numScanLines)333 RgbaOutputFile::ToYca::writePixels (int numScanLines)
334 {
335 if (_fbBase == 0)
336 {
337 THROW (IEX_NAMESPACE::ArgExc, "No frame buffer was specified as the "
338 "pixel data source for image file "
339 "\"" << _outputFile.fileName() << "\".");
340 }
341
342 intptr_t base = reinterpret_cast<intptr_t>(_fbBase);
343 if (_writeY && !_writeC)
344 {
345 //
346 // We are writing only luminance; filtering
347 // and subsampling are not necessary.
348 //
349
350 for (int i = 0; i < numScanLines; ++i)
351 {
352 //
353 // Copy the next scan line from the caller's
354 // frame buffer into _tmpBuf.
355 //
356
357 for (int j = 0; j < _width; ++j)
358 {
359 _tmpBuf[j] = *reinterpret_cast<Rgba*>(base + sizeof(Rgba)*
360 (_fbYStride * _currentScanLine +
361 _fbXStride * (j + _xMin)));
362 }
363
364 //
365 // Convert the scan line from RGB to luminance/chroma,
366 // and store the result in the output file.
367 //
368
369 RGBAtoYCA (_yw, _width, _writeA, _tmpBuf, _tmpBuf);
370 _outputFile.writePixels (1);
371
372 ++_linesConverted;
373
374 if (_lineOrder == INCREASING_Y)
375 ++_currentScanLine;
376 else
377 --_currentScanLine;
378 }
379 }
380 else
381 {
382 //
383 // We are writing chroma; the pixels must be filtered and subsampled.
384 //
385
386 for (int i = 0; i < numScanLines; ++i)
387 {
388 //
389 // Copy the next scan line from the caller's
390 // frame buffer into _tmpBuf.
391 //
392
393 intptr_t base = reinterpret_cast<intptr_t>(_fbBase);
394
395 for (int j = 0; j < _width; ++j)
396 {
397 const Rgba* ptr = reinterpret_cast<const Rgba*>(base+sizeof(Rgba)*
398 (_fbYStride * _currentScanLine + _fbXStride * (j + _xMin)) );
399 _tmpBuf[j + N2] = *ptr;
400 }
401
402 //
403 // Convert the scan line from RGB to luminance/chroma.
404 //
405
406 RGBAtoYCA (_yw, _width, _writeA, _tmpBuf + N2, _tmpBuf + N2);
407
408 //
409 // Append N2 copies of the first and last pixel to the
410 // beginning and end of the scan line.
411 //
412
413 padTmpBuf ();
414
415 //
416 // Filter and subsample the scan line's chroma channels
417 // horizontally; store the result in _buf.
418 //
419
420 rotateBuffers();
421 decimateChromaHoriz (_width, _tmpBuf, _buf[N - 1]);
422
423 //
424 // If this is the first scan line in the image,
425 // store N2 more copies of the scan line in _buf.
426 //
427
428 if (_linesConverted == 0)
429 {
430 for (int j = 0; j < N2; ++j)
431 duplicateLastBuffer();
432 }
433
434 ++_linesConverted;
435
436 //
437 // If we have have converted at least N2 scan lines from
438 // RGBA to luminance/chroma, then we can start to filter
439 // and subsample vertically, and store pixels in the
440 // output file.
441 //
442
443 if (_linesConverted > N2)
444 decimateChromaVertAndWriteScanLine();
445
446 //
447 // If we have already converted the last scan line in
448 // the image to luminance/chroma, filter, subsample and
449 // store the remaining scan lines in _buf.
450 //
451
452 if (_linesConverted >= _height)
453 {
454 for (int j = 0; j < N2 - _height; ++j)
455 duplicateLastBuffer();
456
457 duplicateSecondToLastBuffer();
458 ++_linesConverted;
459 decimateChromaVertAndWriteScanLine();
460
461 for (int j = 1; j < min (_height, N2); ++j)
462 {
463 duplicateLastBuffer();
464 ++_linesConverted;
465 decimateChromaVertAndWriteScanLine();
466 }
467 }
468
469 if (_lineOrder == INCREASING_Y)
470 ++_currentScanLine;
471 else
472 --_currentScanLine;
473 }
474 }
475 }
476
477
478 int
currentScanLine() const479 RgbaOutputFile::ToYca::currentScanLine () const
480 {
481 return _currentScanLine;
482 }
483
484
485 void
padTmpBuf()486 RgbaOutputFile::ToYca::padTmpBuf ()
487 {
488 for (int i = 0; i < N2; ++i)
489 {
490 _tmpBuf[i] = _tmpBuf[N2];
491 _tmpBuf[_width + N2 + i] = _tmpBuf[_width + N2 - 2];
492 }
493 }
494
495
496 void
rotateBuffers()497 RgbaOutputFile::ToYca::rotateBuffers ()
498 {
499 Rgba *tmp = _buf[0];
500
501 for (int i = 0; i < N - 1; ++i)
502 _buf[i] = _buf[i + 1];
503
504 _buf[N - 1] = tmp;
505 }
506
507
508 void
duplicateLastBuffer()509 RgbaOutputFile::ToYca::duplicateLastBuffer ()
510 {
511 rotateBuffers();
512 memcpy (_buf[N - 1], _buf[N - 2], _width * sizeof (Rgba));
513 }
514
515
516 void
duplicateSecondToLastBuffer()517 RgbaOutputFile::ToYca::duplicateSecondToLastBuffer ()
518 {
519 rotateBuffers();
520 memcpy (_buf[N - 1], _buf[N - 3], _width * sizeof (Rgba));
521 }
522
523
524 void
decimateChromaVertAndWriteScanLine()525 RgbaOutputFile::ToYca::decimateChromaVertAndWriteScanLine ()
526 {
527 if (_linesConverted & 1)
528 memcpy (_tmpBuf, _buf[N2], _width * sizeof (Rgba));
529 else
530 decimateChromaVert (_width, _buf, _tmpBuf);
531
532 if (_writeY && _writeC)
533 roundYCA (_width, _roundY, _roundC, _tmpBuf, _tmpBuf);
534
535 _outputFile.writePixels (1);
536 }
537
538
RgbaOutputFile(const char name[],const Header & header,RgbaChannels rgbaChannels,int numThreads)539 RgbaOutputFile::RgbaOutputFile (const char name[],
540 const Header &header,
541 RgbaChannels rgbaChannels,
542 int numThreads):
543 _outputFile (0),
544 _toYca (0)
545 {
546 Header hd (header);
547 insertChannels (hd, rgbaChannels);
548 _outputFile = new OutputFile (name, hd, numThreads);
549
550 if (rgbaChannels & (WRITE_Y | WRITE_C))
551 _toYca = new ToYca (*_outputFile, rgbaChannels);
552 }
553
554
RgbaOutputFile(OPENEXR_IMF_INTERNAL_NAMESPACE::OStream & os,const Header & header,RgbaChannels rgbaChannels,int numThreads)555 RgbaOutputFile::RgbaOutputFile (OPENEXR_IMF_INTERNAL_NAMESPACE::OStream &os,
556 const Header &header,
557 RgbaChannels rgbaChannels,
558 int numThreads):
559 _outputFile (0),
560 _toYca (0)
561 {
562 Header hd (header);
563 insertChannels (hd, rgbaChannels);
564 _outputFile = new OutputFile (os, hd, numThreads);
565
566 if (rgbaChannels & (WRITE_Y | WRITE_C))
567 _toYca = new ToYca (*_outputFile, rgbaChannels);
568 }
569
570
RgbaOutputFile(const char 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)571 RgbaOutputFile::RgbaOutputFile (const char name[],
572 const IMATH_NAMESPACE::Box2i &displayWindow,
573 const IMATH_NAMESPACE::Box2i &dataWindow,
574 RgbaChannels rgbaChannels,
575 float pixelAspectRatio,
576 const IMATH_NAMESPACE::V2f screenWindowCenter,
577 float screenWindowWidth,
578 LineOrder lineOrder,
579 Compression compression,
580 int numThreads):
581 _outputFile (0),
582 _toYca (0)
583 {
584 Header hd (displayWindow,
585 dataWindow.isEmpty()? displayWindow: dataWindow,
586 pixelAspectRatio,
587 screenWindowCenter,
588 screenWindowWidth,
589 lineOrder,
590 compression);
591
592 insertChannels (hd, rgbaChannels);
593 _outputFile = new OutputFile (name, hd, numThreads);
594
595 if (rgbaChannels & (WRITE_Y | WRITE_C))
596 _toYca = new ToYca (*_outputFile, rgbaChannels);
597 }
598
599
RgbaOutputFile(const char name[],int width,int height,RgbaChannels rgbaChannels,float pixelAspectRatio,const IMATH_NAMESPACE::V2f screenWindowCenter,float screenWindowWidth,LineOrder lineOrder,Compression compression,int numThreads)600 RgbaOutputFile::RgbaOutputFile (const char name[],
601 int width,
602 int height,
603 RgbaChannels rgbaChannels,
604 float pixelAspectRatio,
605 const IMATH_NAMESPACE::V2f screenWindowCenter,
606 float screenWindowWidth,
607 LineOrder lineOrder,
608 Compression compression,
609 int numThreads):
610 _outputFile (0),
611 _toYca (0)
612 {
613 Header hd (width,
614 height,
615 pixelAspectRatio,
616 screenWindowCenter,
617 screenWindowWidth,
618 lineOrder,
619 compression);
620
621 insertChannels (hd, rgbaChannels);
622 _outputFile = new OutputFile (name, hd, numThreads);
623
624 if (rgbaChannels & (WRITE_Y | WRITE_C))
625 _toYca = new ToYca (*_outputFile, rgbaChannels);
626 }
627
628
~RgbaOutputFile()629 RgbaOutputFile::~RgbaOutputFile ()
630 {
631 delete _toYca;
632 delete _outputFile;
633 }
634
635
636 void
setFrameBuffer(const Rgba * base,size_t xStride,size_t yStride)637 RgbaOutputFile::setFrameBuffer (const Rgba *base,
638 size_t xStride,
639 size_t yStride)
640 {
641 if (_toYca)
642 {
643 std::lock_guard<std::mutex> lock (*_toYca);
644 _toYca->setFrameBuffer (base, xStride, yStride);
645 }
646 else
647 {
648 size_t xs = xStride * sizeof (Rgba);
649 size_t ys = yStride * sizeof (Rgba);
650
651 FrameBuffer fb;
652
653 fb.insert ("R", Slice (HALF, (char *) &base[0].r, xs, ys));
654 fb.insert ("G", Slice (HALF, (char *) &base[0].g, xs, ys));
655 fb.insert ("B", Slice (HALF, (char *) &base[0].b, xs, ys));
656 fb.insert ("A", Slice (HALF, (char *) &base[0].a, xs, ys));
657
658 _outputFile->setFrameBuffer (fb);
659 }
660 }
661
662
663 void
writePixels(int numScanLines)664 RgbaOutputFile::writePixels (int numScanLines)
665 {
666 if (_toYca)
667 {
668 std::lock_guard<std::mutex> lock (*_toYca);
669 _toYca->writePixels (numScanLines);
670 }
671 else
672 {
673 _outputFile->writePixels (numScanLines);
674 }
675 }
676
677
678 int
currentScanLine() const679 RgbaOutputFile::currentScanLine () const
680 {
681 if (_toYca)
682 {
683 std::lock_guard<std::mutex> lock (*_toYca);
684 return _toYca->currentScanLine();
685 }
686 else
687 {
688 return _outputFile->currentScanLine();
689 }
690 }
691
692
693 const Header &
header() const694 RgbaOutputFile::header () const
695 {
696 return _outputFile->header();
697 }
698
699
700 const FrameBuffer &
frameBuffer() const701 RgbaOutputFile::frameBuffer () const
702 {
703 return _outputFile->frameBuffer();
704 }
705
706
707 const IMATH_NAMESPACE::Box2i &
displayWindow() const708 RgbaOutputFile::displayWindow () const
709 {
710 return _outputFile->header().displayWindow();
711 }
712
713
714 const IMATH_NAMESPACE::Box2i &
dataWindow() const715 RgbaOutputFile::dataWindow () const
716 {
717 return _outputFile->header().dataWindow();
718 }
719
720
721 float
pixelAspectRatio() const722 RgbaOutputFile::pixelAspectRatio () const
723 {
724 return _outputFile->header().pixelAspectRatio();
725 }
726
727
728 const IMATH_NAMESPACE::V2f
screenWindowCenter() const729 RgbaOutputFile::screenWindowCenter () const
730 {
731 return _outputFile->header().screenWindowCenter();
732 }
733
734
735 float
screenWindowWidth() const736 RgbaOutputFile::screenWindowWidth () const
737 {
738 return _outputFile->header().screenWindowWidth();
739 }
740
741
742 LineOrder
lineOrder() const743 RgbaOutputFile::lineOrder () const
744 {
745 return _outputFile->header().lineOrder();
746 }
747
748
749 Compression
compression() const750 RgbaOutputFile::compression () const
751 {
752 return _outputFile->header().compression();
753 }
754
755
756 RgbaChannels
channels() const757 RgbaOutputFile::channels () const
758 {
759 return rgbaChannels (_outputFile->header().channels());
760 }
761
762
763 void
updatePreviewImage(const PreviewRgba newPixels[])764 RgbaOutputFile::updatePreviewImage (const PreviewRgba newPixels[])
765 {
766 _outputFile->updatePreviewImage (newPixels);
767 }
768
769
770 void
setYCRounding(unsigned int roundY,unsigned int roundC)771 RgbaOutputFile::setYCRounding (unsigned int roundY, unsigned int roundC)
772 {
773 if (_toYca)
774 {
775 std::lock_guard<std::mutex> lock (*_toYca);
776 _toYca->setYCRounding (roundY, roundC);
777 }
778 }
779
780
781 void
breakScanLine(int y,int offset,int length,char c)782 RgbaOutputFile::breakScanLine (int y, int offset, int length, char c)
783 {
784 _outputFile->breakScanLine (y, offset, length, c);
785 }
786
787
788 class RgbaInputFile::FromYca: public std::mutex
789 {
790 public:
791
792 FromYca (InputFile &inputFile, RgbaChannels rgbaChannels);
793 ~FromYca ();
794
795 FromYca (const FromYca& other) = delete;
796 FromYca& operator = (const FromYca& other) = delete;
797 FromYca (FromYca&& other) = delete;
798 FromYca& operator = (FromYca&& other) = delete;
799
800 void setFrameBuffer (Rgba *base,
801 size_t xStride,
802 size_t yStride,
803 const string &channelNamePrefix);
804
805 void readPixels (int scanLine1, int scanLine2);
806
807 private:
808
809 void readPixels (int scanLine);
810 void rotateBuf1 (int d);
811 void rotateBuf2 (int d);
812 void readYCAScanLine (int y, Rgba buf[]);
813 void padTmpBuf ();
814
815 InputFile & _inputFile;
816 bool _readC;
817 int _xMin;
818 int _yMin;
819 int _yMax;
820 int _width;
821 int _height;
822 int _currentScanLine;
823 LineOrder _lineOrder;
824 V3f _yw;
825 Rgba * _bufBase;
826 Rgba * _buf1[N + 2];
827 Rgba * _buf2[3];
828 Rgba * _tmpBuf;
829 Rgba * _fbBase;
830 size_t _fbXStride;
831 size_t _fbYStride;
832 };
833
834
FromYca(InputFile & inputFile,RgbaChannels rgbaChannels)835 RgbaInputFile::FromYca::FromYca (InputFile &inputFile,
836 RgbaChannels rgbaChannels)
837 :
838 _inputFile (inputFile)
839 {
840 _readC = (rgbaChannels & WRITE_C)? true: false;
841
842 const Box2i dw = _inputFile.header().dataWindow();
843
844 _xMin = dw.min.x;
845 _yMin = dw.min.y;
846 _yMax = dw.max.y;
847 _width = dw.max.x - dw.min.x + 1;
848 _height = dw.max.y - dw.min.y + 1;
849 _currentScanLine = dw.min.y - N - 2;
850 _lineOrder = _inputFile.header().lineOrder();
851 _yw = ywFromHeader (_inputFile.header());
852
853 ptrdiff_t pad = cachePadding (_width * sizeof (Rgba)) / sizeof (Rgba);
854
855 _bufBase = new Rgba[(_width + pad) * (N + 2 + 3)];
856
857 for (int i = 0; i < N + 2; ++i)
858 _buf1[i] = _bufBase + (i * (_width + pad));
859
860 for (int i = 0; i < 3; ++i)
861 _buf2[i] = _bufBase + ((i + N + 2) * (_width + pad));
862
863 _tmpBuf = new Rgba[_width + N - 1];
864
865 _fbBase = 0;
866 _fbXStride = 0;
867 _fbYStride = 0;
868 }
869
870
~FromYca()871 RgbaInputFile::FromYca::~FromYca ()
872 {
873 delete [] _bufBase;
874 delete [] _tmpBuf;
875 }
876
877
878 void
setFrameBuffer(Rgba * base,size_t xStride,size_t yStride,const string & channelNamePrefix)879 RgbaInputFile::FromYca::setFrameBuffer (Rgba *base,
880 size_t xStride,
881 size_t yStride,
882 const string &channelNamePrefix)
883 {
884 if (_fbBase == 0)
885 {
886 FrameBuffer fb;
887
888 fb.insert (channelNamePrefix + "Y",
889 Slice (HALF, // type
890 (char *) &_tmpBuf[N2 - _xMin].g, // base
891 sizeof (Rgba), // xStride
892 0, // yStride
893 1, // xSampling
894 1, // ySampling
895 0.5)); // fillValue
896
897 if (_readC)
898 {
899 fb.insert (channelNamePrefix + "RY",
900 Slice (HALF, // type
901 (char *) &_tmpBuf[N2 - _xMin].r, // base
902 sizeof (Rgba) * 2, // xStride
903 0, // yStride
904 2, // xSampling
905 2, // ySampling
906 0.0)); // fillValue
907
908 fb.insert (channelNamePrefix + "BY",
909 Slice (HALF, // type
910 (char *) &_tmpBuf[N2 - _xMin].b, // base
911 sizeof (Rgba) * 2, // xStride
912 0, // yStride
913 2, // xSampling
914 2, // ySampling
915 0.0)); // fillValue
916 }
917
918 fb.insert (channelNamePrefix + "A",
919 Slice (HALF, // type
920 (char *) &_tmpBuf[N2 - _xMin].a, // base
921 sizeof (Rgba), // xStride
922 0, // yStride
923 1, // xSampling
924 1, // ySampling
925 1.0)); // fillValue
926
927 _inputFile.setFrameBuffer (fb);
928 }
929
930 _fbBase = base;
931 _fbXStride = xStride;
932 _fbYStride = yStride;
933 }
934
935
936 void
readPixels(int scanLine1,int scanLine2)937 RgbaInputFile::FromYca::readPixels (int scanLine1, int scanLine2)
938 {
939 int minY = min (scanLine1, scanLine2);
940 int maxY = max (scanLine1, scanLine2);
941
942 if (_lineOrder == INCREASING_Y)
943 {
944 for (int y = minY; y <= maxY; ++y)
945 readPixels (y);
946 }
947 else
948 {
949 for (int y = maxY; y >= minY; --y)
950 readPixels (y);
951 }
952 }
953
954
955 void
readPixels(int scanLine)956 RgbaInputFile::FromYca::readPixels (int scanLine)
957 {
958 if (_fbBase == 0)
959 {
960 THROW (IEX_NAMESPACE::ArgExc, "No frame buffer was specified as the "
961 "pixel data destination for image file "
962 "\"" << _inputFile.fileName() << "\".");
963 }
964
965 //
966 // In order to convert one scan line to RGB format, we need that
967 // scan line plus N2+1 extra scan lines above and N2+1 scan lines
968 // below in luminance/chroma format.
969 //
970 // We allow random access to scan lines, but we buffer partially
971 // processed luminance/chroma data in order to make reading pixels
972 // in increasing y or decreasing y order reasonably efficient:
973 //
974 // _currentScanLine holds the y coordinate of the scan line
975 // that was most recently read.
976 //
977 // _buf1 contains scan lines _currentScanLine-N2-1
978 // through _currentScanLine+N2+1 in
979 // luminance/chroma format. Odd-numbered
980 // lines contain no chroma data. Even-numbered
981 // lines have valid chroma data for all pixels.
982 //
983 // _buf2 contains scan lines _currentScanLine-1
984 // through _currentScanLine+1, in RGB format.
985 // Super-saturated pixels (see ImfRgbaYca.h)
986 // have not yet been eliminated.
987 //
988 // If the scan line we are trying to read now is close enough to
989 // _currentScanLine, we don't have to recompute the contents of _buf1
990 // and _buf2 from scratch. We can rotate _buf1 and _buf2, and fill
991 // in the missing data.
992 //
993
994 int dy = scanLine - _currentScanLine;
995
996 if (abs (dy) < N + 2)
997 rotateBuf1 (dy);
998
999 if (abs (dy) < 3)
1000 rotateBuf2 (dy);
1001
1002 if (dy < 0)
1003 {
1004 {
1005 int n = min (-dy, N + 2);
1006 int yMin = scanLine - N2 - 1;
1007
1008 for (int i = n - 1; i >= 0; --i)
1009 readYCAScanLine (yMin + i, _buf1[i]);
1010 }
1011
1012 {
1013 int n = min (-dy, 3);
1014
1015 for (int i = 0; i < n; ++i)
1016 {
1017 if ((scanLine + i) & 1)
1018 {
1019 YCAtoRGBA (_yw, _width, _buf1[N2 + i], _buf2[i]);
1020 }
1021 else
1022 {
1023 reconstructChromaVert (_width, _buf1 + i, _buf2[i]);
1024 YCAtoRGBA (_yw, _width, _buf2[i], _buf2[i]);
1025 }
1026 }
1027 }
1028 }
1029 else
1030 {
1031 {
1032 int n = min (dy, N + 2);
1033 int yMax = scanLine + N2 + 1;
1034
1035 for (int i = n - 1; i >= 0; --i)
1036 readYCAScanLine (yMax - i, _buf1[N + 1 - i]);
1037 }
1038
1039 {
1040 int n = min (dy, 3);
1041
1042 for (int i = 2; i > 2 - n; --i)
1043 {
1044 if ((scanLine + i) & 1)
1045 {
1046 YCAtoRGBA (_yw, _width, _buf1[N2 + i], _buf2[i]);
1047 }
1048 else
1049 {
1050 reconstructChromaVert (_width, _buf1 + i, _buf2[i]);
1051 YCAtoRGBA (_yw, _width, _buf2[i], _buf2[i]);
1052 }
1053 }
1054 }
1055 }
1056
1057 fixSaturation (_yw, _width, _buf2, _tmpBuf);
1058
1059
1060 intptr_t base = reinterpret_cast<intptr_t>(_fbBase);
1061 for (int i = 0; i < _width; ++i)
1062 {
1063 Rgba* ptr = reinterpret_cast<Rgba*>(base + sizeof(Rgba)*(_fbYStride * scanLine + _fbXStride * (i + _xMin)));
1064 *ptr = _tmpBuf[i];
1065 }
1066 _currentScanLine = scanLine;
1067 }
1068
1069
1070 void
rotateBuf1(int d)1071 RgbaInputFile::FromYca::rotateBuf1 (int d)
1072 {
1073 d = modp (d, N + 2);
1074
1075 Rgba *tmp[N + 2];
1076
1077 for (int i = 0; i < N + 2; ++i)
1078 tmp[i] = _buf1[i];
1079
1080 for (int i = 0; i < N + 2; ++i)
1081 _buf1[i] = tmp[(i + d) % (N + 2)];
1082 }
1083
1084
1085 void
rotateBuf2(int d)1086 RgbaInputFile::FromYca::rotateBuf2 (int d)
1087 {
1088 d = modp (d, 3);
1089
1090 Rgba *tmp[3];
1091
1092 for (int i = 0; i < 3; ++i)
1093 tmp[i] = _buf2[i];
1094
1095 for (int i = 0; i < 3; ++i)
1096 _buf2[i] = tmp[(i + d) % 3];
1097 }
1098
1099
1100 void
readYCAScanLine(int y,Rgba * buf)1101 RgbaInputFile::FromYca::readYCAScanLine (int y, Rgba *buf)
1102 {
1103 //
1104 // Clamp y.
1105 //
1106
1107 if (y < _yMin)
1108 y = _yMin;
1109 else if (y > _yMax)
1110 y = _yMax - 1;
1111
1112 //
1113 // Read scan line y into _tmpBuf.
1114 //
1115
1116 _inputFile.readPixels (y);
1117
1118 //
1119 // Reconstruct missing chroma samples and copy
1120 // the scan line into buf.
1121 //
1122
1123 if (!_readC)
1124 {
1125 for (int i = 0; i < _width; ++i)
1126 {
1127 _tmpBuf[i + N2].r = 0;
1128 _tmpBuf[i + N2].b = 0;
1129 }
1130 }
1131
1132 if (y & 1)
1133 {
1134 memcpy (buf, _tmpBuf + N2, _width * sizeof (Rgba));
1135 }
1136 else
1137 {
1138 padTmpBuf();
1139 reconstructChromaHoriz (_width, _tmpBuf, buf);
1140 }
1141 }
1142
1143
1144 void
padTmpBuf()1145 RgbaInputFile::FromYca::padTmpBuf ()
1146 {
1147 for (int i = 0; i < N2; ++i)
1148 {
1149 _tmpBuf[i] = _tmpBuf[N2];
1150 _tmpBuf[_width + N2 + i] = _tmpBuf[_width + N2 - 2];
1151 }
1152 }
1153
1154
RgbaInputFile(const char name[],int numThreads)1155 RgbaInputFile::RgbaInputFile (const char name[], int numThreads):
1156 _inputFile (new InputFile (name, numThreads)),
1157 _fromYca (0),
1158 _channelNamePrefix ("")
1159 {
1160 RgbaChannels rgbaChannels = channels();
1161
1162 if (rgbaChannels & WRITE_C)
1163 _fromYca = new FromYca (*_inputFile, rgbaChannels);
1164 }
1165
1166
RgbaInputFile(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,int numThreads)1167 RgbaInputFile::RgbaInputFile (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is, int numThreads):
1168 _inputFile (new InputFile (is, numThreads)),
1169 _fromYca (0),
1170 _channelNamePrefix ("")
1171 {
1172 RgbaChannels rgbaChannels = channels();
1173
1174 if (rgbaChannels & WRITE_C)
1175 _fromYca = new FromYca (*_inputFile, rgbaChannels);
1176 }
1177
1178
RgbaInputFile(const char name[],const string & layerName,int numThreads)1179 RgbaInputFile::RgbaInputFile (const char name[],
1180 const string &layerName,
1181 int numThreads)
1182 :
1183 _inputFile (new InputFile (name, numThreads)),
1184 _fromYca (0),
1185 _channelNamePrefix (prefixFromLayerName (layerName, _inputFile->header()))
1186 {
1187 RgbaChannels rgbaChannels = channels();
1188
1189 if (rgbaChannels & WRITE_C)
1190 _fromYca = new FromYca (*_inputFile, rgbaChannels);
1191 }
1192
1193
RgbaInputFile(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,const string & layerName,int numThreads)1194 RgbaInputFile::RgbaInputFile (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is,
1195 const string &layerName,
1196 int numThreads)
1197 :
1198 _inputFile (new InputFile (is, numThreads)),
1199 _fromYca (0),
1200 _channelNamePrefix (prefixFromLayerName (layerName, _inputFile->header()))
1201 {
1202 RgbaChannels rgbaChannels = channels();
1203
1204 if (rgbaChannels & WRITE_C)
1205 _fromYca = new FromYca (*_inputFile, rgbaChannels);
1206 }
1207
1208
~RgbaInputFile()1209 RgbaInputFile::~RgbaInputFile ()
1210 {
1211 delete _inputFile;
1212 delete _fromYca;
1213 }
1214
1215
1216 void
setFrameBuffer(Rgba * base,size_t xStride,size_t yStride)1217 RgbaInputFile::setFrameBuffer (Rgba *base, size_t xStride, size_t yStride)
1218 {
1219 if (_fromYca)
1220 {
1221 std::lock_guard<std::mutex> lock (*_fromYca);
1222 _fromYca->setFrameBuffer (base, xStride, yStride, _channelNamePrefix);
1223 }
1224 else
1225 {
1226 size_t xs = xStride * sizeof (Rgba);
1227 size_t ys = yStride * sizeof (Rgba);
1228
1229 FrameBuffer fb;
1230
1231 if( channels() & WRITE_Y )
1232 {
1233 fb.insert (_channelNamePrefix + "Y",
1234 Slice (HALF,
1235 (char *) &base[0].r,
1236 xs, ys,
1237 1, 1, // xSampling, ySampling
1238 0.0)); // fillValue
1239 }
1240 else
1241 {
1242
1243
1244 fb.insert (_channelNamePrefix + "R",
1245 Slice (HALF,
1246 (char *) &base[0].r,
1247 xs, ys,
1248 1, 1, // xSampling, ySampling
1249 0.0)); // fillValue
1250
1251
1252
1253 fb.insert (_channelNamePrefix + "G",
1254 Slice (HALF,
1255 (char *) &base[0].g,
1256 xs, ys,
1257 1, 1, // xSampling, ySampling
1258 0.0)); // fillValue
1259
1260 fb.insert (_channelNamePrefix + "B",
1261 Slice (HALF,
1262 (char *) &base[0].b,
1263 xs, ys,
1264 1, 1, // xSampling, ySampling
1265 0.0)); // fillValue
1266 }
1267 fb.insert (_channelNamePrefix + "A",
1268 Slice (HALF,
1269 (char *) &base[0].a,
1270 xs, ys,
1271 1, 1, // xSampling, ySampling
1272 1.0)); // fillValue
1273
1274 _inputFile->setFrameBuffer (fb);
1275 }
1276 }
1277
1278
1279 void
setLayerName(const string & layerName)1280 RgbaInputFile::setLayerName (const string &layerName)
1281 {
1282 delete _fromYca;
1283 _fromYca = 0;
1284
1285 _channelNamePrefix = prefixFromLayerName (layerName, _inputFile->header());
1286
1287 RgbaChannels rgbaChannels = channels();
1288
1289 if (rgbaChannels & WRITE_C)
1290 _fromYca = new FromYca (*_inputFile, rgbaChannels);
1291
1292 FrameBuffer fb;
1293 _inputFile->setFrameBuffer (fb);
1294 }
1295
1296
1297 void
readPixels(int scanLine1,int scanLine2)1298 RgbaInputFile::readPixels (int scanLine1, int scanLine2)
1299 {
1300 if (_fromYca)
1301 {
1302 std::lock_guard<std::mutex> lock (*_fromYca);
1303 _fromYca->readPixels (scanLine1, scanLine2);
1304 }
1305 else
1306 {
1307 _inputFile->readPixels (scanLine1, scanLine2);
1308
1309 if (channels() & WRITE_Y)
1310 {
1311 //
1312 // Luma channel has been written into red channel
1313 // Duplicate into green and blue channel to create gray image
1314 //
1315 const Slice* s = _inputFile->frameBuffer().findSlice(_channelNamePrefix + "Y");
1316 Box2i dataWindow = _inputFile->header().dataWindow();
1317 intptr_t base = reinterpret_cast<intptr_t>(s->base);
1318
1319 for( int scanLine = scanLine1 ; scanLine <= scanLine2 ; scanLine++ )
1320 {
1321 intptr_t rowBase = base + scanLine*s->yStride;
1322 for(int x = dataWindow.min.x ; x <= dataWindow.max.x ; ++x )
1323 {
1324 Rgba* pixel = reinterpret_cast<Rgba*>(rowBase+x*s->xStride);
1325 pixel->g = pixel->r;
1326 pixel->b = pixel->r;
1327 }
1328
1329 }
1330 }
1331 }
1332 }
1333
1334
1335 void
readPixels(int scanLine)1336 RgbaInputFile::readPixels (int scanLine)
1337 {
1338 readPixels (scanLine, scanLine);
1339 }
1340
1341
1342 bool
isComplete() const1343 RgbaInputFile::isComplete () const
1344 {
1345 return _inputFile->isComplete();
1346 }
1347
1348
1349 const Header &
header() const1350 RgbaInputFile::header () const
1351 {
1352 return _inputFile->header();
1353 }
1354
1355
1356 const char *
fileName() const1357 RgbaInputFile::fileName () const
1358 {
1359 return _inputFile->fileName();
1360 }
1361
1362
1363 const FrameBuffer &
frameBuffer() const1364 RgbaInputFile::frameBuffer () const
1365 {
1366 return _inputFile->frameBuffer();
1367 }
1368
1369
1370 const IMATH_NAMESPACE::Box2i &
displayWindow() const1371 RgbaInputFile::displayWindow () const
1372 {
1373 return _inputFile->header().displayWindow();
1374 }
1375
1376
1377 const IMATH_NAMESPACE::Box2i &
dataWindow() const1378 RgbaInputFile::dataWindow () const
1379 {
1380 return _inputFile->header().dataWindow();
1381 }
1382
1383
1384 float
pixelAspectRatio() const1385 RgbaInputFile::pixelAspectRatio () const
1386 {
1387 return _inputFile->header().pixelAspectRatio();
1388 }
1389
1390
1391 const IMATH_NAMESPACE::V2f
screenWindowCenter() const1392 RgbaInputFile::screenWindowCenter () const
1393 {
1394 return _inputFile->header().screenWindowCenter();
1395 }
1396
1397
1398 float
screenWindowWidth() const1399 RgbaInputFile::screenWindowWidth () const
1400 {
1401 return _inputFile->header().screenWindowWidth();
1402 }
1403
1404
1405 LineOrder
lineOrder() const1406 RgbaInputFile::lineOrder () const
1407 {
1408 return _inputFile->header().lineOrder();
1409 }
1410
1411
1412 Compression
compression() const1413 RgbaInputFile::compression () const
1414 {
1415 return _inputFile->header().compression();
1416 }
1417
1418
1419 RgbaChannels
channels() const1420 RgbaInputFile::channels () const
1421 {
1422 return rgbaChannels (_inputFile->header().channels(), _channelNamePrefix);
1423 }
1424
1425
1426 int
version() const1427 RgbaInputFile::version () const
1428 {
1429 return _inputFile->version();
1430 }
1431
1432
1433 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
1434