2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.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
8 // http://floralicense.org/license/
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.
21 #include <dali-toolkit/internal/controls/image-view/masked-image-view-impl.h>
32 namespace // unnamed namespace
35 const char* CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::CUSTOM_PROPERTY_COUNT ] =
44 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE =
45 "precision mediump float; \n"
46 "uniform vec2 uTargetSize; \n"
47 "uniform vec2 uSourceSize; \n"
48 "uniform vec2 uSourceOffset; \n"
49 "uniform vec2 uMaskSize; \n"
50 "uniform vec2 uMaskOffset; \n"
51 "varying vec2 vMaskTexCoord; \n"
54 " float x = uSourceSize.x*aPosition.x + uSourceOffset.x; \n"
55 " float y = uSourceSize.y*aPosition.y + uSourceOffset.y; \n"
57 " gl_Position = vec4( x/(uTargetSize.x*0.5), y/(uTargetSize.y*0.5), 0.0, 1.0 ); \n"
59 " vMaskTexCoord.x = (uMaskSize.x*0.5 + x - uMaskOffset.x) / uMaskSize.x; \n"
60 " vMaskTexCoord.y = (uMaskSize.y*0.5 + y - uMaskOffset.y) / uMaskSize.y; \n";
62 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0 =
64 " vTexCoord = aTexCoord; \n"
67 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90 =
69 " vTexCoord.x = aTexCoord.y; \n"
70 " vTexCoord.y = 1.0 - aTexCoord.x; \n"
73 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180 =
75 " vTexCoord.x = 1.0 - aTexCoord.x; \n"
76 " vTexCoord.y = 1.0 - aTexCoord.y; \n"
79 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270 =
81 " vTexCoord.x = 1.0 - aTexCoord.y; \n"
82 " vTexCoord.y = aTexCoord.x; \n"
85 const char* const MASKED_IMAGE_VIEW_FRAGMENT_SOURCE =
86 "precision mediump float; \n"
87 "varying vec2 vMaskTexCoord; \n"
90 " highp vec4 mask = texture2D(sEffect, vMaskTexCoord); \n"
91 " gl_FragColor = texture2D(sTexture, vTexCoord) * vec4(1,1,1,mask.a); \n"
94 Vector2 EqualToConstraintVector2( const Vector2& current, const PropertyInput& property )
96 return property.GetVector2();
99 Vector2 GetSizeForAspectRatio( const Vector2& targetSize, float aspectRatio )
101 Vector2 sizeToKeepAspectRatio( targetSize );
103 float targetAspectRatio( targetSize.width / targetSize.height );
105 if( aspectRatio > targetAspectRatio )
107 sizeToKeepAspectRatio.width = sizeToKeepAspectRatio.height * aspectRatio;
109 else if ( aspectRatio < targetAspectRatio )
111 sizeToKeepAspectRatio.height = sizeToKeepAspectRatio.width / aspectRatio;
114 return sizeToKeepAspectRatio;
117 Vector2 ClampSourceSize( const Vector2& sourceSize, const Vector2& targetSize, float widthOverHeight, float maxSourceScale )
119 Vector2 clampedSize( sourceSize );
121 Vector2 minSize( targetSize );
122 if ( widthOverHeight > 0.0f )
124 minSize = GetSizeForAspectRatio( targetSize, widthOverHeight );
127 if ( clampedSize.width < minSize.width ||
128 clampedSize.height < minSize.height )
130 clampedSize = minSize;
132 else if ( clampedSize.width > minSize.width *maxSourceScale ||
133 clampedSize.height > minSize.height*maxSourceScale )
135 clampedSize = minSize * maxSourceScale;
141 Vector2 ClampSourceOffset( const Vector2& sourceOffset, const Vector2& targetSize, const Vector2& sourceSize )
145 if ( sourceSize.width > targetSize.width )
147 float offset = (sourceSize.width - targetSize.width) * 0.5f;
152 if ( sourceSize.height > targetSize.height )
154 float offset = (sourceSize.height - targetSize.height) * 0.5f;
159 return Vector2( Clamp(sourceOffset.x, min.x, max.x), Clamp(sourceOffset.y, min.y, max.y) );
162 } // unnamed namespace
164 Dali::Toolkit::MaskedImageView MaskedImageView::New( unsigned int targetWidth,
165 unsigned int targetHeight,
169 // Create the implementation
170 MaskedImageView* maskedImageView = new MaskedImageView();
172 // Pass ownership to CustomActor via derived handle
173 Dali::Toolkit::MaskedImageView handle(*maskedImageView);
175 // Second-phase init of the implementation
176 // This can only be done after the CustomActor connection has been made...
177 maskedImageView->Initialize( targetWidth, targetHeight, sourceImage, maskImage );
182 void MaskedImageView::SetSourceImage( Image sourceImage )
184 mSourceImageActor.SetImage( sourceImage );
187 Image MaskedImageView::GetSourceImage()
189 return mSourceImageActor.GetImage();
192 void MaskedImageView::SetMaskImage( Image maskImage )
194 mMaskImage = maskImage;
195 mSourceImageActor.GetShaderEffect().SetEffectImage( maskImage );
198 Image MaskedImageView::GetMaskImage()
203 Property::Index MaskedImageView::GetPropertyIndex( Dali::Toolkit::MaskedImageView::CustomProperty customProperty ) const
205 Property::Index index = Property::INVALID_INDEX;
207 switch ( customProperty )
209 case Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR:
211 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ];
215 case Dali::Toolkit::MaskedImageView::SOURCE_SIZE:
217 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ];
221 case Dali::Toolkit::MaskedImageView::SOURCE_OFFSET:
223 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ];
227 case Dali::Toolkit::MaskedImageView::MASK_SIZE:
229 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ];
233 case Dali::Toolkit::MaskedImageView::MASK_OFFSET:
235 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ];
246 void MaskedImageView::Pause()
250 mRenderTask.SetRefreshRate( RenderTask::REFRESH_ONCE );
254 void MaskedImageView::Resume()
258 mRenderTask.SetRefreshRate( RenderTask::REFRESH_ALWAYS );
262 bool MaskedImageView::IsPaused() const
264 if( mRenderTask.GetRefreshRate() ) // REFRESH_ALWAYS
274 void MaskedImageView::SetEditMode( Dali::Toolkit::MaskedImageView::EditMode editMode )
278 mEditMode = editMode;
280 if ( Dali::Toolkit::MaskedImageView::EDIT_DISABLED == editMode )
282 if ( mPanGestureDetector )
284 mPanGestureDetector.DetachAll();
285 mPanGestureDetector.Reset();
288 if ( mPinchDetector )
290 mPinchDetector.DetachAll();
291 mPinchDetector.Reset();
296 if ( !mPanGestureDetector )
298 mPanGestureDetector = PanGestureDetector::New();
299 mPanGestureDetector.Attach( self );
300 mPanGestureDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPan);
303 if ( !mPinchDetector )
305 mPinchDetector = PinchGestureDetector::New();
306 mPinchDetector.Attach( self );
307 mPinchDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPinch);
310 if( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == editMode )
312 // Re-clamp values to preserve image aspect-ratio etc.
313 ClampSourceSizeAndOffset();
318 Dali::Toolkit::MaskedImageView::EditMode MaskedImageView::GetEditMode() const
323 void MaskedImageView::OnPropertySet( Property::Index index, Property::Value propertyValue )
325 // Ignore OnPropertySet if MaskedImageView is setting the properties
326 if( !mSelfPropertySetting )
328 // Synchronize with user-supplied property values...
329 if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] == index )
331 // Note that clamping will take effect when edit-mode is used later
332 mSourcePosition.mStartPinchSize = propertyValue.Get<Vector2>();
333 mSourcePosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
335 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] == index )
337 // Note that clamping will take effect when edit-mode is used later
338 mSourcePosition.mPanOffset = propertyValue.Get<Vector2>();
340 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] == index )
342 mMaskPosition.mStartPinchSize = propertyValue.Get<Vector2>();
343 mMaskPosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
345 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] == index )
347 mMaskPosition.mPanOffset = propertyValue.Get<Vector2>();
349 // else it's fine to do nothing here
353 void MaskedImageView::OnPan(Actor source, PanGesture gesture)
355 // Used to flag whether edit mode is setting properties
356 mSelfPropertySetting = true;
360 if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
362 mSourcePosition.mPanOffset += gesture.displacement;
363 mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
365 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
369 mMaskPosition.mPanOffset += gesture.displacement;
371 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_OFFSET ), mMaskPosition.mPanOffset );
374 // Used to flag whether edit mode is setting properties
375 mSelfPropertySetting = false;
378 void MaskedImageView::OnPinch(Actor actor, PinchGesture pinch)
380 // Used to flag whether edit mode is setting properties
381 mSelfPropertySetting = true;
385 if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
387 if ( pinch.state == Gesture::Started )
389 mSourcePosition.mStartPinchSize = mSourcePosition.mCurrentPinchSize;
392 mSourcePosition.mCurrentPinchSize = mSourcePosition.mStartPinchSize * pinch.scale;
394 ClampSourceSizeAndOffset();
398 if ( pinch.state == Gesture::Started )
400 mMaskPosition.mStartPinchSize = mMaskPosition.mCurrentPinchSize;
403 mMaskPosition.mCurrentPinchSize = mMaskPosition.mStartPinchSize * pinch.scale;
405 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_SIZE ), mMaskPosition.mCurrentPinchSize );
408 // Used to flag whether edit mode is setting properties
409 mSelfPropertySetting = false;
412 void MaskedImageView::SetSourceAspectRatio( float widthOverHeight )
416 if ( widthOverHeight > 0.0f )
418 mWidthOverHeight = widthOverHeight;
420 ClampSourceSizeAndOffset();
424 mWidthOverHeight = 0.0f; // ignore aspect-ratio
428 float MaskedImageView::GetSourceAspectRatio() const
430 return mWidthOverHeight;
433 void MaskedImageView::SetMaximumSourceScale( float scale )
435 mMaximumSourceScale = scale;
438 float MaskedImageView::GetMaximumSourceScale() const
440 return mMaximumSourceScale;
443 void MaskedImageView::SetSourceRotation( MaskedImageView::ImageRotation newRotation )
445 if( mSourceRotation != newRotation )
447 bool oldLandscape( Dali::Toolkit::MaskedImageView::ROTATE_90 == mSourceRotation || Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation );
448 bool newLandscape( Dali::Toolkit::MaskedImageView::ROTATE_90 == newRotation || Dali::Toolkit::MaskedImageView::ROTATE_270 == newRotation );
450 if ( oldLandscape != newLandscape )
452 // Changing between landscape & portraint, swap width & height
453 float temp = mSourcePosition.mCurrentPinchSize.width;
454 mSourcePosition.mCurrentPinchSize.width = mSourcePosition.mCurrentPinchSize.height;
455 mSourcePosition.mCurrentPinchSize.height = temp;
458 mSourceRotation = newRotation;
460 ApplyMaskedImageShader( newRotation );
462 ClampSourceSizeAndOffset();
466 MaskedImageView::ImageRotation MaskedImageView::GetSourceRotation() const
468 return mSourceRotation;
471 Dali::Toolkit::MaskedImageView::MaskedImageViewSignal& MaskedImageView::MaskFinishedSignal()
473 return mMaskFinishedSignal;
476 MaskedImageView::MaskedImageView()
478 mEditMode( Dali::Toolkit::MaskedImageView::EDIT_DISABLED ),
479 mSelfPropertySetting( false ),
480 mSourceRotation( Dali::Toolkit::MaskedImageView::ROTATE_0 ),
481 mWidthOverHeight( 0.0f ),
482 mMaximumSourceScale( Dali::Toolkit::MaskedImageView::DEFAULT_MAXIMUM_SOURCE_SCALE )
486 void MaskedImageView::Initialize( unsigned int targetWidth,
487 unsigned int targetHeight,
493 // Register custom properties
495 mTargetSize = Vector2( static_cast<float>(targetWidth), static_cast<float>(targetHeight) );
497 mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ]
498 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ], Color::BLACK );
500 mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ]
501 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ], mTargetSize );
503 mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ]
504 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ], Vector2::ZERO );
506 mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ]
507 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_SIZE ], mTargetSize );
509 mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ]
510 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ], Vector2::ZERO );
512 // Create destination image (FBO)
513 mDestinationImage = FrameBufferImage::New( targetWidth, targetHeight, Pixel::RGBA8888 );
515 // Create source actor for off-screen image processing
516 mSourceImageActor = ImageActor::New( sourceImage );
517 self.Add( mSourceImageActor );
518 mSourceImageActor.SetParentOrigin( ParentOrigin::CENTER );
519 mSourceImageActor.SetPositionInheritanceMode( DONT_INHERIT_POSITION );
520 mSourceImageActor.SetInheritRotation( false );
521 mSourceImageActor.SetInheritScale( false );
522 mSourceImageActor.SetColorMode( USE_OWN_COLOR );
523 mSourceImageActor.SetSize( Vector3::ONE );
525 // Apply masking effect to source actor
526 mMaskImage = maskImage;
527 ApplyMaskedImageShader( Dali::Toolkit::MaskedImageView::ROTATE_0 );
529 // Create actor to display result of off-screen rendering
530 mDestinationImageActor = ImageActor::New( mDestinationImage );
531 self.Add( mDestinationImageActor );
532 mDestinationImageActor.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), EqualToConstraint() ) );
533 mDestinationImageActor.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
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 mRenderTask.ApplyConstraint( Constraint::New<Vector4>( RenderTask::CLEAR_COLOR,
543 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ] ),
544 EqualToConstraint() ) );
545 mRenderTask.FinishedSignal().Connect( this, &MaskedImageView::OnRenderTaskFinished );
547 // Edit mode initialization
548 mSourcePosition.mCurrentPinchSize = Vector2( targetWidth, targetHeight );
549 mMaskPosition.mCurrentPinchSize = mSourcePosition.mCurrentPinchSize;
552 void MaskedImageView::ApplyMaskedImageShader( ImageRotation rotation )
556 // Vertex shader has different postfix for each rotation
557 std::stringstream vertexSource;
558 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE;
559 if( Dali::Toolkit::MaskedImageView::ROTATE_90 == rotation )
561 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90;
563 else if( Dali::Toolkit::MaskedImageView::ROTATE_180 == rotation )
565 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180;
567 else if( Dali::Toolkit::MaskedImageView::ROTATE_270 == rotation )
569 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270;
571 else // Default to Dali::Toolkit::MaskedImageView::ROTATE_0
573 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0;
576 ShaderEffect shader = ShaderEffect::New( vertexSource.str(),
577 MASKED_IMAGE_VIEW_FRAGMENT_SOURCE,
578 GeometryType( GEOMETRY_TYPE_IMAGE ),
579 ShaderEffect::GeometryHints( ShaderEffect::HINT_BLENDING ) );
581 shader.SetUniform( "uTargetSize", mTargetSize );
582 shader.SetUniform( "uSourceSize", mTargetSize );
583 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uSourceSize" ),
584 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] ),
585 EqualToConstraintVector2 ) );
586 shader.SetUniform( "uSourceOffset", Vector2::ZERO );
587 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uSourceOffset" ),
588 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] ),
589 EqualToConstraintVector2 ) );
590 shader.SetUniform( "uMaskSize", mTargetSize );
591 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uMaskSize" ),
592 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] ),
593 EqualToConstraintVector2 ) );
594 shader.SetUniform( "uMaskOffset", mTargetSize );
595 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uMaskOffset" ),
596 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] ),
597 EqualToConstraintVector2 ) );
599 shader.SetEffectImage( mMaskImage );
600 mSourceImageActor.SetShaderEffect( shader );
603 void MaskedImageView::ClampSourceSizeAndOffset()
605 float rotatedAspectRatio( mWidthOverHeight );
606 if( mWidthOverHeight > 0.0f &&
607 ( Dali::Toolkit::MaskedImageView::ROTATE_90 == mSourceRotation ||
608 Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation ) )
610 rotatedAspectRatio = 1.0f / mWidthOverHeight;
615 mSourcePosition.mCurrentPinchSize = ClampSourceSize( mSourcePosition.mCurrentPinchSize, mTargetSize, rotatedAspectRatio, mMaximumSourceScale );
616 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_SIZE ), mSourcePosition.mCurrentPinchSize );
618 mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
619 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
622 MaskedImageView::~MaskedImageView()
624 // Guard to allow handle destruction after Core has been destroyed
625 if( Stage::IsInstalled() )
627 Stage::GetCurrent().GetRenderTaskList().RemoveTask( mRenderTask );
631 void MaskedImageView::OnRenderTaskFinished( Dali::RenderTask& renderTask )
633 Toolkit::MaskedImageView handle( GetOwner() );
634 mMaskFinishedSignal.Emit( handle );
637 } // namespace Internal
639 } // namespace Toolkit