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