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/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>
38 namespace // unnamed namespace
41 const char* CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::CUSTOM_PROPERTY_COUNT ] =
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"
60 " float x = uSourceSize.x*aPosition.x + uSourceOffset.x; \n"
61 " float y = uSourceSize.y*aPosition.y + uSourceOffset.y; \n"
63 " gl_Position = vec4( x/(uTargetSize.x*0.5), y/(uTargetSize.y*0.5), 0.0, 1.0 ); \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";
68 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0 =
70 " vTexCoord = aTexCoord; \n"
73 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90 =
75 " vTexCoord.x = aTexCoord.y; \n"
76 " vTexCoord.y = 1.0 - aTexCoord.x; \n"
79 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180 =
81 " vTexCoord.x = 1.0 - aTexCoord.x; \n"
82 " vTexCoord.y = 1.0 - aTexCoord.y; \n"
85 const char* const MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270 =
87 " vTexCoord.x = 1.0 - aTexCoord.y; \n"
88 " vTexCoord.y = aTexCoord.x; \n"
91 const char* const MASKED_IMAGE_VIEW_FRAGMENT_SOURCE =
92 "precision mediump float; \n"
93 "varying vec2 vMaskTexCoord; \n"
96 " highp vec4 mask = texture2D(sEffect, vMaskTexCoord); \n"
97 " gl_FragColor = texture2D(sTexture, vTexCoord) * vec4(1,1,1,mask.a); \n"
100 Vector2 GetSizeForAspectRatio( const Vector2& targetSize, float aspectRatio )
102 Vector2 sizeToKeepAspectRatio( targetSize );
104 float targetAspectRatio( targetSize.width / targetSize.height );
106 if( aspectRatio > targetAspectRatio )
108 sizeToKeepAspectRatio.width = sizeToKeepAspectRatio.height * aspectRatio;
110 else if ( aspectRatio < targetAspectRatio )
112 sizeToKeepAspectRatio.height = sizeToKeepAspectRatio.width / aspectRatio;
115 return sizeToKeepAspectRatio;
118 Vector2 ClampSourceSize( const Vector2& sourceSize, const Vector2& targetSize, float widthOverHeight, float maxSourceScale )
120 Vector2 clampedSize( sourceSize );
122 Vector2 minSize( targetSize );
123 if ( widthOverHeight > 0.0f )
125 minSize = GetSizeForAspectRatio( targetSize, widthOverHeight );
128 if ( clampedSize.width < minSize.width ||
129 clampedSize.height < minSize.height )
131 clampedSize = minSize;
133 else if ( clampedSize.width > minSize.width *maxSourceScale ||
134 clampedSize.height > minSize.height*maxSourceScale )
136 clampedSize = minSize * maxSourceScale;
142 Vector2 ClampSourceOffset( const Vector2& sourceOffset, const Vector2& targetSize, const Vector2& sourceSize )
146 if ( sourceSize.width > targetSize.width )
148 float offset = (sourceSize.width - targetSize.width) * 0.5f;
153 if ( sourceSize.height > targetSize.height )
155 float offset = (sourceSize.height - targetSize.height) * 0.5f;
160 return Vector2( Clamp(sourceOffset.x, min.x, max.x), Clamp(sourceOffset.y, min.y, max.y) );
163 } // unnamed namespace
165 Dali::Toolkit::MaskedImageView MaskedImageView::New( unsigned int targetWidth,
166 unsigned int targetHeight,
170 // Create the implementation
171 MaskedImageView* maskedImageView = new MaskedImageView();
173 // Pass ownership to CustomActor via derived handle
174 Dali::Toolkit::MaskedImageView handle(*maskedImageView);
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 );
183 void MaskedImageView::SetSourceImage( Image sourceImage )
185 mSourceImageActor.SetImage( sourceImage );
188 Image MaskedImageView::GetSourceImage()
190 return mSourceImageActor.GetImage();
193 void MaskedImageView::SetMaskImage( Image maskImage )
195 mMaskImage = maskImage;
196 mSourceImageActor.GetShaderEffect().SetEffectImage( maskImage );
199 Image MaskedImageView::GetMaskImage()
204 Property::Index MaskedImageView::GetPropertyIndex( Dali::Toolkit::MaskedImageView::CustomProperty customProperty ) const
206 Property::Index index = Property::INVALID_INDEX;
208 switch ( customProperty )
210 case Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR:
212 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ];
216 case Dali::Toolkit::MaskedImageView::SOURCE_SIZE:
218 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ];
222 case Dali::Toolkit::MaskedImageView::SOURCE_OFFSET:
224 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ];
228 case Dali::Toolkit::MaskedImageView::MASK_SIZE:
230 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ];
234 case Dali::Toolkit::MaskedImageView::MASK_OFFSET:
236 index = mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ];
247 void MaskedImageView::Pause()
251 mRenderTask.SetRefreshRate( RenderTask::REFRESH_ONCE );
255 void MaskedImageView::Resume()
259 mRenderTask.SetRefreshRate( RenderTask::REFRESH_ALWAYS );
263 bool MaskedImageView::IsPaused() const
265 if( mRenderTask.GetRefreshRate() ) // REFRESH_ALWAYS
275 void MaskedImageView::SetEditMode( Dali::Toolkit::MaskedImageView::EditMode editMode )
279 mEditMode = editMode;
281 if ( Dali::Toolkit::MaskedImageView::EDIT_DISABLED == editMode )
283 if ( mPanGestureDetector )
285 mPanGestureDetector.DetachAll();
286 mPanGestureDetector.Reset();
289 if ( mPinchDetector )
291 mPinchDetector.DetachAll();
292 mPinchDetector.Reset();
297 if ( !mPanGestureDetector )
299 mPanGestureDetector = PanGestureDetector::New();
300 mPanGestureDetector.Attach( self );
301 mPanGestureDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPan);
304 if ( !mPinchDetector )
306 mPinchDetector = PinchGestureDetector::New();
307 mPinchDetector.Attach( self );
308 mPinchDetector.DetectedSignal().Connect(this, &MaskedImageView::OnPinch);
311 if( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == editMode )
313 // Re-clamp values to preserve image aspect-ratio etc.
314 ClampSourceSizeAndOffset();
319 Dali::Toolkit::MaskedImageView::EditMode MaskedImageView::GetEditMode() const
324 void MaskedImageView::OnPropertySet( Property::Index index, Property::Value propertyValue )
326 // Ignore OnPropertySet if MaskedImageView is setting the properties
327 if( !mSelfPropertySetting )
329 // Synchronize with user-supplied property values...
330 if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ] == index )
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>();
336 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ] == index )
338 // Note that clamping will take effect when edit-mode is used later
339 mSourcePosition.mPanOffset = propertyValue.Get<Vector2>();
341 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ] == index )
343 mMaskPosition.mStartPinchSize = propertyValue.Get<Vector2>();
344 mMaskPosition.mCurrentPinchSize = propertyValue.Get<Vector2>();
346 else if( mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ] == index )
348 mMaskPosition.mPanOffset = propertyValue.Get<Vector2>();
350 // else it's fine to do nothing here
354 void MaskedImageView::OnPan(Actor source, const PanGesture& gesture)
356 // Used to flag whether edit mode is setting properties
357 mSelfPropertySetting = true;
361 if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
363 mSourcePosition.mPanOffset += gesture.displacement;
364 mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
366 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
370 mMaskPosition.mPanOffset += gesture.displacement;
372 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_OFFSET ), mMaskPosition.mPanOffset );
375 // Used to flag whether edit mode is setting properties
376 mSelfPropertySetting = false;
379 void MaskedImageView::OnPinch(Actor actor, const PinchGesture& pinch)
381 // Used to flag whether edit mode is setting properties
382 mSelfPropertySetting = true;
386 if ( Dali::Toolkit::MaskedImageView::EDIT_SOURCE == mEditMode )
388 if ( pinch.state == Gesture::Started )
390 mSourcePosition.mStartPinchSize = mSourcePosition.mCurrentPinchSize;
393 mSourcePosition.mCurrentPinchSize = mSourcePosition.mStartPinchSize * pinch.scale;
395 ClampSourceSizeAndOffset();
399 if ( pinch.state == Gesture::Started )
401 mMaskPosition.mStartPinchSize = mMaskPosition.mCurrentPinchSize;
404 mMaskPosition.mCurrentPinchSize = mMaskPosition.mStartPinchSize * pinch.scale;
406 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::MASK_SIZE ), mMaskPosition.mCurrentPinchSize );
409 // Used to flag whether edit mode is setting properties
410 mSelfPropertySetting = false;
413 void MaskedImageView::SetSourceAspectRatio( float widthOverHeight )
417 if ( widthOverHeight > 0.0f )
419 mWidthOverHeight = widthOverHeight;
421 ClampSourceSizeAndOffset();
425 mWidthOverHeight = 0.0f; // ignore aspect-ratio
429 float MaskedImageView::GetSourceAspectRatio() const
431 return mWidthOverHeight;
434 void MaskedImageView::SetMaximumSourceScale( float scale )
436 mMaximumSourceScale = scale;
439 float MaskedImageView::GetMaximumSourceScale() const
441 return mMaximumSourceScale;
444 void MaskedImageView::SetSourceRotation( MaskedImageView::ImageRotation newRotation )
446 if( mSourceRotation != newRotation )
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 );
451 if ( oldLandscape != newLandscape )
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;
459 mSourceRotation = newRotation;
461 ApplyMaskedImageShader( newRotation );
463 ClampSourceSizeAndOffset();
467 MaskedImageView::ImageRotation MaskedImageView::GetSourceRotation() const
469 return mSourceRotation;
472 Dali::Toolkit::MaskedImageView::MaskedImageViewSignal& MaskedImageView::MaskFinishedSignal()
474 return mMaskFinishedSignal;
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 )
487 void MaskedImageView::Initialize( unsigned int targetWidth,
488 unsigned int targetHeight,
494 // Register custom properties
496 mTargetSize = Vector2( static_cast<float>(targetWidth), static_cast<float>(targetHeight) );
498 mCustomProperties[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ]
499 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::BACKGROUND_COLOR ], Color::BLACK );
501 mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ]
502 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_SIZE ], mTargetSize );
504 mCustomProperties[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ]
505 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ], Vector2::ZERO );
507 mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_SIZE ]
508 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_SIZE ], mTargetSize );
510 mCustomProperties[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ]
511 = self.RegisterProperty( CUSTOM_PROPERTY_NAMES[ Dali::Toolkit::MaskedImageView::MASK_OFFSET ], Vector2::ZERO );
513 // Create destination image (FBO)
514 mDestinationImage = FrameBufferImage::New( targetWidth, targetHeight, Pixel::RGBA8888 );
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 );
526 // Apply masking effect to source actor
527 mMaskImage = maskImage;
528 ApplyMaskedImageShader( Dali::Toolkit::MaskedImageView::ROTATE_0 );
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 );
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 );
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 );
548 // Edit mode initialization
549 mSourcePosition.mCurrentPinchSize = Vector2( targetWidth, targetHeight );
550 mMaskPosition.mCurrentPinchSize = mSourcePosition.mCurrentPinchSize;
553 void MaskedImageView::ApplyMaskedImageShader( ImageRotation rotation )
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 )
562 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE90;
564 else if( Dali::Toolkit::MaskedImageView::ROTATE_180 == rotation )
566 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE180;
568 else if( Dali::Toolkit::MaskedImageView::ROTATE_270 == rotation )
570 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE270;
572 else // Default to Dali::Toolkit::MaskedImageView::ROTATE_0
574 vertexSource << MASKED_IMAGE_VIEW_VERTEX_SOURCE_ROTATE0;
577 ShaderEffect shader = ShaderEffect::New( vertexSource.str(),
578 MASKED_IMAGE_VIEW_FRAGMENT_SOURCE,
579 GeometryType( GEOMETRY_TYPE_IMAGE ),
580 ShaderEffect::GeometryHints( ShaderEffect::HINT_BLENDING ) );
582 shader.SetUniform( "uTargetSize", mTargetSize );
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();
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();
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();
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();
604 shader.SetEffectImage( mMaskImage );
605 mSourceImageActor.SetShaderEffect( shader );
608 void MaskedImageView::ClampSourceSizeAndOffset()
610 float rotatedAspectRatio( mWidthOverHeight );
611 if( mWidthOverHeight > 0.0f &&
612 ( Dali::Toolkit::MaskedImageView::ROTATE_90 == mSourceRotation ||
613 Dali::Toolkit::MaskedImageView::ROTATE_270 == mSourceRotation ) )
615 rotatedAspectRatio = 1.0f / mWidthOverHeight;
620 mSourcePosition.mCurrentPinchSize = ClampSourceSize( mSourcePosition.mCurrentPinchSize, mTargetSize, rotatedAspectRatio, mMaximumSourceScale );
621 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_SIZE ), mSourcePosition.mCurrentPinchSize );
623 mSourcePosition.mPanOffset = ClampSourceOffset( mSourcePosition.mPanOffset, mTargetSize, mSourcePosition.mCurrentPinchSize );
624 self.SetProperty( GetPropertyIndex( Dali::Toolkit::MaskedImageView::SOURCE_OFFSET ), mSourcePosition.mPanOffset );
627 MaskedImageView::~MaskedImageView()
629 // Guard to allow handle destruction after Core has been destroyed
630 if( Stage::IsInstalled() )
632 Stage::GetCurrent().GetRenderTaskList().RemoveTask( mRenderTask );
636 void MaskedImageView::OnControlSizeSet( const Vector3& targetSize )
638 mDestinationImageActor.SetSize(targetSize);
641 void MaskedImageView::OnRenderTaskFinished( Dali::RenderTask& renderTask )
643 Toolkit::MaskedImageView handle( GetOwner() );
644 mMaskFinishedSignal.Emit( handle );
647 } // namespace Internal
649 } // namespace Toolkit