2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/controls/image-view/masked-image-view-impl.h>
23 #include <dali/public-api/animation/constraints.h>
24 #include <dali/public-api/common/stage.h>
25 #include <dali/public-api/render-tasks/render-task-list.h>
26 #include <dali/public-api/shader-effects/shader-effect.h>
37 namespace // unnamed namespace
40 const char* CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::CUSTOM_PROPERTY_COUNT ] =
49 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE =
50 "precision mediump float; \n"
51 "uniform vec2 uTargetSize; \n"
52 "uniform vec2 uSourceSize; \n"
53 "uniform vec2 uSourceOffset; \n"
54 "uniform vec2 uMaskSize; \n"
55 "uniform vec2 uMaskOffset; \n"
56 "varying vec2 vMaskTexCoord; \n"
59 " float x = uSourceSize.x*aPosition.x + uSourceOffset.x; \n"
60 " float y = uSourceSize.y*aPosition.y + uSourceOffset.y; \n"
62 " gl_Position = vec4( x/(uTargetSize.x*0.5), y/(uTargetSize.y*0.5), 0.0, 1.0 ); \n"
64 " vMaskTexCoord.x = (uMaskSize.x*0.5 + x - uMaskOffset.x) / uMaskSize.x; \n"
65 " vMaskTexCoord.y = (uMaskSize.y*0.5 + y - uMaskOffset.y) / uMaskSize.y; \n";
67 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0 =
69 " vTexCoord = aTexCoord; \n"
72 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90 =
74 " vTexCoord.x = aTexCoord.y; \n"
75 " vTexCoord.y = 1.0 - aTexCoord.x; \n"
78 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180 =
80 " vTexCoord.x = 1.0 - aTexCoord.x; \n"
81 " vTexCoord.y = 1.0 - aTexCoord.y; \n"
84 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270 =
86 " vTexCoord.x = 1.0 - aTexCoord.y; \n"
87 " vTexCoord.y = aTexCoord.x; \n"
90 const char* const MASKED_IMAGE_VIEW_FRAGMENT_SOURCE =
91 "precision mediump float; \n"
92 "varying vec2 vMaskTexCoord; \n"
95 " highp vec4 mask = texture2D(sEffect, vMaskTexCoord); \n"
96 " gl_FragColor = texture2D(sTexture, vTexCoord) * vec4(1,1,1,mask.a); \n"
99 Vector2 EqualToConstraintVector2( const Vector2& current, const PropertyInput& property )
101 return property.GetVector2();
104 Vector2 GetSizeForAspectRatio( const Vector2& targetSize, float aspectRatio )
106 Vector2 sizeToKeepAspectRatio( targetSize );
108 float targetAspectRatio( targetSize.width / targetSize.height );
110 if( aspectRatio > targetAspectRatio )
112 sizeToKeepAspectRatio.width = sizeToKeepAspectRatio.height * aspectRatio;
114 else if ( aspectRatio < targetAspectRatio )
116 sizeToKeepAspectRatio.height = sizeToKeepAspectRatio.width / aspectRatio;
119 return sizeToKeepAspectRatio;
122 Vector2 ClampSourceSize( const Vector2& sourceSize, const Vector2& targetSize, float widthOverHeight, float maxSourceScale )
124 Vector2 clampedSize( sourceSize );
126 Vector2 minSize( targetSize );
127 if ( widthOverHeight > 0.0f )
129 minSize = GetSizeForAspectRatio( targetSize, widthOverHeight );
132 if ( clampedSize.width < minSize.width ||
133 clampedSize.height < minSize.height )
135 clampedSize = minSize;
137 else if ( clampedSize.width > minSize.width *maxSourceScale ||
138 clampedSize.height > minSize.height*maxSourceScale )
140 clampedSize = minSize * maxSourceScale;
146 Vector2 ClampSourceOffset( const Vector2& sourceOffset, const Vector2& targetSize, const Vector2& sourceSize )
150 if ( sourceSize.width > targetSize.width )
152 float offset = (sourceSize.width - targetSize.width) * 0.5f;
157 if ( sourceSize.height > targetSize.height )
159 float offset = (sourceSize.height - targetSize.height) * 0.5f;
164 return Vector2( Clamp(sourceOffset.x, min.x, max.x), Clamp(sourceOffset.y, min.y, max.y) );
167 } // unnamed namespace
169 Dali::Toolkit::MaskedImageView MaskedImageView::New( unsigned int targetWidth,
170 unsigned int targetHeight,
174 // Create the implementation
175 MaskedImageView* maskedImageView = new MaskedImageView();
177 // Pass ownership to CustomActor via derived handle
178 Dali::Toolkit::MaskedImageView handle(*maskedImageView);
180 // Second-phase init of the implementation
181 // This can only be done after the CustomActor connection has been made...
182 maskedImageView->Initialize( targetWidth, targetHeight, sourceImage, maskImage );
187 void MaskedImageView::SetSourceImage( Image sourceImage )
189 mSourceImageActor.SetImage( sourceImage );
192 Image MaskedImageView::GetSourceImage()
194 return mSourceImageActor.GetImage();
197 void MaskedImageView::SetMaskImage( Image maskImage )
199 mMaskImage = maskImage;
200 mSourceImageActor.GetShaderEffect().SetEffectImage( maskImage );
203 Image MaskedImageView::GetMaskImage()
208 Property::Index MaskedImageView::GetPropertyIndex( Dali::Toolkit::MaskedImageView::CustomProperty customProperty ) const
210 Property::Index index = Property::INVALID_INDEX;
212 switch ( customProperty )
214 case Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR:
216 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ];
220 case Dali::Toolkit::MaskedImageView::SOURCE_SIZE:
222 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ];
226 case Dali::Toolkit::MaskedImageView::SOURCE_OFFSET:
228 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ];
232 case Dali::Toolkit::MaskedImageView::MASK_SIZE:
234 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ];
238 case Dali::Toolkit::MaskedImageView::MASK_OFFSET:
240 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ];
251 void MaskedImageView::Pause()
255 mRenderTask.SetRefreshRate( RenderTask::REFRESH_ONCE );
259 void MaskedImageView::Resume()
263 mRenderTask.SetRefreshRate( RenderTask::REFRESH_ALWAYS );
267 bool MaskedImageView::IsPaused() const
269 if( mRenderTask.GetRefreshRate() ) // REFRESH_ALWAYS
279 void MaskedImageView::SetEditMode( Dali::Toolkit::MaskedImageView::EditMode editMode )
283 mEditMode = editMode;
285 if ( Dali::Toolkit::MaskedImageView::EDIT_DISABLED == editMode )
287 if ( mPanGestureDetector )
289 mPanGestureDetector.DetachAll();
290 mPanGestureDetector.Reset();
293 if ( mPinchDetector )
295 mPinchDetector.DetachAll();
296 mPinchDetector.Reset();
301 if ( !mPanGestureDetector )
303 mPanGestureDetector = PanGestureDetector::New();
304 mPanGestureDetector.Attach( self );
305 mPanGestureDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPan);
308 if ( !mPinchDetector )
310 mPinchDetector = PinchGestureDetector::New();
311 mPinchDetector.Attach( self );
312 mPinchDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPinch);
315 if( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == editMode )
317 // Re-clamp values to preserve image aspect-ratio etc.
318 ClampSourceSizeAndOffset();
323 Dali::Toolkit::MaskedImageView::EditMode MaskedImageView::GetEditMode() const
328 void MaskedImageView::OnPropertySet( Property::Index index, Property::Value propertyValue )
330 // Ignore OnPropertySet if MaskedImageView is setting the properties
331 if( !mSelfPropertySetting )
333 // Synchronize with user-supplied property values...
334 if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] == index )
336 // Note that clamping will take effect when edit-mode is used later
337 mSourcePosition.mStartPinchSize = propertyValue.Get<Vector2>();
338 mSourcePosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
340 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] == index )
342 // Note that clamping will take effect when edit-mode is used later
343 mSourcePosition.mPanOffset = propertyValue.Get<Vector2>();
345 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] == index )
347 mMaskPosition.mStartPinchSize = propertyValue.Get<Vector2>();
348 mMaskPosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
350 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] == index )
352 mMaskPosition.mPanOffset = propertyValue.Get<Vector2>();
354 // else it's fine to do nothing here
358 void MaskedImageView::OnPan(Actor source, const PanGesture& gesture)
360 // Used to flag whether edit mode is setting properties
361 mSelfPropertySetting = true;
365 if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
367 mSourcePosition.mPanOffset += gesture.displacement;
368 mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
370 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
374 mMaskPosition.mPanOffset += gesture.displacement;
376 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_OFFSET ), mMaskPosition.mPanOffset );
379 // Used to flag whether edit mode is setting properties
380 mSelfPropertySetting = false;
383 void MaskedImageView::OnPinch(Actor actor, const PinchGesture& pinch)
385 // Used to flag whether edit mode is setting properties
386 mSelfPropertySetting = true;
390 if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
392 if ( pinch.state == Gesture::Started )
394 mSourcePosition.mStartPinchSize = mSourcePosition.mCurrentPinchSize;
397 mSourcePosition.mCurrentPinchSize = mSourcePosition.mStartPinchSize * pinch.scale;
399 ClampSourceSizeAndOffset();
403 if ( pinch.state == Gesture::Started )
405 mMaskPosition.mStartPinchSize = mMaskPosition.mCurrentPinchSize;
408 mMaskPosition.mCurrentPinchSize = mMaskPosition.mStartPinchSize * pinch.scale;
410 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_SIZE ), mMaskPosition.mCurrentPinchSize );
413 // Used to flag whether edit mode is setting properties
414 mSelfPropertySetting = false;
417 void MaskedImageView::SetSourceAspectRatio( float widthOverHeight )
421 if ( widthOverHeight > 0.0f )
423 mWidthOverHeight = widthOverHeight;
425 ClampSourceSizeAndOffset();
429 mWidthOverHeight = 0.0f; // ignore aspect-ratio
433 float MaskedImageView::GetSourceAspectRatio() const
435 return mWidthOverHeight;
438 void MaskedImageView::SetMaximumSourceScale( float scale )
440 mMaximumSourceScale = scale;
443 float MaskedImageView::GetMaximumSourceScale() const
445 return mMaximumSourceScale;
448 void MaskedImageView::SetSourceRotation( MaskedImageView::ImageRotation newRotation )
450 if( mSourceRotation != newRotation )
452 bool oldLandscape( Dali::Toolkit::MaskedImageView::ROTATE_90 == mSourceRotation || Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation );
453 bool newLandscape( Dali::Toolkit::MaskedImageView::ROTATE_90 == newRotation || Dali::Toolkit::MaskedImageView::ROTATE_270 == newRotation );
455 if ( oldLandscape != newLandscape )
457 // Changing between landscape & portraint, swap width & height
458 float temp = mSourcePosition.mCurrentPinchSize.width;
459 mSourcePosition.mCurrentPinchSize.width = mSourcePosition.mCurrentPinchSize.height;
460 mSourcePosition.mCurrentPinchSize.height = temp;
463 mSourceRotation = newRotation;
465 ApplyMaskedImageShader( newRotation );
467 ClampSourceSizeAndOffset();
471 MaskedImageView::ImageRotation MaskedImageView::GetSourceRotation() const
473 return mSourceRotation;
476 Dali::Toolkit::MaskedImageView::MaskedImageViewSignal& MaskedImageView::MaskFinishedSignal()
478 return mMaskFinishedSignal;
481 MaskedImageView::MaskedImageView()
482 : Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
483 mEditMode( Dali::Toolkit::MaskedImageView::EDIT_DISABLED ),
484 mSelfPropertySetting( false ),
485 mSourceRotation( Dali::Toolkit::MaskedImageView::ROTATE_0 ),
486 mWidthOverHeight( 0.0f ),
487 mMaximumSourceScale( Dali::Toolkit::MaskedImageView::DEFAULT_MAXIMUM_SOURCE_SCALE )
491 void MaskedImageView::Initialize( unsigned int targetWidth,
492 unsigned int targetHeight,
498 // Register custom properties
500 mTargetSize = Vector2( static_cast<float>(targetWidth), static_cast<float>(targetHeight) );
502 mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ]
503 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ], Color::BLACK );
505 mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ]
506 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ], mTargetSize );
508 mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ]
509 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ], Vector2::ZERO );
511 mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ]
512 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_SIZE ], mTargetSize );
514 mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ]
515 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ], Vector2::ZERO );
517 // Create destination image (FBO)
518 mDestinationImage = FrameBufferImage::New( targetWidth, targetHeight, Pixel::RGBA8888 );
520 // Create source actor for off-screen image processing
521 mSourceImageActor = ImageActor::New( sourceImage );
522 self.Add( mSourceImageActor );
523 mSourceImageActor.SetParentOrigin( ParentOrigin::CENTER );
524 mSourceImageActor.SetPositionInheritanceMode( DONT_INHERIT_POSITION );
525 mSourceImageActor.SetInheritRotation( false );
526 mSourceImageActor.SetInheritScale( false );
527 mSourceImageActor.SetColorMode( USE_OWN_COLOR );
528 mSourceImageActor.SetSize( Vector3::ONE );
530 // Apply masking effect to source actor
531 mMaskImage = maskImage;
532 ApplyMaskedImageShader( Dali::Toolkit::MaskedImageView::ROTATE_0 );
534 // Create actor to display result of off-screen rendering
535 mDestinationImageActor = ImageActor::New( mDestinationImage );
536 self.Add( mDestinationImageActor );
537 mDestinationImageActor.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
539 // Start the masking operation
540 mRenderTask = Stage::GetCurrent().GetRenderTaskList().CreateTask();
541 mRenderTask.SetSourceActor( mSourceImageActor );
542 mRenderTask.SetTargetFrameBuffer( mDestinationImage );
543 mRenderTask.SetInputEnabled( false );
544 mRenderTask.SetExclusive( true );
545 mRenderTask.SetClearEnabled( true );
546 mRenderTask.ApplyConstraint( Constraint::New<Vector4>( RenderTask::CLEAR_COLOR,
547 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ] ),
548 EqualToConstraint() ) );
549 mRenderTask.FinishedSignal().Connect( this, &MaskedImageView::OnRenderTaskFinished );
551 // Edit mode initialization
552 mSourcePosition.mCurrentPinchSize = Vector2( targetWidth, targetHeight );
553 mMaskPosition.mCurrentPinchSize = mSourcePosition.mCurrentPinchSize;
556 void MaskedImageView::ApplyMaskedImageShader( ImageRotation rotation )
560 // Vertex shader has different postfix for each rotation
561 std::stringstream vertexSource;
562 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE;
563 if( Dali::Toolkit::MaskedImageView::ROTATE_90 == rotation )
565 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90;
567 else if( Dali::Toolkit::MaskedImageView::ROTATE_180 == rotation )
569 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180;
571 else if( Dali::Toolkit::MaskedImageView::ROTATE_270 == rotation )
573 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270;
575 else // Default to Dali::Toolkit::MaskedImageView::ROTATE_0
577 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0;
580 ShaderEffect shader = ShaderEffect::New( vertexSource.str(),
581 MASKED_IMAGE_VIEW_FRAGMENT_SOURCE,
582 GeometryType( GEOMETRY_TYPE_IMAGE ),
583 ShaderEffect::GeometryHints( ShaderEffect::HINT_BLENDING ) );
585 shader.SetUniform( "uTargetSize", mTargetSize );
586 shader.SetUniform( "uSourceSize", mTargetSize );
587 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uSourceSize" ),
588 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] ),
589 EqualToConstraintVector2 ) );
590 shader.SetUniform( "uSourceOffset", Vector2::ZERO );
591 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uSourceOffset" ),
592 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] ),
593 EqualToConstraintVector2 ) );
594 shader.SetUniform( "uMaskSize", mTargetSize );
595 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uMaskSize" ),
596 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] ),
597 EqualToConstraintVector2 ) );
598 shader.SetUniform( "uMaskOffset", mTargetSize );
599 shader.ApplyConstraint( Constraint::New<Vector2>( shader.GetPropertyIndex( "uMaskOffset" ),
600 Source( self, mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] ),
601 EqualToConstraintVector2 ) );
603 shader.SetEffectImage( mMaskImage );
604 mSourceImageActor.SetShaderEffect( shader );
607 void MaskedImageView::ClampSourceSizeAndOffset()
609 float rotatedAspectRatio( mWidthOverHeight );
610 if( mWidthOverHeight > 0.0f &&
611 ( Dali::Toolkit::MaskedImageView::ROTATE_90 == mSourceRotation ||
612 Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation ) )
614 rotatedAspectRatio = 1.0f / mWidthOverHeight;
619 mSourcePosition.mCurrentPinchSize = ClampSourceSize( mSourcePosition.mCurrentPinchSize, mTargetSize, rotatedAspectRatio, mMaximumSourceScale );
620 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_SIZE ), mSourcePosition.mCurrentPinchSize );
622 mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
623 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
626 MaskedImageView::~MaskedImageView()
628 // Guard to allow handle destruction after Core has been destroyed
629 if( Stage::IsInstalled() )
631 Stage::GetCurrent().GetRenderTaskList().RemoveTask( mRenderTask );
635 void MaskedImageView::OnControlSizeSet( const Vector3& targetSize )
637 mDestinationImageActor.SetSize(targetSize);
640 void MaskedImageView::OnRenderTaskFinished( Dali::RenderTask& renderTask )
642 Toolkit::MaskedImageView handle( GetOwner() );
643 mMaskFinishedSignal.Emit( handle );
646 } // namespace Internal
648 } // namespace Toolkit