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 <OpenColorIO/OpenColorIO.h>
30
31 #include "GpuShaderUtils.h"
32 #include "HashUtils.h"
33 #include "MatrixOps.h"
34 #include "MathUtils.h"
35
36 #include <cstring>
37 #include <sstream>
38
39 OCIO_NAMESPACE_ENTER
40 {
41 namespace
42 {
43 void ApplyScale(float* rgbaBuffer, long numPixels,
44 const float* scale4)
45 {
46 for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
47 {
48 rgbaBuffer[0] *= scale4[0];
49 rgbaBuffer[1] *= scale4[1];
50 rgbaBuffer[2] *= scale4[2];
51 rgbaBuffer[3] *= scale4[3];
52
53 rgbaBuffer += 4;
54 }
55 }
56
57 void ApplyOffset(float* rgbaBuffer, long numPixels,
58 const float* offset4)
59 {
60 for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
61 {
62 rgbaBuffer[0] += offset4[0];
63 rgbaBuffer[1] += offset4[1];
64 rgbaBuffer[2] += offset4[2];
65 rgbaBuffer[3] += offset4[3];
66
67 rgbaBuffer += 4;
68 }
69 }
70
71 void ApplyMatrix(float* rgbaBuffer, long numPixels,
72 const float* mat44)
73 {
74 float r,g,b,a;
75
76 for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
77 {
78 r = rgbaBuffer[0];
79 g = rgbaBuffer[1];
80 b = rgbaBuffer[2];
81 a = rgbaBuffer[3];
82
83 rgbaBuffer[0] = r*mat44[0] + g*mat44[1] + b*mat44[2] + a*mat44[3];
84 rgbaBuffer[1] = r*mat44[4] + g*mat44[5] + b*mat44[6] + a*mat44[7];
85 rgbaBuffer[2] = r*mat44[8] + g*mat44[9] + b*mat44[10] + a*mat44[11];
86 rgbaBuffer[3] = r*mat44[12] + g*mat44[13] + b*mat44[14] + a*mat44[15];
87
88 rgbaBuffer += 4;
89 }
90 }
91 }
92
93
94
95
96
97
98
99 ///////////////////////////////////////////////////////////////////////////
100
101
102
103
104
105
106
107
108 namespace
109 {
110 class MatrixOffsetOp : public Op
111 {
112 public:
113 MatrixOffsetOp(const float * m44,
114 const float * offset4,
115 TransformDirection direction);
116 virtual ~MatrixOffsetOp();
117
118 virtual OpRcPtr clone() const;
119
120 virtual std::string getInfo() const;
121 virtual std::string getCacheID() const;
122
123 virtual bool isNoOp() const;
124 virtual bool isSameType(const OpRcPtr & op) const;
125 virtual bool isInverse(const OpRcPtr & op) const;
126 virtual bool canCombineWith(const OpRcPtr & op) const;
127 virtual void combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const;
128
129 virtual bool hasChannelCrosstalk() const;
130 virtual void finalize();
131 virtual void apply(float* rgbaBuffer, long numPixels) const;
132
133 virtual bool supportsGpuShader() const;
134 virtual void writeGpuShader(std::ostream & shader,
135 const std::string & pixelName,
136 const GpuShaderDesc & shaderDesc) const;
137
138 private:
139 bool m_isNoOp;
140 float m_m44[16];
141 float m_offset4[4];
142 TransformDirection m_direction;
143
144 // Set in finalize
145 bool m_m44IsIdentity;
146 bool m_m44IsDiagonal;
147 bool m_offset4IsIdentity;
148 float m_m44_inv[16];
149 std::string m_cacheID;
150 };
151
152
153 typedef OCIO_SHARED_PTR<MatrixOffsetOp> MatrixOffsetOpRcPtr;
154
155
156 MatrixOffsetOp::MatrixOffsetOp(const float * m44,
157 const float * offset4,
158 TransformDirection direction):
159 Op(),
160 m_isNoOp(false),
161 m_direction(direction),
162 m_m44IsIdentity(false),
163 m_offset4IsIdentity(false)
164 {
165 if(m_direction == TRANSFORM_DIR_UNKNOWN)
166 {
167 throw Exception("Cannot apply MatrixOffsetOp op, unspecified transform direction.");
168 }
169
170 memcpy(m_m44, m44, 16*sizeof(float));
171 memcpy(m_offset4, offset4, 4*sizeof(float));
172
173 memset(m_m44_inv, 0, 16*sizeof(float));
174
175 // This Op will be a NoOp if and old if both the offset and matrix
176 // are identity. This hold true no matter what the direction is,
177 // so we can compute this ahead of time.
178 m_isNoOp = (IsVecEqualToZero(m_offset4, 4) && IsM44Identity(m_m44));
179 }
180
181 OpRcPtr MatrixOffsetOp::clone() const
182 {
183 OpRcPtr op = OpRcPtr(new MatrixOffsetOp(m_m44, m_offset4, m_direction));
184 return op;
185 }
186
187 MatrixOffsetOp::~MatrixOffsetOp()
188 { }
189
190 std::string MatrixOffsetOp::getInfo() const
191 {
192 return "<MatrixOffsetOp>";
193 }
194
195 std::string MatrixOffsetOp::getCacheID() const
196 {
197 return m_cacheID;
198 }
199
200 bool MatrixOffsetOp::isNoOp() const
201 {
202 return m_isNoOp;
203 }
204
205 bool MatrixOffsetOp::isSameType(const OpRcPtr & op) const
206 {
207 MatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<MatrixOffsetOp>(op);
208 if(!typedRcPtr) return false;
209 return true;
210 }
211
212 bool MatrixOffsetOp::isInverse(const OpRcPtr & op) const
213 {
214 MatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<MatrixOffsetOp>(op);
215 if(!typedRcPtr) return false;
216
217 if(GetInverseTransformDirection(m_direction) != typedRcPtr->m_direction)
218 return false;
219
220 float error = std::numeric_limits<float>::min();
221 if(!VecsEqualWithRelError(m_m44, 16, typedRcPtr->m_m44, 16, error))
222 return false;
223 if(!VecsEqualWithRelError(m_offset4, 4,typedRcPtr->m_offset4, 4, error))
224 return false;
225
226 return true;
227 }
228
229 bool MatrixOffsetOp::canCombineWith(const OpRcPtr & op) const
230 {
231 return isSameType(op);
232 }
233
234 void MatrixOffsetOp::combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const
235 {
236 MatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<MatrixOffsetOp>(secondOp);
237 if(!typedRcPtr)
238 {
239 std::ostringstream os;
240 os << "MatrixOffsetOp can only be combined with other ";
241 os << "MatrixOffsetOps. secondOp:" << secondOp->getInfo();
242 throw Exception(os.str().c_str());
243 }
244
245 float mout[16];
246 float vout[4];
247
248 if(m_direction == TRANSFORM_DIR_FORWARD &&
249 typedRcPtr->m_direction == TRANSFORM_DIR_FORWARD)
250 {
251 GetMxbCombine(mout, vout,
252 m_m44, m_offset4,
253 typedRcPtr->m_m44, typedRcPtr->m_offset4);
254 }
255 else if(m_direction == TRANSFORM_DIR_FORWARD &&
256 typedRcPtr->m_direction == TRANSFORM_DIR_INVERSE)
257 {
258 float minv2[16];
259 float vinv2[4];
260
261 if(!GetMxbInverse(minv2, vinv2, typedRcPtr->m_m44, typedRcPtr->m_offset4))
262 {
263 std::ostringstream os;
264 os << "Cannot invert second MatrixOffsetOp op. ";
265 os << "Matrix inverse does not exist for (";
266 for(int i=0; i<16; ++i)
267 {
268 os << typedRcPtr->m_m44[i] << " ";
269 }
270 os << ").";
271 throw Exception(os.str().c_str());
272 }
273
274 GetMxbCombine(mout, vout,
275 m_m44, m_offset4,
276 minv2, vinv2);
277 }
278 else if(m_direction == TRANSFORM_DIR_INVERSE &&
279 typedRcPtr->m_direction == TRANSFORM_DIR_FORWARD)
280 {
281 float minv1[16];
282 float vinv1[4];
283
284 if(!GetMxbInverse(minv1, vinv1, m_m44, m_offset4))
285 {
286 std::ostringstream os;
287 os << "Cannot invert primary MatrixOffsetOp op. ";
288 os << "Matrix inverse does not exist for (";
289 for(int i=0; i<16; ++i)
290 {
291 os << m_m44[i] << " ";
292 }
293 os << ").";
294 throw Exception(os.str().c_str());
295 }
296
297 GetMxbCombine(mout, vout,
298 minv1, vinv1,
299 typedRcPtr->m_m44, typedRcPtr->m_offset4);
300
301 }
302 else if(m_direction == TRANSFORM_DIR_INVERSE &&
303 typedRcPtr->m_direction == TRANSFORM_DIR_INVERSE)
304 {
305 float minv1[16];
306 float vinv1[4];
307 float minv2[16];
308 float vinv2[4];
309
310 if(!GetMxbInverse(minv1, vinv1, m_m44, m_offset4))
311 {
312 std::ostringstream os;
313 os << "Cannot invert primary MatrixOffsetOp op. ";
314 os << "Matrix inverse does not exist for (";
315 for(int i=0; i<16; ++i)
316 {
317 os << m_m44[i] << " ";
318 }
319 os << ").";
320 throw Exception(os.str().c_str());
321 }
322
323 if(!GetMxbInverse(minv2, vinv2, typedRcPtr->m_m44, typedRcPtr->m_offset4))
324 {
325 std::ostringstream os;
326 os << "Cannot invert second MatrixOffsetOp op. ";
327 os << "Matrix inverse does not exist for (";
328 for(int i=0; i<16; ++i)
329 {
330 os << typedRcPtr->m_m44[i] << " ";
331 }
332 os << ").";
333 throw Exception(os.str().c_str());
334 }
335
336 GetMxbCombine(mout, vout,
337 minv1, vinv1,
338 minv2, vinv2);
339 }
340 else
341 {
342 std::ostringstream os;
343 os << "MatrixOffsetOp cannot combine ops with unspecified ";
344 os << "directions. First op: " << m_direction << " ";
345 os << "secondOp:" << typedRcPtr->m_direction;
346 throw Exception(os.str().c_str());
347 }
348
349 CreateMatrixOffsetOp(ops,
350 mout, vout,
351 TRANSFORM_DIR_FORWARD);
352 }
353
354 bool MatrixOffsetOp::hasChannelCrosstalk() const
355 {
356 return (!m_m44IsDiagonal);
357 }
358
359 void MatrixOffsetOp::finalize()
360 {
361 m_offset4IsIdentity = IsVecEqualToZero(m_offset4, 4);
362 m_m44IsIdentity = IsM44Identity(m_m44);
363 m_m44IsDiagonal = IsM44Diagonal(m_m44);
364
365 if(m_direction == TRANSFORM_DIR_INVERSE)
366 {
367 if(!GetM44Inverse(m_m44_inv, m_m44))
368 {
369 std::ostringstream os;
370 os << "Cannot apply MatrixOffsetOp op. ";
371 os << "Matrix inverse does not exist for m44 (";
372 for(int i=0; i<16; ++i) os << m_m44[i] << " ";
373 os << ").";
374 throw Exception(os.str().c_str());
375 }
376 }
377
378 // Create the cacheID
379 md5_state_t state;
380 md5_byte_t digest[16];
381 md5_init(&state);
382 md5_append(&state, (const md5_byte_t *)m_m44, (int)(16*sizeof(float)));
383 md5_append(&state, (const md5_byte_t *)m_offset4, (int)(4*sizeof(float)));
384 md5_finish(&state, digest);
385
386 std::ostringstream cacheIDStream;
387 cacheIDStream << "<MatrixOffsetOp ";
388 cacheIDStream << GetPrintableHash(digest) << " ";
389 cacheIDStream << TransformDirectionToString(m_direction) << " ";
390 cacheIDStream << ">";
391
392 m_cacheID = cacheIDStream.str();
393 }
394
395 void MatrixOffsetOp::apply(float* rgbaBuffer, long numPixels) const
396 {
397 if(m_direction == TRANSFORM_DIR_FORWARD)
398 {
399 if(!m_m44IsIdentity)
400 {
401 if(m_m44IsDiagonal)
402 {
403 float scale[4];
404 GetM44Diagonal(scale, m_m44);
405 ApplyScale(rgbaBuffer, numPixels, scale);
406 }
407 else
408 {
409 ApplyMatrix(rgbaBuffer, numPixels, m_m44);
410 }
411 }
412
413 if(!m_offset4IsIdentity)
414 {
415 ApplyOffset(rgbaBuffer, numPixels, m_offset4);
416 }
417 }
418 else if(m_direction == TRANSFORM_DIR_INVERSE)
419 {
420 if(!m_offset4IsIdentity)
421 {
422 float offset_inv[] = { -m_offset4[0],
423 -m_offset4[1],
424 -m_offset4[2],
425 -m_offset4[3] };
426
427 ApplyOffset(rgbaBuffer, numPixels, offset_inv);
428 }
429
430 if(!m_m44IsIdentity)
431 {
432 if(m_m44IsDiagonal)
433 {
434 float scale[4];
435 GetM44Diagonal(scale, m_m44_inv);
436 ApplyScale(rgbaBuffer, numPixels, scale);
437 }
438 else
439 {
440 ApplyMatrix(rgbaBuffer, numPixels, m_m44_inv);
441 }
442 }
443 }
444 } // Op::process
445
446 bool MatrixOffsetOp::supportsGpuShader() const
447 {
448 return true;
449 }
450
451 void MatrixOffsetOp::writeGpuShader(std::ostream & shader,
452 const std::string & pixelName,
453 const GpuShaderDesc & shaderDesc) const
454 {
455 GpuLanguage lang = shaderDesc.getLanguage();
456
457 // TODO: This should not act upon alpha,
458 // since we dont apply it on the CPU?
459
460 if(m_direction == TRANSFORM_DIR_FORWARD)
461 {
462 if(!m_m44IsIdentity)
463 {
464 if(m_m44IsDiagonal)
465 {
466 shader << pixelName << " = ";
467 float scale[4];
468 GetM44Diagonal(scale, m_m44);
469 Write_half4(shader, scale, lang);
470 shader << " * " << pixelName << ";\n";
471 }
472 else
473 {
474 shader << pixelName << " = ";
475 Write_mtx_x_vec(shader,
476 GpuTextHalf4x4(m_m44, lang), pixelName,
477 lang);
478 shader << ";\n";
479 }
480 }
481
482 if(!m_offset4IsIdentity)
483 {
484 shader << pixelName << " = ";
485 Write_half4(shader, m_offset4, lang);
486 shader << " + " << pixelName << ";\n";
487 }
488 }
489 else if(m_direction == TRANSFORM_DIR_INVERSE)
490 {
491 if(!m_offset4IsIdentity)
492 {
493 float offset_inv[] = { -m_offset4[0],
494 -m_offset4[1],
495 -m_offset4[2],
496 -m_offset4[3] };
497
498 shader << pixelName << " = ";
499 Write_half4(shader, offset_inv, lang);
500 shader << " + " << pixelName << ";\n";
501 }
502
503 if(!m_m44IsIdentity)
504 {
505 if(m_m44IsDiagonal)
506 {
507 shader << pixelName << " = ";
508 float scale[4];
509 GetM44Diagonal(scale, m_m44_inv);
510 Write_half4(shader, scale, lang);
511 shader << " * " << pixelName << ";\n";
512 }
513 else
514 {
515 shader << pixelName << " = ";
516 Write_mtx_x_vec(shader,
517 GpuTextHalf4x4(m_m44_inv, lang), pixelName,
518 lang);
519 shader << ";\n";
520 }
521 }
522 }
523 }
524
525 } // Anon namespace
526
527
528
529
530
531
532
533
534
535
536 ///////////////////////////////////////////////////////////////////////////
537
538
539
540
541
542
543
544
545
546 void CreateScaleOp(OpRcPtrVec & ops,
547 const float * scale4,
548 TransformDirection direction)
549 {
550 float offset4[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
551 CreateScaleOffsetOp(ops, scale4, offset4, direction);
552 }
553
554 void CreateMatrixOp(OpRcPtrVec & ops,
555 const float * m44,
556 TransformDirection direction)
557 {
558 float offset4[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
559 CreateMatrixOffsetOp(ops, m44, offset4, direction);
560 }
561
562 void CreateOffsetOp(OpRcPtrVec & ops,
563 const float * offset4,
564 TransformDirection direction)
565 {
566 float scale4[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
567 CreateScaleOffsetOp(ops, scale4, offset4, direction);
568 }
569
570 void CreateScaleOffsetOp(OpRcPtrVec & ops,
571 const float * scale4, const float * offset4,
572 TransformDirection direction)
573 {
574 float m44[16];
575 memset(m44, 0, 16*sizeof(float));
576
577 m44[0] = scale4[0];
578 m44[5] = scale4[1];
579 m44[10] = scale4[2];
580 m44[15] = scale4[3];
581
582 CreateMatrixOffsetOp(ops,
583 m44, offset4,
584 direction);
585 }
586
587 void CreateSaturationOp(OpRcPtrVec & ops,
588 float sat,
589 const float * lumaCoef3,
590 TransformDirection direction)
591 {
592 float matrix[16];
593 float offset[4];
594 MatrixTransform::Sat(matrix, offset,
595 sat, lumaCoef3);
596
597 CreateMatrixOffsetOp(ops, matrix, offset, direction);
598 }
599
600 void CreateMatrixOffsetOp(OpRcPtrVec & ops,
601 const float * m44, const float * offset4,
602 TransformDirection direction)
603 {
604 bool mtxIsIdentity = IsM44Identity(m44);
605 bool offsetIsIdentity = IsVecEqualToZero(offset4, 4);
606 if(mtxIsIdentity && offsetIsIdentity) return;
607
608 ops.push_back( MatrixOffsetOpRcPtr(new MatrixOffsetOp(m44,
609 offset4, direction)) );
610 }
611
612 void CreateFitOp(OpRcPtrVec & ops,
613 const float * oldmin4, const float * oldmax4,
614 const float * newmin4, const float * newmax4,
615 TransformDirection direction)
616 {
617 float matrix[16];
618 float offset[4];
619 MatrixTransform::Fit(matrix, offset,
620 oldmin4, oldmax4,
621 newmin4, newmax4);
622
623 CreateMatrixOffsetOp(ops, matrix, offset, direction);
624 }
625
626 }
627 OCIO_NAMESPACE_EXIT
628
629
630
631 ////////////////////////////////////////////////////////////////////////////////
632
633
634 #ifdef OCIO_UNIT_TEST
635
636 namespace OCIO = OCIO_NAMESPACE;
637 #include "UnitTest.h"
638
639 OCIO_NAMESPACE_USING
640
OIIO_ADD_TEST(MatrixOps,Combining)641 OIIO_ADD_TEST(MatrixOps, Combining)
642 {
643 float m1[16] = { 1.1f, 0.2f, 0.3f, 0.4f,
644 0.5f, 1.6f, 0.7f, 0.8f,
645 0.2f, 0.1f, 1.1f, 0.2f,
646 0.3f, 0.4f, 0.5f, 1.6f };
647
648 float v1[4] = { -0.5f, -0.25f, 0.25f, 0.0f };
649
650 float m2[16] = { 1.1f, -0.1f, -0.1f, 0.0f,
651 0.1f, 0.9f, -0.2f, 0.0f,
652 0.05f, 0.0f, 1.1f, 0.0f,
653 0.0f, 0.0f, 0.0f, 1.0f };
654 float v2[4] = { -0.2f, -0.1f, -0.1f, -0.2f };
655
656 const float source[] = { 0.1f, 0.2f, 0.3f, 0.4f,
657 -0.1f, -0.2f, 50.0f, 123.4f,
658 1.0f, 1.0f, 1.0f, 1.0f };
659 float error = 1e-4f;
660
661 {
662 OpRcPtrVec ops;
663 CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_FORWARD);
664 CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_FORWARD);
665 OIIO_CHECK_EQUAL(ops.size(), 2);
666 ops[0]->finalize();
667 ops[1]->finalize();
668
669 OpRcPtrVec combined;
670 ops[0]->combineWith(combined, ops[1]);
671 OIIO_CHECK_EQUAL(combined.size(), 1);
672 combined[0]->finalize();
673
674 for(int test=0; test<3; ++test)
675 {
676 float tmp[4];
677 memcpy(tmp, &source[4*test], 4*sizeof(float));
678 ops[0]->apply(tmp, 1);
679 ops[1]->apply(tmp, 1);
680
681 float tmp2[4];
682 memcpy(tmp2, &source[4*test], 4*sizeof(float));
683 combined[0]->apply(tmp2, 1);
684
685 for(unsigned int i=0; i<4; ++i)
686 {
687 OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error);
688 }
689 }
690 }
691
692
693 {
694 OpRcPtrVec ops;
695 CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_FORWARD);
696 CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_INVERSE);
697 OIIO_CHECK_EQUAL(ops.size(), 2);
698 ops[0]->finalize();
699 ops[1]->finalize();
700
701 OpRcPtrVec combined;
702 ops[0]->combineWith(combined, ops[1]);
703 OIIO_CHECK_EQUAL(combined.size(), 1);
704 combined[0]->finalize();
705
706
707 for(int test=0; test<3; ++test)
708 {
709 float tmp[4];
710 memcpy(tmp, &source[4*test], 4*sizeof(float));
711 ops[0]->apply(tmp, 1);
712 ops[1]->apply(tmp, 1);
713
714 float tmp2[4];
715 memcpy(tmp2, &source[4*test], 4*sizeof(float));
716 combined[0]->apply(tmp2, 1);
717
718 for(unsigned int i=0; i<4; ++i)
719 {
720 OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error);
721 }
722 }
723 }
724
725 {
726 OpRcPtrVec ops;
727 CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_INVERSE);
728 CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_FORWARD);
729 OIIO_CHECK_EQUAL(ops.size(), 2);
730 ops[0]->finalize();
731 ops[1]->finalize();
732
733 OpRcPtrVec combined;
734 ops[0]->combineWith(combined, ops[1]);
735 OIIO_CHECK_EQUAL(combined.size(), 1);
736 combined[0]->finalize();
737
738 for(int test=0; test<3; ++test)
739 {
740 float tmp[4];
741 memcpy(tmp, &source[4*test], 4*sizeof(float));
742 ops[0]->apply(tmp, 1);
743 ops[1]->apply(tmp, 1);
744
745 float tmp2[4];
746 memcpy(tmp2, &source[4*test], 4*sizeof(float));
747 combined[0]->apply(tmp2, 1);
748
749 for(unsigned int i=0; i<4; ++i)
750 {
751 OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error);
752 }
753 }
754 }
755
756 {
757 OpRcPtrVec ops;
758 CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_INVERSE);
759 CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_INVERSE);
760 OIIO_CHECK_EQUAL(ops.size(), 2);
761 ops[0]->finalize();
762 ops[1]->finalize();
763
764 OpRcPtrVec combined;
765 ops[0]->combineWith(combined, ops[1]);
766 OIIO_CHECK_EQUAL(combined.size(), 1);
767 combined[0]->finalize();
768
769 for(int test=0; test<3; ++test)
770 {
771 float tmp[4];
772 memcpy(tmp, &source[4*test], 4*sizeof(float));
773 ops[0]->apply(tmp, 1);
774 ops[1]->apply(tmp, 1);
775
776 float tmp2[4];
777 memcpy(tmp2, &source[4*test], 4*sizeof(float));
778 combined[0]->apply(tmp2, 1);
779
780 for(unsigned int i=0; i<4; ++i)
781 {
782 OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error);
783 }
784 }
785 }
786 }
787
788 #endif
789