Merge "Add utc test cases" into tizen
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / image-view / masked-image-view-impl.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/controls/image-view/masked-image-view-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <sstream>
23 #include <dali/public-api/animation/constraint.h>
24 #include <dali/public-api/animation/constraints.h>
25 #include <dali/public-api/common/stage.h>
26 #include <dali/public-api/render-tasks/render-task-list.h>
27 #include <dali/public-api/shader-effects/shader-effect.h>
28
29 namespace Dali
30 {
31
32 namespace Toolkit
33 {
34
35 namespace Internal
36 {
37
38 namespace // unnamed namespace
39 {
40
41 const char* CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::CUSTOM_PROPERTY_COUNT ] =
42 {
43   "background-color",
44   "source-size",
45   "source-offset",
46   "mask-size",
47   "mask-offset"
48 };
49
50 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE =
51   "precision mediump float;                                                                                      \n"
52   "uniform vec2 uTargetSize;                                                                                     \n"
53   "uniform vec2 uSourceSize;                                                                                     \n"
54   "uniform vec2 uSourceOffset;                                                                                   \n"
55   "uniform vec2 uMaskSize;                                                                                       \n"
56   "uniform vec2 uMaskOffset;                                                                                     \n"
57   "varying vec2 vMaskTexCoord;                                                                                   \n"
58   "void main()                                                                                                   \n"
59   "{                                                                                                             \n"
60   "  float x = uSourceSize.x*aPosition.x + uSourceOffset.x;                                                      \n"
61   "  float y = uSourceSize.y*aPosition.y + uSourceOffset.y;                                                      \n"
62   "                                                                                                              \n"
63   "  gl_Position = vec4( x/(uTargetSize.x*0.5), y/(uTargetSize.y*0.5), 0.0, 1.0 );                               \n"
64   "                                                                                                              \n"
65   "  vMaskTexCoord.x = (uMaskSize.x*0.5 + x - uMaskOffset.x) / uMaskSize.x;                                      \n"
66   "  vMaskTexCoord.y = (uMaskSize.y*0.5 + y - uMaskOffset.y) / uMaskSize.y;                                      \n";
67
68 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0 =
69   "                                                                                                              \n"
70   "  vTexCoord = aTexCoord;                                                                                      \n"
71   "}";
72
73 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90 =
74   "                                                                                                              \n"
75   "  vTexCoord.x = aTexCoord.y;                                                                                  \n"
76   "  vTexCoord.y = 1.0 - aTexCoord.x;                                                                            \n"
77   "}";
78
79 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180 =
80   "                                                                                                              \n"
81   "  vTexCoord.x = 1.0 - aTexCoord.x;                                                                            \n"
82   "  vTexCoord.y = 1.0 - aTexCoord.y;                                                                            \n"
83   "}";
84
85 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270 =
86   "                                                                                                              \n"
87   "  vTexCoord.x = 1.0 - aTexCoord.y;                                                                            \n"
88   "  vTexCoord.y = aTexCoord.x;                                                                                  \n"
89   "}";
90
91 const char* const MASKED_IMAGE_VIEW_FRAGMENT_SOURCE =
92   "precision mediump float;                                                                                      \n"
93   "varying vec2 vMaskTexCoord;                                                                                   \n"
94   "void main()                                                                                                   \n"
95   "{                                                                                                             \n"
96   "  highp vec4 mask = texture2D(sEffect, vMaskTexCoord);                                                        \n"
97   "  gl_FragColor = texture2D(sTexture, vTexCoord) * vec4(1,1,1,mask.a);                                         \n"
98   "}";
99
100 Vector2 GetSizeForAspectRatio( const Vector2& targetSize, float aspectRatio )
101 {
102   Vector2 sizeToKeepAspectRatio( targetSize );
103
104   float targetAspectRatio( targetSize.width / targetSize.height );
105
106   if( aspectRatio > targetAspectRatio )
107   {
108     sizeToKeepAspectRatio.width = sizeToKeepAspectRatio.height * aspectRatio;
109   }
110   else if ( aspectRatio < targetAspectRatio )
111   {
112     sizeToKeepAspectRatio.height = sizeToKeepAspectRatio.width / aspectRatio;
113   }
114
115   return sizeToKeepAspectRatio;
116 }
117
118 Vector2 ClampSourceSize( const Vector2& sourceSize, const Vector2& targetSize, float widthOverHeight, float maxSourceScale )
119 {
120   Vector2 clampedSize( sourceSize );
121
122   Vector2 minSize( targetSize );
123   if ( widthOverHeight > 0.0f )
124   {
125     minSize = GetSizeForAspectRatio( targetSize, widthOverHeight );
126   }
127
128   if ( clampedSize.width  < minSize.width ||
129        clampedSize.height < minSize.height )
130   {
131     clampedSize = minSize;
132   }
133   else if ( clampedSize.width  > minSize.width *maxSourceScale ||
134             clampedSize.height > minSize.height*maxSourceScale )
135   {
136     clampedSize = minSize * maxSourceScale;
137   }
138
139   return clampedSize;
140 }
141
142 Vector2 ClampSourceOffset( const Vector2& sourceOffset, const Vector2& targetSize, const Vector2& sourceSize )
143 {
144   Vector2 min, max;
145
146   if ( sourceSize.width > targetSize.width )
147   {
148     float offset = (sourceSize.width - targetSize.width) * 0.5f;
149     min.x = -offset;
150     max.x =  offset;
151   }
152
153   if ( sourceSize.height > targetSize.height )
154   {
155     float offset = (sourceSize.height - targetSize.height) * 0.5f;
156     min.y = -offset;
157     max.y =  offset;
158   }
159
160   return Vector2( Clamp(sourceOffset.x, min.x, max.x), Clamp(sourceOffset.y, min.y, max.y) );
161 }
162
163 } // unnamed namespace
164
165 Dali::Toolkit::MaskedImageView MaskedImageView::New( unsigned int targetWidth,
166                                                      unsigned int targetHeight,
167                                                      Image sourceImage,
168                                                      Image maskImage )
169 {
170   // Create the implementation
171   MaskedImageView* maskedImageView = new MaskedImageView();
172
173   // Pass ownership to CustomActor via derived handle
174   Dali::Toolkit::MaskedImageView handle(*maskedImageView);
175
176   // Second-phase init of the implementation
177   // This can only be done after the CustomActor connection has been made...
178   maskedImageView->Initialize( targetWidth, targetHeight, sourceImage, maskImage );
179
180   return handle;
181 }
182
183 void MaskedImageView::SetSourceImage( Image sourceImage )
184 {
185   mSourceImageActor.SetImage( sourceImage );
186 }
187
188 Image MaskedImageView::GetSourceImage()
189 {
190   return mSourceImageActor.GetImage();
191 }
192
193 void MaskedImageView::SetMaskImage( Image maskImage )
194 {
195   mMaskImage = maskImage;
196   mSourceImageActor.GetShaderEffect().SetEffectImage( maskImage );
197 }
198
199 Image MaskedImageView::GetMaskImage()
200 {
201   return mMaskImage;
202 }
203
204 Property::Index MaskedImageView::GetPropertyIndex( Dali::Toolkit::MaskedImageView::CustomProperty customProperty ) const
205 {
206   Property::Index index = Property::INVALID_INDEX;
207
208   switch ( customProperty )
209   {
210     case Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR:
211     {
212       index = mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ];
213       break;
214     }
215
216     case Dali::Toolkit::MaskedImageView::SOURCE_SIZE:
217     {
218       index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ];
219       break;
220     }
221
222     case Dali::Toolkit::MaskedImageView::SOURCE_OFFSET:
223     {
224       index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ];
225       break;
226     }
227
228     case Dali::Toolkit::MaskedImageView::MASK_SIZE:
229     {
230       index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ];
231       break;
232     }
233
234     case Dali::Toolkit::MaskedImageView::MASK_OFFSET:
235     {
236       index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ];
237       break;
238     }
239
240     default:
241       break;
242   }
243
244   return index;
245 }
246
247 void MaskedImageView::Pause()
248 {
249   if ( mRenderTask )
250   {
251     mRenderTask.SetRefreshRate( RenderTask::REFRESH_ONCE );
252   }
253 }
254
255 void MaskedImageView::Resume()
256 {
257   if ( mRenderTask )
258   {
259     mRenderTask.SetRefreshRate( RenderTask::REFRESH_ALWAYS );
260   }
261 }
262
263 bool MaskedImageView::IsPaused() const
264 {
265   if( mRenderTask.GetRefreshRate() ) // REFRESH_ALWAYS
266   {
267     return false;
268   }
269   else // REFRESH_ONCE
270   {
271     return true;
272   }
273 }
274
275 void MaskedImageView::SetEditMode( Dali::Toolkit::MaskedImageView::EditMode editMode )
276 {
277   Actor self = Self();
278
279   mEditMode = editMode;
280
281   if ( Dali::Toolkit::MaskedImageView::EDIT_DISABLED == editMode )
282   {
283     if ( mPanGestureDetector )
284     {
285       mPanGestureDetector.DetachAll();
286       mPanGestureDetector.Reset();
287     }
288
289     if ( mPinchDetector )
290     {
291       mPinchDetector.DetachAll();
292       mPinchDetector.Reset();
293     }
294   }
295   else
296   {
297     if ( !mPanGestureDetector )
298     {
299       mPanGestureDetector = PanGestureDetector::New();
300       mPanGestureDetector.Attach( self );
301       mPanGestureDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPan);
302     }
303
304     if ( !mPinchDetector )
305     {
306       mPinchDetector = PinchGestureDetector::New();
307       mPinchDetector.Attach( self );
308       mPinchDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPinch);
309     }
310
311     if( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == editMode )
312     {
313       // Re-clamp values to preserve image aspect-ratio etc.
314       ClampSourceSizeAndOffset();
315     }
316   }
317 }
318
319 Dali::Toolkit::MaskedImageView::EditMode MaskedImageView::GetEditMode() const
320 {
321   return mEditMode;
322 }
323
324 void MaskedImageView::OnPropertySet( Property::Index index, Property::Value propertyValue )
325 {
326   // Ignore OnPropertySet if MaskedImageView is setting the properties
327   if( !mSelfPropertySetting )
328   {
329     // Synchronize with user-supplied property values...
330     if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] == index )
331     {
332       // Note that clamping will take effect when edit-mode is used later
333       mSourcePosition.mStartPinchSize   = propertyValue.Get<Vector2>();
334       mSourcePosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
335     }
336     else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] == index )
337     {
338       // Note that clamping will take effect when edit-mode is used later
339       mSourcePosition.mPanOffset = propertyValue.Get<Vector2>();
340     }
341     else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] == index )
342     {
343       mMaskPosition.mStartPinchSize   = propertyValue.Get<Vector2>();
344       mMaskPosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
345     }
346     else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] == index )
347     {
348       mMaskPosition.mPanOffset = propertyValue.Get<Vector2>();
349     }
350     // else it's fine to do nothing here
351   }
352 }
353
354 void MaskedImageView::OnPan(Actor source, const PanGesture& gesture)
355 {
356   // Used to flag whether edit mode is setting properties
357   mSelfPropertySetting = true;
358
359   Actor self = Self();
360
361   if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
362   {
363     mSourcePosition.mPanOffset += gesture.displacement;
364     mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
365
366     self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
367   }
368   else // Edit mask
369   {
370     mMaskPosition.mPanOffset += gesture.displacement;
371
372     self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_OFFSET ), mMaskPosition.mPanOffset );
373   }
374
375   // Used to flag whether edit mode is setting properties
376   mSelfPropertySetting = false;
377 }
378
379 void MaskedImageView::OnPinch(Actor actor, const PinchGesture& pinch)
380 {
381   // Used to flag whether edit mode is setting properties
382   mSelfPropertySetting = true;
383
384   Actor self = Self();
385
386   if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
387   {
388     if ( pinch.state == Gesture::Started )
389     {
390       mSourcePosition.mStartPinchSize = mSourcePosition.mCurrentPinchSize;
391     }
392
393     mSourcePosition.mCurrentPinchSize = mSourcePosition.mStartPinchSize * pinch.scale;
394
395     ClampSourceSizeAndOffset();
396   }
397   else // Edit mask
398   {
399     if ( pinch.state == Gesture::Started )
400     {
401       mMaskPosition.mStartPinchSize = mMaskPosition.mCurrentPinchSize;
402     }
403
404     mMaskPosition.mCurrentPinchSize = mMaskPosition.mStartPinchSize * pinch.scale;
405
406     self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_SIZE ), mMaskPosition.mCurrentPinchSize );
407   }
408
409   // Used to flag whether edit mode is setting properties
410   mSelfPropertySetting = false;
411 }
412
413 void MaskedImageView::SetSourceAspectRatio( float widthOverHeight )
414 {
415   Actor self = Self();
416
417   if ( widthOverHeight > 0.0f )
418   {
419     mWidthOverHeight = widthOverHeight;
420
421     ClampSourceSizeAndOffset();
422   }
423   else
424   {
425     mWidthOverHeight = 0.0f; // ignore aspect-ratio
426   }
427 }
428
429 float MaskedImageView::GetSourceAspectRatio() const
430 {
431   return mWidthOverHeight;
432 }
433
434 void MaskedImageView::SetMaximumSourceScale( float scale )
435 {
436   mMaximumSourceScale = scale;
437 }
438
439 float MaskedImageView::GetMaximumSourceScale() const
440 {
441   return mMaximumSourceScale;
442 }
443
444 void MaskedImageView::SetSourceRotation( MaskedImageView::ImageRotation newRotation )
445 {
446   if( mSourceRotation != newRotation )
447   {
448     bool oldLandscape( Dali::Toolkit::MaskedImageView::ROTATE_90 == mSourceRotation || Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation );
449     bool newLandscape( Dali::Toolkit::MaskedImageView::ROTATE_90 == newRotation     || Dali::Toolkit::MaskedImageView::ROTATE_270 == newRotation     );
450
451     if ( oldLandscape != newLandscape )
452     {
453       // Changing between landscape & portraint, swap width & height
454       float temp = mSourcePosition.mCurrentPinchSize.width;
455       mSourcePosition.mCurrentPinchSize.width  = mSourcePosition.mCurrentPinchSize.height;
456       mSourcePosition.mCurrentPinchSize.height = temp;
457     }
458
459     mSourceRotation = newRotation;
460
461     ApplyMaskedImageShader( newRotation );
462
463     ClampSourceSizeAndOffset();
464   }
465 }
466
467 MaskedImageView::ImageRotation MaskedImageView::GetSourceRotation() const
468 {
469   return mSourceRotation;
470 }
471
472 Dali::Toolkit::MaskedImageView::MaskedImageViewSignal& MaskedImageView::MaskFinishedSignal()
473 {
474   return mMaskFinishedSignal;
475 }
476
477 MaskedImageView::MaskedImageView()
478 : Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
479   mEditMode( Dali::Toolkit::MaskedImageView::EDIT_DISABLED ),
480   mSelfPropertySetting( false ),
481   mSourceRotation( Dali::Toolkit::MaskedImageView::ROTATE_0 ),
482   mWidthOverHeight( 0.0f ),
483   mMaximumSourceScale( Dali::Toolkit::MaskedImageView::DEFAULT_MAXIMUM_SOURCE_SCALE )
484 {
485 }
486
487 void MaskedImageView::Initialize( unsigned int targetWidth,
488                                   unsigned int targetHeight,
489                                   Image sourceImage,
490                                   Image maskImage )
491 {
492   Actor self = Self();
493
494   // Register custom properties
495
496   mTargetSize = Vector2( static_cast<float>(targetWidth), static_cast<float>(targetHeight) );
497
498   mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ]
499     = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ], Color::BLACK  );
500
501   mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ]
502     = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ], mTargetSize );
503
504   mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ]
505     = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ], Vector2::ZERO );
506
507   mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ]
508     = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_SIZE ], mTargetSize );
509
510   mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ]
511     = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ], Vector2::ZERO );
512
513   // Create destination image (FBO)
514   mDestinationImage = FrameBufferImage::New( targetWidth, targetHeight, Pixel::RGBA8888 );
515
516   // Create source actor for off-screen image processing
517   mSourceImageActor = ImageActor::New( sourceImage );
518   self.Add( mSourceImageActor );
519   mSourceImageActor.SetParentOrigin( ParentOrigin::CENTER );
520   mSourceImageActor.SetPositionInheritanceMode( DONT_INHERIT_POSITION );
521   mSourceImageActor.SetInheritOrientation( false );
522   mSourceImageActor.SetInheritScale( false );
523   mSourceImageActor.SetColorMode( USE_OWN_COLOR );
524   mSourceImageActor.SetSize( Vector3::ONE );
525
526   // Apply masking effect to source actor
527   mMaskImage = maskImage;
528   ApplyMaskedImageShader( Dali::Toolkit::MaskedImageView::ROTATE_0 );
529
530   // Create actor to display result of off-screen rendering
531   mDestinationImageActor = ImageActor::New( mDestinationImage );
532   self.Add( mDestinationImageActor );
533   mDestinationImageActor.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
534
535   // Start the masking operation
536   mRenderTask = Stage::GetCurrent().GetRenderTaskList().CreateTask();
537   mRenderTask.SetSourceActor( mSourceImageActor );
538   mRenderTask.SetTargetFrameBuffer( mDestinationImage );
539   mRenderTask.SetInputEnabled( false );
540   mRenderTask.SetExclusive( true );
541   mRenderTask.SetClearEnabled( true );
542
543   Constraint clearColorConstraint = Constraint::New<Vector4>( mRenderTask, RenderTask::Property::CLEAR_COLOR, EqualToConstraint() );
544   clearColorConstraint.AddSource( Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ] ) );
545   clearColorConstraint.Apply();
546   mRenderTask.FinishedSignal().Connect( this, &MaskedImageView::OnRenderTaskFinished );
547
548   // Edit mode initialization
549   mSourcePosition.mCurrentPinchSize = Vector2( targetWidth, targetHeight );
550   mMaskPosition.mCurrentPinchSize   = mSourcePosition.mCurrentPinchSize;
551 }
552
553 void MaskedImageView::ApplyMaskedImageShader( ImageRotation rotation )
554 {
555   Actor self = Self();
556
557   // Vertex shader has different postfix for each rotation
558   std::stringstream vertexSource;
559   vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE;
560   if( Dali::Toolkit::MaskedImageView::ROTATE_90 == rotation )
561   {
562     vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90;
563   }
564   else if( Dali::Toolkit::MaskedImageView::ROTATE_180 == rotation )
565   {
566     vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180;
567   }
568   else if( Dali::Toolkit::MaskedImageView::ROTATE_270 == rotation )
569   {
570     vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270;
571   }
572   else // Default to Dali::Toolkit::MaskedImageView::ROTATE_0
573   {
574     vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0;
575   }
576
577   ShaderEffect shader = ShaderEffect::New( vertexSource.str(),
578                                            MASKED_IMAGE_VIEW_FRAGMENT_SOURCE,
579                                            GeometryType( GEOMETRY_TYPE_IMAGE ),
580                                            ShaderEffect::GeometryHints( ShaderEffect::HINT_BLENDING ) );
581
582   shader.SetUniform( "uTargetSize", mTargetSize );
583
584   shader.SetUniform( "uSourceSize", mTargetSize );
585   Constraint sourceSizeConstraint = Constraint::New<Vector2>( shader, shader.GetPropertyIndex( "uSourceSize" ), EqualToConstraint() );
586   sourceSizeConstraint.AddSource( Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] ) );
587   sourceSizeConstraint.Apply();
588
589   shader.SetUniform( "uSourceOffset", Vector2::ZERO );
590   Constraint sourceOffsetConstraint = Constraint::New<Vector2>( shader, shader.GetPropertyIndex( "uSourceOffset" ), EqualToConstraint() );
591   sourceOffsetConstraint.AddSource( Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] ) );
592   sourceOffsetConstraint.Apply();
593
594   shader.SetUniform( "uMaskSize", mTargetSize );
595   Constraint maskSizeConstraint = Constraint::New<Vector2>( shader, shader.GetPropertyIndex( "uMaskSize" ), EqualToConstraint() );
596   maskSizeConstraint.AddSource( Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] ) );
597   maskSizeConstraint.Apply();
598
599   shader.SetUniform( "uMaskOffset", mTargetSize );
600   Constraint maskOffsetConstraint = Constraint::New<Vector2>( shader, shader.GetPropertyIndex( "uMaskOffset" ), EqualToConstraint() );
601   maskOffsetConstraint.AddSource( Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] ) );
602   maskOffsetConstraint.Apply();
603
604   shader.SetEffectImage( mMaskImage );
605   mSourceImageActor.SetShaderEffect( shader );
606 }
607
608 void MaskedImageView::ClampSourceSizeAndOffset()
609 {
610   float rotatedAspectRatio( mWidthOverHeight );
611   if( mWidthOverHeight > 0.0f &&
612       ( Dali::Toolkit::MaskedImageView::ROTATE_90  == mSourceRotation ||
613         Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation ) )
614   {
615     rotatedAspectRatio = 1.0f / mWidthOverHeight;
616   }
617
618   Actor self = Self();
619
620   mSourcePosition.mCurrentPinchSize = ClampSourceSize( mSourcePosition.mCurrentPinchSize, mTargetSize, rotatedAspectRatio, mMaximumSourceScale );
621   self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_SIZE ), mSourcePosition.mCurrentPinchSize );
622
623   mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
624   self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
625 }
626
627 MaskedImageView::~MaskedImageView()
628 {
629   // Guard to allow handle destruction after Core has been destroyed
630   if( Stage::IsInstalled() )
631   {
632     Stage::GetCurrent().GetRenderTaskList().RemoveTask( mRenderTask );
633   }
634 }
635
636 void MaskedImageView::OnControlSizeSet( const Vector3& targetSize )
637 {
638   mDestinationImageActor.SetSize(targetSize);
639 }
640
641 void MaskedImageView::OnRenderTaskFinished( Dali::RenderTask& renderTask )
642 {
643   Toolkit::MaskedImageView handle( GetOwner() );
644   mMaskFinishedSignal.Emit( handle );
645 }
646
647 } // namespace Internal
648
649 } // namespace Toolkit
650
651 } // namespace Dali