1 /*
2 Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al.
3 All Rights Reserved.
4 
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
8 * Redistributions of source code must retain the above copyright
9   notice, this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11   notice, this list of conditions and the following disclaimer in the
12   documentation and/or other materials provided with the distribution.
13 * Neither the name of Sony Pictures Imageworks nor the names of its
14   contributors may be used to endorse or promote products derived from
15   this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 
29 #include <cmath>
30 #include <cstring>
31 #include <sstream>
32 #include <algorithm>
33 
34 #include <OpenColorIO/OpenColorIO.h>
35 
36 #include "ExponentOps.h"
37 #include "GpuShaderUtils.h"
38 #include "MathUtils.h"
39 
40 OCIO_NAMESPACE_ENTER
41 {
42     namespace
43     {
44         void ApplyClampExponent(float* rgbaBuffer, long numPixels,
45                                 const float* exp4)
46         {
47             for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
48             {
49                 rgbaBuffer[0] = powf( std::max(0.0f, rgbaBuffer[0]), exp4[0]);
50                 rgbaBuffer[1] = powf( std::max(0.0f, rgbaBuffer[1]), exp4[1]);
51                 rgbaBuffer[2] = powf( std::max(0.0f, rgbaBuffer[2]), exp4[2]);
52                 rgbaBuffer[3] = powf( std::max(0.0f, rgbaBuffer[3]), exp4[3]);
53                 rgbaBuffer += 4;
54             }
55         }
56 
57         const int FLOAT_DECIMALS = 7;
58     }
59 
60 
61     namespace
62     {
63         class ExponentOp : public Op
64         {
65         public:
66             ExponentOp(const double * exp4,
67                        TransformDirection direction);
68             virtual ~ExponentOp();
69 
70             virtual OpRcPtr clone() const;
71 
72             virtual std::string getInfo() const;
73             virtual std::string getCacheID() const;
74 
75             virtual bool isNoOp() const;
76             virtual bool isSameType(const OpRcPtr & op) const;
77             virtual bool isInverse(const OpRcPtr & op) const;
78 
79             virtual bool canCombineWith(const OpRcPtr & op) const;
80             virtual void combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const;
81 
82             virtual bool hasChannelCrosstalk() const;
83             virtual void finalize();
84             virtual void apply(float* rgbaBuffer, long numPixels) const;
85 
86             virtual bool supportsGpuShader() const;
87             virtual void writeGpuShader(std::ostream & shader,
88                                         const std::string & pixelName,
89                                         const GpuShaderDesc & shaderDesc) const;
90         private:
91             double m_exp4[4];
92 
93             // Set in finalize
94             std::string m_cacheID;
95         };
96 
97         typedef OCIO_SHARED_PTR<ExponentOp> ExponentOpRcPtr;
98 
99 
100         ExponentOp::ExponentOp(const double * exp4,
101                                TransformDirection direction):
102                                Op()
103         {
104             if(direction == TRANSFORM_DIR_UNKNOWN)
105             {
106                 throw Exception("Cannot create ExponentOp with unspecified transform direction.");
107             }
108 
109             if(direction == TRANSFORM_DIR_INVERSE)
110             {
111                 for(int i=0; i<4; ++i)
112                 {
113                     if(!IsScalarEqualToZeroFlt(exp4[i]))
114                     {
115                         m_exp4[i] = 1.0 / exp4[i];
116                     }
117                     else
118                     {
119                         throw Exception("Cannot apply ExponentOp op, Cannot apply 0.0 exponent in the inverse.");
120                     }
121                 }
122             }
123             else
124             {
125                 memcpy(m_exp4, exp4, 4*sizeof(double));
126             }
127         }
128 
129         OpRcPtr ExponentOp::clone() const
130         {
131             OpRcPtr op = OpRcPtr(new ExponentOp(m_exp4, TRANSFORM_DIR_FORWARD));
132             return op;
133         }
134 
135         ExponentOp::~ExponentOp()
136         { }
137 
138         std::string ExponentOp::getInfo() const
139         {
140             return "<ExponentOp>";
141         }
142 
143         std::string ExponentOp::getCacheID() const
144         {
145             return m_cacheID;
146         }
147 
148         bool ExponentOp::isNoOp() const
149         {
150             return IsVecEqualToOneFlt(m_exp4, 4);
151         }
152 
153         bool ExponentOp::isSameType(const OpRcPtr & op) const
154         {
155             ExponentOpRcPtr typedRcPtr = DynamicPtrCast<ExponentOp>(op);
156             if(!typedRcPtr) return false;
157             return true;
158         }
159 
160         bool ExponentOp::isInverse(const OpRcPtr & op) const
161         {
162             ExponentOpRcPtr typedRcPtr = DynamicPtrCast<ExponentOp>(op);
163             if(!typedRcPtr) return false;
164 
165             double combined[4] = { m_exp4[0]*typedRcPtr->m_exp4[0],
166                                    m_exp4[1]*typedRcPtr->m_exp4[1],
167                                    m_exp4[2]*typedRcPtr->m_exp4[2],
168                                    m_exp4[3]*typedRcPtr->m_exp4[3] };
169 
170             return IsVecEqualToOneFlt(combined, 4);
171         }
172 
173         bool ExponentOp::canCombineWith(const OpRcPtr & op) const
174         {
175             return isSameType(op);
176         }
177 
178         void ExponentOp::combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const
179         {
180             ExponentOpRcPtr typedRcPtr = DynamicPtrCast<ExponentOp>(secondOp);
181             if(!typedRcPtr)
182             {
183                 std::ostringstream os;
184                 os << "ExponentOp can only be combined with other ";
185                 os << "ExponentOps.  secondOp:" << secondOp->getInfo();
186                 throw Exception(os.str().c_str());
187             }
188 
189             double combined[4] = { m_exp4[0]*typedRcPtr->m_exp4[0],
190                                    m_exp4[1]*typedRcPtr->m_exp4[1],
191                                    m_exp4[2]*typedRcPtr->m_exp4[2],
192                                    m_exp4[3]*typedRcPtr->m_exp4[3] };
193 
194             if(!IsVecEqualToOneFlt(combined, 4))
195             {
196                 ops.push_back(
197                     ExponentOpRcPtr(new ExponentOp(combined,
198                         TRANSFORM_DIR_FORWARD)) );
199             }
200         }
201 
202         bool ExponentOp::hasChannelCrosstalk() const
203         {
204             return false;
205         }
206 
207         void ExponentOp::finalize()
208         {
209             // Create the cacheID
210             std::ostringstream cacheIDStream;
211             cacheIDStream << "<ExponentOp ";
212             cacheIDStream.precision(FLOAT_DECIMALS);
213             for(int i=0; i<4; ++i)
214             {
215                 cacheIDStream << m_exp4[i] << " ";
216             }
217             cacheIDStream << ">";
218             m_cacheID = cacheIDStream.str();
219         }
220 
221         void ExponentOp::apply(float* rgbaBuffer, long numPixels) const
222         {
223             if(!rgbaBuffer) return;
224             float exp[4] = { float(m_exp4[0]), float(m_exp4[1]),
225                 float(m_exp4[2]), float(m_exp4[3]) };
226             ApplyClampExponent(rgbaBuffer, numPixels, exp);
227         }
228 
229         bool ExponentOp::supportsGpuShader() const
230         {
231             return true;
232         }
233 
234         void ExponentOp::writeGpuShader(std::ostream & shader,
235                                         const std::string & pixelName,
236                                         const GpuShaderDesc & shaderDesc) const
237         {
238             float exp[4] = { float(m_exp4[0]), float(m_exp4[1]),
239                 float(m_exp4[2]), float(m_exp4[3]) };
240 
241             GpuLanguage lang = shaderDesc.getLanguage();
242             float zerovec[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
243 
244             shader << pixelName << " = pow(";
245             shader << "max(" << pixelName << ", " << GpuTextHalf4(zerovec, lang) << ")";
246             shader << ", " << GpuTextHalf4(exp, lang) << ");\n";
247         }
248 
249     }  // Anon namespace
250 
251 
252 
253     void CreateExponentOp(OpRcPtrVec & ops,
254                           const float * exp4,
255                           TransformDirection direction)
256     {
257         bool expIsIdentity = IsVecEqualToOne(exp4, 4);
258         if(expIsIdentity) return;
259         double d_exp[4] = { double(exp4[0]), double(exp4[1]),
260                 double(exp4[2]), double(exp4[3]) };
261         ops.push_back( ExponentOpRcPtr(new ExponentOp(d_exp, direction)) );
262     }
263 }
264 OCIO_NAMESPACE_EXIT
265 
266 
267 ///////////////////////////////////////////////////////////////////////////////
268 
269 #ifdef OCIO_UNIT_TEST
270 
271 #include "UnitTest.h"
272 
273 OCIO_NAMESPACE_USING
274 
275 OIIO_ADD_TEST(ExponentOps, Value)
276 {
277     float exp1[4] = { 1.2f, 1.3f, 1.4f, 1.5f };
278 
279     OpRcPtrVec ops;
280     CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD);
281     CreateExponentOp(ops, exp1, TRANSFORM_DIR_INVERSE);
282     OIIO_CHECK_EQUAL(ops.size(), 2);
283 
284     for(unsigned int i=0; i<ops.size(); ++i)
285     {
286         ops[i]->finalize();
287     }
288 
289     float error = 1e-6f;
290 
291     const float source[] = {  0.5f, 0.5f, 0.5f, 0.5f, };
292 
293     const float result1[] = {  0.43527528164806206f, 0.40612619817811774f,
294                                0.37892914162759955f, 0.35355339059327379f };
295 
296     float tmp[4];
297     memcpy(tmp, source, 4*sizeof(float));
298     ops[0]->apply(tmp, 1);
299 
300     for(unsigned int i=0; i<4; ++i)
301     {
302         OIIO_CHECK_CLOSE(tmp[i], result1[i], error);
303     }
304 
305     ops[1]->apply(tmp, 1);
306     for(unsigned int i=0; i<4; ++i)
307     {
308         OIIO_CHECK_CLOSE(tmp[i], source[i], error);
309     }
310 }
311 
OIIO_ADD_TEST(ExponentOps,Inverse)312 OIIO_ADD_TEST(ExponentOps, Inverse)
313 {
314     float exp1[4] = { 2.0f, 1.02345f, 5.651321f, 0.12345678910f };
315     float exp2[4] = { 2.0f, 2.0f, 2.0f, 2.0f };
316 
317     OpRcPtrVec ops;
318 
319     CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD);
320     CreateExponentOp(ops, exp1, TRANSFORM_DIR_INVERSE);
321     CreateExponentOp(ops, exp2, TRANSFORM_DIR_FORWARD);
322     CreateExponentOp(ops, exp2, TRANSFORM_DIR_INVERSE);
323 
324     OIIO_CHECK_EQUAL(ops.size(), 4);
325 
326     OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[1]));
327     OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[2]));
328     OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[3]->clone()));
329 
330     OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[0]), false);
331     OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[1]), true);
332     OIIO_CHECK_EQUAL(ops[1]->isInverse(ops[0]), true);
333     OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[2]), false);
334     OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[3]), false);
335     OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[0]), false);
336     OIIO_CHECK_EQUAL(ops[2]->isInverse(ops[3]), true);
337     OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[2]), true);
338     OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[3]), false);
339 }
340 
OIIO_ADD_TEST(ExponentOps,Combining)341 OIIO_ADD_TEST(ExponentOps, Combining)
342 {
343     {
344     float exp1[4] = { 2.0f, 2.0f, 2.0f, 1.0f };
345     float exp2[4] = { 1.2f, 1.2f, 1.2f, 1.0f };
346 
347     OpRcPtrVec ops;
348     CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD);
349     CreateExponentOp(ops, exp2, TRANSFORM_DIR_FORWARD);
350     OIIO_CHECK_EQUAL(ops.size(), 2);
351 
352     float error = 1e-6f;
353     const float source[] = {  0.5f, 0.5f, 0.5f, 0.5f, };
354     const float result[] = {  0.18946457081379978f, 0.18946457081379978f,
355                                0.18946457081379978f, 0.5f };
356 
357     float tmp[4];
358     memcpy(tmp, source, 4*sizeof(float));
359     ops[0]->apply(tmp, 1);
360     ops[1]->apply(tmp, 1);
361 
362     for(unsigned int i=0; i<4; ++i)
363     {
364         OIIO_CHECK_CLOSE(tmp[i], result[i], error);
365     }
366 
367 
368     OpRcPtrVec combined;
369     ops[0]->combineWith(combined, ops[1]);
370     OIIO_CHECK_EQUAL(combined.size(), 1);
371 
372     float tmp2[4];
373     memcpy(tmp2, source, 4*sizeof(float));
374     combined[0]->apply(tmp2, 1);
375 
376     for(unsigned int i=0; i<4; ++i)
377     {
378         OIIO_CHECK_CLOSE(tmp2[i], result[i], error);
379     }
380     }
381 
382     {
383 
384     float exp1[4] = {1.037289f, 1.019015f, 0.966082f, 1.0f};
385 
386     OpRcPtrVec ops;
387     CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD);
388     CreateExponentOp(ops, exp1, TRANSFORM_DIR_INVERSE);
389     OIIO_CHECK_EQUAL(ops.size(), 2);
390 
391     bool isInverse = ops[0]->isInverse(ops[1]);
392     OIIO_CHECK_EQUAL(isInverse, true);
393     }
394 }
395 
396 #endif // OCIO_UNIT_TEST
397