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