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