90940f0c5c94361c53421a3c77a11a89862c5efb
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / image-view / image-view-impl.cpp
1 /*
2  * Copyright (c) 2020 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 "image-view-impl.h"
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/object/type-registry.h>
23 #include <dali/public-api/object/type-registry-helper.h>
24 #include <dali/devel-api/scripting/scripting.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
28 #include <dali-toolkit/devel-api/controls/control-devel.h>
29 #include <dali-toolkit/public-api/visuals/visual-properties.h>
30 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
31 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
32 #include <dali-toolkit/internal/visuals/visual-base-impl.h>
33 #include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
34 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
35
36 namespace Dali
37 {
38
39 namespace Toolkit
40 {
41
42 namespace Internal
43 {
44
45 namespace
46 {
47
48 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
49
50 BaseHandle Create()
51 {
52   return Toolkit::ImageView::New();
53 }
54
55 // Setup properties, signals and actions using the type-registry.
56 DALI_TYPE_REGISTRATION_BEGIN( Toolkit::ImageView, Toolkit::Control, Create );
57 DALI_PROPERTY_REGISTRATION( Toolkit, ImageView, "image", MAP, IMAGE )
58 DALI_PROPERTY_REGISTRATION( Toolkit, ImageView, "preMultipliedAlpha", BOOLEAN, PRE_MULTIPLIED_ALPHA )
59
60 DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT( Toolkit, ImageView, "pixelArea", Vector4(0.f, 0.f, 1.f, 1.f), PIXEL_AREA )
61 DALI_TYPE_REGISTRATION_END()
62
63 } // anonymous namespace
64
65 using namespace Dali;
66
67 ImageView::ImageView()
68 : Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ),
69   mImageSize(),
70   mImageVisualPaddingSetByTransform( false ),
71   mImageViewPixelAreaSetByFittingMode( false )
72 {
73   DevelControl::SetAccessibilityConstructor( Self(), []( Dali::Actor actor ) {
74     return std::unique_ptr< Dali::Accessibility::Accessible >(
75         new Control::Impl::AccessibleImpl( actor, Dali::Accessibility::Role::IMAGE ) );
76   } );
77 }
78
79 ImageView::~ImageView()
80 {
81 }
82
83 Toolkit::ImageView ImageView::New()
84 {
85   ImageView* impl = new ImageView();
86
87   Toolkit::ImageView handle = Toolkit::ImageView( *impl );
88
89   // Second-phase init of the implementation
90   // This can only be done after the CustomActor connection has been made...
91   impl->Initialize();
92
93   return handle;
94 }
95
96 /////////////////////////////////////////////////////////////
97
98 void ImageView::OnInitialize()
99 {
100   // ImageView can relayout in the OnImageReady, alternative to a signal would be to have a upcall from the Control to ImageView
101   Dali::Toolkit::Control handle( GetOwner() );
102   handle.ResourceReadySignal().Connect( this, &ImageView::OnResourceReady );
103 }
104
105 void ImageView::SetImage( const Property::Map& map )
106 {
107   // Comparing a property map is too expensive so just creating a new visual
108   mPropertyMap = map;
109   mUrl.clear();
110
111   Toolkit::Visual::Base visual =  Toolkit::VisualFactory::Get().CreateVisual( mPropertyMap );
112   if( visual )
113   {
114     // Don't set mVisual until it is ready and shown. Getters will still use current visual.
115     if( !mVisual )
116     {
117       mVisual = visual;
118     }
119
120     if( !mShaderMap.Empty() )
121     {
122       Internal::Visual::Base& visualImpl = Toolkit::GetImplementation( visual );
123       visualImpl.SetCustomShader( mShaderMap );
124     }
125
126     DevelControl::RegisterVisual( *this, Toolkit::ImageView::Property::IMAGE, visual  );
127   }
128   else
129   {
130     // Unregister the exsiting visual
131     DevelControl::UnregisterVisual( *this, Toolkit::ImageView::Property::IMAGE );
132
133     // Trigger a size negotiation request that may be needed when unregistering a visual.
134     RelayoutRequest();
135   }
136
137   // Signal that a Relayout may be needed
138 }
139
140 void ImageView::SetImage( const std::string& url, ImageDimensions size )
141 {
142   // Don't bother comparing if we had a visual previously, just drop old visual and create new one
143   mUrl = url;
144   mImageSize = size;
145   mPropertyMap.Clear();
146
147   // Don't set mVisual until it is ready and shown. Getters will still use current visual.
148   Toolkit::Visual::Base visual =  Toolkit::VisualFactory::Get().CreateVisual( url, size );
149   if( visual )
150   {
151     if( !mVisual )
152     {
153       mVisual = visual;
154     }
155
156     if( !mShaderMap.Empty() )
157     {
158       Internal::Visual::Base& visualImpl = Toolkit::GetImplementation( visual );
159       visualImpl.SetCustomShader( mShaderMap );
160     }
161
162     DevelControl::RegisterVisual( *this, Toolkit::ImageView::Property::IMAGE, visual );
163   }
164   else
165   {
166     // Unregister the exsiting visual
167     DevelControl::UnregisterVisual( *this, Toolkit::ImageView::Property::IMAGE );
168
169     // Trigger a size negotiation request that may be needed when unregistering a visual.
170     RelayoutRequest();
171   }
172
173   // Signal that a Relayout may be needed
174 }
175
176 void ImageView::EnablePreMultipliedAlpha( bool preMultipled )
177 {
178   if( mVisual )
179   {
180     Toolkit::GetImplementation( mVisual ).EnablePreMultipliedAlpha( preMultipled );
181   }
182 }
183
184 bool ImageView::IsPreMultipliedAlphaEnabled() const
185 {
186   if( mVisual )
187   {
188     return Toolkit::GetImplementation( mVisual ).IsPreMultipliedAlphaEnabled();
189   }
190   return false;
191 }
192
193 void ImageView::SetDepthIndex( int depthIndex )
194 {
195   if( mVisual )
196   {
197     mVisual.SetDepthIndex( depthIndex );
198   }
199 }
200
201 Vector3 ImageView::GetNaturalSize()
202 {
203   if( mVisual )
204   {
205     Vector2 rendererNaturalSize;
206     mVisual.GetNaturalSize( rendererNaturalSize );
207
208     Extents padding;
209     padding = Self().GetProperty<Extents>( Toolkit::Control::Property::PADDING );
210
211     rendererNaturalSize.width += ( padding.start + padding.end );
212     rendererNaturalSize.height += ( padding.top + padding.bottom );
213     return Vector3( rendererNaturalSize );
214   }
215
216   // if no visual then use Control's natural size
217   return Control::GetNaturalSize();
218 }
219
220 float ImageView::GetHeightForWidth( float width )
221 {
222   Extents padding;
223   padding = Self().GetProperty<Extents>( Toolkit::Control::Property::PADDING );
224
225   if( mVisual )
226   {
227     return mVisual.GetHeightForWidth( width ) + padding.top + padding.bottom;
228   }
229   else
230   {
231     return Control::GetHeightForWidth( width ) + padding.top + padding.bottom;
232   }
233 }
234
235 float ImageView::GetWidthForHeight( float height )
236 {
237   Extents padding;
238   padding = Self().GetProperty<Extents>( Toolkit::Control::Property::PADDING );
239
240   if( mVisual )
241   {
242     return mVisual.GetWidthForHeight( height ) + padding.start + padding.end;
243   }
244   else
245   {
246     return Control::GetWidthForHeight( height ) + padding.start + padding.end;
247   }
248 }
249
250 void ImageView::OnRelayout( const Vector2& size, RelayoutContainer& container )
251 {
252   Control::OnRelayout( size, container );
253   if( mVisual )
254   {
255     Property::Map transformMap = Property::Map();
256
257     Extents padding = Self().GetProperty<Extents>( Toolkit::Control::Property::PADDING );
258
259     bool zeroPadding = ( padding == Extents() );
260
261     Vector2 naturalSize;
262     mVisual.GetNaturalSize( naturalSize );
263
264     Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(
265           Self().GetProperty( Dali::Actor::Property::LAYOUT_DIRECTION ).Get<int>() );
266     if( Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection )
267     {
268       std::swap( padding.start, padding.end );
269     }
270
271     // remove padding from the size to know how much is left for the visual
272     Vector2 finalSize = size - Vector2( padding.start + padding.end, padding.top + padding.bottom );
273     Vector2 finalOffset = Vector2( padding.start, padding.top );
274
275     ApplyFittingMode( finalSize, naturalSize, finalOffset, zeroPadding , transformMap );
276
277     mVisual.SetTransformAndSize( transformMap, size );
278
279     // mVisual is not updated util the resource is ready in the case of visual replacement.
280     // in this case, the Property Map must be initialized so that the previous value is not reused.
281     // after mVisual is updated, the correct value will be reset.
282     Toolkit::Visual::Base visual = DevelControl::GetVisual( *this, Toolkit::ImageView::Property::IMAGE );
283     if( visual && visual != mVisual )
284     {
285       visual.SetTransformAndSize( Property::Map(), size );
286     }
287   }
288 }
289
290 void ImageView::OnResourceReady( Toolkit::Control control )
291 {
292   // Visual ready so update visual attached to this ImageView, following call to RelayoutRequest will use this visual.
293   mVisual = DevelControl::GetVisual( *this, Toolkit::ImageView::Property::IMAGE );
294   // Signal that a Relayout may be needed
295 }
296
297 void ImageView::SetTransformMapForFittingMode( Vector2 finalSize, Vector2 naturalSize, Vector2 finalOffset, Visual::FittingMode fittingMode, Property::Map& transformMap )
298 {
299   switch(fittingMode)
300   {
301     case Visual::FittingMode::FIT_KEEP_ASPECT_RATIO:
302     {
303       auto availableVisualSize = finalSize;
304
305       // scale to fit the padded area
306       finalSize = naturalSize * std::min( ( naturalSize.width  ? ( availableVisualSize.width  / naturalSize.width  ) : 0 ),
307                                             ( naturalSize.height ? ( availableVisualSize.height / naturalSize.height ) : 0 ) );
308
309       // calculate final offset within the padded area
310       finalOffset += ( availableVisualSize - finalSize ) * .5f;
311
312       // populate the transform map
313       transformMap.Add( Toolkit::Visual::Transform::Property::OFFSET, finalOffset )
314                   .Add( Toolkit::Visual::Transform::Property::SIZE, finalSize );
315       break;
316     }
317     case Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO:
318     {
319       mImageViewPixelAreaSetByFittingMode = true;
320       auto availableVisualSize = finalSize;
321       finalSize = naturalSize * std::max( ( naturalSize.width  ? ( availableVisualSize.width  / naturalSize.width  ) : 0 ),
322                                           ( naturalSize.height ? ( availableVisualSize.height / naturalSize.height ) : 0 ) );
323
324       auto originalOffset = finalOffset;
325       finalOffset += ( availableVisualSize - finalSize ) * .5f;
326
327       float x = abs( (availableVisualSize.width - finalSize.width ) / finalSize.width )* .5f;
328       float y = abs( (availableVisualSize.height - finalSize.height ) / finalSize.height ) * .5f;
329       float widthRatio = 1.f - abs( (availableVisualSize.width - finalSize.width ) / finalSize.width );
330       float heightRatio = 1.f - abs( (availableVisualSize.height - finalSize.height ) / finalSize.height );
331       Vector4 pixelArea = Vector4( x, y, widthRatio, heightRatio);
332       Self().SetProperty( Toolkit::ImageView::Property::PIXEL_AREA, pixelArea );
333
334       // populate the transform map
335       transformMap.Add( Toolkit::Visual::Transform::Property::OFFSET, originalOffset )
336                   .Add( Toolkit::Visual::Transform::Property::SIZE, availableVisualSize );
337       break;
338     }
339     case Visual::FittingMode::CENTER:
340     {
341       auto availableVisualSize = finalSize;
342       if( availableVisualSize.width > naturalSize.width && availableVisualSize.height > naturalSize.height )
343       {
344         finalSize = naturalSize;
345       }
346       else
347       {
348         finalSize = naturalSize * std::min( ( naturalSize.width  ? ( availableVisualSize.width  / naturalSize.width  ) : 0 ),
349                                           ( naturalSize.height ? ( availableVisualSize.height / naturalSize.height ) : 0 ) );
350       }
351
352       finalOffset += ( availableVisualSize - finalSize ) * .5f;
353
354       // populate the transform map
355       transformMap.Add( Toolkit::Visual::Transform::Property::OFFSET, finalOffset )
356                   .Add( Toolkit::Visual::Transform::Property::SIZE, finalSize );
357       break;
358     }
359     case Visual::FittingMode::FILL:
360     {
361       transformMap.Add( Toolkit::Visual::Transform::Property::OFFSET, finalOffset )
362                   .Add( Toolkit::Visual::Transform::Property::SIZE, finalSize );
363       break;
364     }
365     case Visual::FittingMode::FIT_WIDTH:
366     case Visual::FittingMode::FIT_HEIGHT:
367     {
368       // This FittingMode already converted
369       break;
370     }
371   }
372 }
373
374 void ImageView::ApplyFittingMode( Vector2 finalSize, Vector2 naturalSize, Vector2 finalOffset, bool zeroPadding , Property::Map& transformMap )
375 {
376     Visual::FittingMode fittingMode = Toolkit::GetImplementation(mVisual).GetFittingMode();
377
378     // Reset PIXEL_AREA after using OVER_FIT_KEEP_ASPECT_RATIO
379     if( mImageViewPixelAreaSetByFittingMode )
380     {
381       Self().SetProperty( Toolkit::ImageView::Property::PIXEL_AREA, FULL_TEXTURE_RECT );
382       mImageViewPixelAreaSetByFittingMode = false;
383     }
384
385     if( ( !zeroPadding ) || // If padding is not zero
386         ( fittingMode != Visual::FittingMode::FILL ) )
387     {
388       mImageVisualPaddingSetByTransform = true;
389
390       // If FittingMode use FIT_WIDTH or FIT_HEIGTH, it need to change proper fittingMode
391       if( fittingMode == Visual::FittingMode::FIT_WIDTH )
392       {
393         fittingMode = ( finalSize.height  / naturalSize.height ) < ( finalSize.width / naturalSize.width ) ? Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::FIT_KEEP_ASPECT_RATIO;
394       }
395       else if( fittingMode == Visual::FittingMode::FIT_HEIGHT )
396       {
397         fittingMode = ( finalSize.height  / naturalSize.height ) < ( finalSize.width / naturalSize.width ) ? Visual::FittingMode::FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO;
398       }
399
400       SetTransformMapForFittingMode( finalSize, naturalSize, finalOffset, fittingMode, transformMap );
401
402       // Set extra value for applying transformMap
403       transformMap.Add( Toolkit::Visual::Transform::Property::OFFSET_POLICY,
404                         Vector2( Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE ) )
405                   .Add( Toolkit::Visual::Transform::Property::ORIGIN, Toolkit::Align::TOP_BEGIN )
406                   .Add( Toolkit::Visual::Transform::Property::ANCHOR_POINT, Toolkit::Align::TOP_BEGIN )
407                   .Add( Toolkit::Visual::Transform::Property::SIZE_POLICY,
408                         Vector2( Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE ) );
409     }
410     else if ( mImageVisualPaddingSetByTransform && zeroPadding )  // Reset offset to zero only if padding applied previously
411     {
412       mImageVisualPaddingSetByTransform = false;
413       // Reset the transform map
414       transformMap.Add( Toolkit::Visual::Transform::Property::OFFSET, Vector2::ZERO )
415                   .Add( Toolkit::Visual::Transform::Property::OFFSET_POLICY,
416                         Vector2( Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE ) )
417                   .Add( Toolkit::Visual::Transform::Property::SIZE, Vector2::ONE )
418                   .Add( Toolkit::Visual::Transform::Property::SIZE_POLICY,
419                         Vector2( Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE ) );
420     }
421 }
422
423 ///////////////////////////////////////////////////////////
424 //
425 // Properties
426 //
427
428 void ImageView::SetProperty( BaseObject* object, Property::Index index, const Property::Value& value )
429 {
430   Toolkit::ImageView imageView = Toolkit::ImageView::DownCast( Dali::BaseHandle( object ) );
431
432   if ( imageView )
433   {
434     ImageView& impl = GetImpl( imageView );
435     switch ( index )
436     {
437       case Toolkit::ImageView::Property::IMAGE:
438       {
439         std::string imageUrl;
440         const Property::Map* map;
441         if( value.Get( imageUrl ) )
442         {
443           impl.SetImage( imageUrl, ImageDimensions() );
444         }
445         // if its not a string then get a Property::Map from the property if possible.
446         else
447         {
448           map = value.GetMap();
449           if( map )
450           {
451             Property::Value* shaderValue = map->Find( Toolkit::Visual::Property::SHADER, CUSTOM_SHADER );
452             // set image only if property map contains image information other than custom shader
453             if( map->Count() > 1u ||  !shaderValue )
454             {
455               impl.SetImage( *map );
456             }
457             // the property map contains only the custom shader
458             else if( ( map->Count() == 1u )&&( shaderValue ) )
459             {
460               Property::Map* shaderMap = shaderValue->GetMap();
461               if( shaderMap )
462               {
463                 impl.mShaderMap = *shaderMap;
464
465                 if( !impl.mUrl.empty() )
466                 {
467                   impl.SetImage( impl.mUrl, impl.mImageSize );
468                 }
469                 else if( !impl.mPropertyMap.Empty() )
470                 {
471                   impl.SetImage( impl.mPropertyMap );
472                 }
473               }
474             }
475           }
476         }
477         break;
478       }
479
480       case Toolkit::ImageView::Property::PRE_MULTIPLIED_ALPHA:
481       {
482         bool isPre;
483         if( value.Get( isPre ) )
484         {
485           impl.EnablePreMultipliedAlpha( isPre );
486         }
487         break;
488       }
489     }
490   }
491 }
492
493 Property::Value ImageView::GetProperty( BaseObject* object, Property::Index propertyIndex )
494 {
495   Property::Value value;
496
497   Toolkit::ImageView imageview = Toolkit::ImageView::DownCast( Dali::BaseHandle( object ) );
498
499   if ( imageview )
500   {
501     ImageView& impl = GetImpl( imageview );
502     switch ( propertyIndex )
503     {
504       case Toolkit::ImageView::Property::IMAGE:
505       {
506         if ( !impl.mUrl.empty() )
507         {
508           value = impl.mUrl;
509         }
510         else
511         {
512           Property::Map map;
513           Toolkit::Visual::Base visual = DevelControl::GetVisual( impl, Toolkit::ImageView::Property::IMAGE );
514           if( visual )
515           {
516             visual.CreatePropertyMap( map );
517           }
518           value = map;
519         }
520         break;
521       }
522
523       case Toolkit::ImageView::Property::PRE_MULTIPLIED_ALPHA:
524       {
525         value = impl.IsPreMultipliedAlphaEnabled();
526         break;
527       }
528     }
529   }
530
531   return value;
532 }
533
534 } // namespace Internal
535 } // namespace Toolkit
536 } // namespace Dali