Fixed segfault if nine-patch image failed to load
[platform/core/uifw/dali-core.git] / dali / internal / event / actors / image-actor-impl.cpp
1 /*
2  * Copyright (c) 2014 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 <dali/internal/event/actors/image-actor-impl.h>
20
21 // INTERNAL INCLUDES
22 #include <dali/internal/event/images/nine-patch-image-impl.h>
23 #include <dali/public-api/object/type-registry.h>
24 #include <dali/internal/event/common/property-index-ranges.h>
25 #include <dali/internal/event/images/image-connector.h>
26 #include <dali/public-api/scripting/scripting.h>
27
28 namespace Dali
29 {
30
31 const Property::Index ImageActor::PIXEL_AREA           = Internal::DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT;
32 const Property::Index ImageActor::FADE_IN              = Internal::DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT + 1;
33 const Property::Index ImageActor::FADE_IN_DURATION     = Internal::DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT + 2;
34 const Property::Index ImageActor::STYLE                = Internal::DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT + 3;
35 const Property::Index ImageActor::BORDER               = Internal::DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT + 4;
36 const Property::Index ImageActor::IMAGE                = Internal::DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT + 5;
37
38 namespace Internal
39 {
40 bool ImageActor::mFirstInstance = true;
41 Actor::DefaultPropertyLookup* ImageActor::mDefaultImageActorPropertyLookup = NULL;
42
43 namespace
44 {
45
46 BaseHandle Create()
47 {
48   return Dali::ImageActor::New();
49 }
50
51 TypeRegistration mType( typeid(Dali::ImageActor), typeid(Dali::RenderableActor), Create );
52
53 const std::string DEFAULT_IMAGE_ACTOR_PROPERTY_NAMES[] =
54 {
55   "pixel-area",
56   "fade-in",
57   "fade-in-duration",
58   "style",
59   "border",
60   "image"
61 };
62 const int DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT = sizeof( DEFAULT_IMAGE_ACTOR_PROPERTY_NAMES ) / sizeof( std::string );
63
64 const Property::Type DEFAULT_IMAGE_ACTOR_PROPERTY_TYPES[DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT] =
65 {
66   Property::RECTANGLE,  // "pixel-area",
67   Property::BOOLEAN,    // "fade-in",
68   Property::FLOAT,      // "fade-in-duration",
69   Property::STRING,     // "style",
70   Property::VECTOR4,    // "border",
71   Property::MAP,        // "image",
72 };
73
74 ImageActor::Style StyleEnum(const std::string &s)
75 {
76   if(s == "STYLE_NINE_PATCH")
77   {
78     return Dali::ImageActor::STYLE_NINE_PATCH;
79   }
80   else // if(s == "QUAD")
81   {
82     return Dali::ImageActor::STYLE_QUAD;
83   }
84 }
85
86 std::string StyleString(const ImageActor::Style style)
87 {
88   if(style == Dali::ImageActor::STYLE_NINE_PATCH)
89   {
90     return "STYLE_NINE_PATCH";
91   }
92   else // if(s == "QUAD")
93   {
94     return "STYLE_QUAD";
95   }
96 }
97 }
98
99 ImageActorPtr ImageActor::New( Image* anImage )
100 {
101   ImageActorPtr actor( new ImageActor() );
102   ImagePtr theImage( anImage );
103
104   // Second-phase construction
105   actor->Initialize();
106   NinePatchImage* ninePatchImage = NULL;
107
108   // Automatically convert upcasted nine-patch images to cropped bitmap
109   ninePatchImage = NinePatchImage::GetNinePatchImage( anImage );
110
111   if( ninePatchImage != NULL )
112   {
113     theImage = ninePatchImage->CreateCroppedBitmapImage();
114     // Note, if theImage is empty, the nine-patch image was not loaded
115   }
116
117   // Create the attachment
118   actor->mImageAttachment = ImageAttachment::New( *actor->mNode, theImage.Get() );
119   actor->Attach( *actor->mImageAttachment );
120
121   // Adjust the actor's size
122   if( theImage )
123   {
124     actor->mImageNext.Set( theImage.Get(), false );
125     actor->OnImageSet( *theImage );
126     actor->SetNaturalSize( *theImage );
127   }
128
129   if( ninePatchImage != NULL )
130   {
131     actor->SetStyle( Dali::ImageActor::STYLE_NINE_PATCH );
132     Vector4 border = ninePatchImage->GetStretchBorders();
133     actor->SetNinePatchBorder( border, true );
134   }
135
136   return actor;
137 }
138
139
140 ImageActorPtr ImageActor::New( Image* image, const PixelArea& pixelArea )
141 {
142   // re-use basic New
143   ImageActorPtr actor = New( image );
144   // then set the pixel area
145   actor->mImageAttachment->SetPixelArea( pixelArea );
146   return actor;
147 }
148
149 void ImageActor::OnInitialize()
150 {
151   if(ImageActor::mFirstInstance)
152   {
153     mDefaultImageActorPropertyLookup = new DefaultPropertyLookup();
154     const int start = DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT;
155     for ( int i = 0; i < DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT; ++i )
156     {
157       (*mDefaultImageActorPropertyLookup)[DEFAULT_IMAGE_ACTOR_PROPERTY_NAMES[i]] = i + start;
158     }
159     ImageActor::mFirstInstance = false;
160   }
161 }
162
163 void ImageActor::SetImage( Image* image )
164 {
165   Image* currentImage = static_cast<Image*>(mImageAttachment->GetImage().GetObjectPtr());
166   // early exit if it's the same image
167   if ( currentImage == image || mImageNext.Get() == image )
168   {
169     return;
170   }
171
172   mLoadedConnection.DisconnectAll();
173
174   ImagePtr imagePtr( image );
175
176   // Automatically convert nine-patch images to cropped bitmap
177   NinePatchImage* ninePatchImage = NinePatchImage::GetNinePatchImage( image );
178   if( ninePatchImage )
179   {
180     imagePtr = ninePatchImage->CreateCroppedBitmapImage();
181   }
182
183   mImageNext.Set( imagePtr.Get(), OnStage() );
184
185   if( ninePatchImage )
186   {
187     SetStyle( Dali::ImageActor::STYLE_NINE_PATCH );
188     SetNinePatchBorder( ninePatchImage->GetStretchBorders(), true );
189   }
190
191   if ( !imagePtr )
192   {
193     mImageAttachment->SetImage( NULL );
194   }
195   else
196   {
197     // don't disconnect currently shown image until we made sure that the new one is loaded
198     OnImageSet( *imagePtr.Get() );
199   }
200 }
201
202 Dali::Image ImageActor::GetImage()
203 {
204   return mImageAttachment->GetImage();
205 }
206
207 void ImageActor::SetToNaturalSize()
208 {
209   mUsingNaturalSize = true;
210   Dali::Image image = mImageAttachment->GetImage();
211
212   if( image )
213   {
214     SetNaturalSize( GetImplementation(image) );
215   }
216 }
217
218 void ImageActor::SetPixelArea( const PixelArea& pixelArea )
219 {
220   mImageAttachment->SetPixelArea( pixelArea );
221
222   if( mUsingNaturalSize )
223   {
224     mInternalSetSize = true;
225     SetSize(pixelArea.width, pixelArea.height);
226     mInternalSetSize = false;
227   }
228 }
229
230 const ImageActor::PixelArea& ImageActor::GetPixelArea() const
231 {
232   return mImageAttachment->GetPixelArea();
233 }
234
235 bool ImageActor::IsPixelAreaSet() const
236 {
237   return mImageAttachment->IsPixelAreaSet();
238 }
239
240 void ImageActor::ClearPixelArea()
241 {
242   mImageAttachment->ClearPixelArea();
243
244   if( mUsingNaturalSize )
245   {
246     Dali::Image image = mImageAttachment->GetImage();
247     if( image )
248     {
249       mInternalSetSize = true;
250       SetSize(GetImplementation(image).GetNaturalSize());
251       mInternalSetSize = false;
252     }
253   }
254 }
255
256 void ImageActor::SetStyle( Style style )
257 {
258   mImageAttachment->SetStyle( style );
259 }
260
261 ImageActor::Style ImageActor::GetStyle() const
262 {
263   return mImageAttachment->GetStyle();
264 }
265
266 void ImageActor::SetNinePatchBorder( const Vector4& border, bool inPixels )
267 {
268   mImageAttachment->SetNinePatchBorder( border, inPixels );
269 }
270
271 Vector4 ImageActor::GetNinePatchBorder() const
272 {
273   return mImageAttachment->GetNinePatchBorder();
274 }
275
276 void ImageActor::SetFadeIn( bool enableFade )
277 {
278   mFadeIn = enableFade;
279 }
280
281 bool ImageActor::GetFadeIn() const
282 {
283   return mFadeIn;
284 }
285
286 void ImageActor::SetFadeInDuration( float durationSeconds )
287 {
288   mFadeInDuration = durationSeconds;
289 }
290
291 float ImageActor::GetFadeInDuration() const
292 {
293   return mFadeInDuration;
294 }
295
296 ImageAttachment& ImageActor::GetImageAttachment()
297 {
298   return *mImageAttachment;
299 }
300
301 Vector2 ImageActor::GetCurrentImageSize() const
302 {
303   Vector3 size;
304   Vector3 currentSize;
305
306   // get the texture size / pixel area if only a subset of it is displayed
307   if( IsPixelAreaSet() )
308   {
309     PixelArea area(GetPixelArea());
310     return Vector2(area.width, area.height );
311   }
312   else
313   {
314     return Vector2( GetCurrentSize() );
315   }
316 }
317
318 RenderableAttachment& ImageActor::GetRenderableAttachment() const
319 {
320   DALI_ASSERT_DEBUG( mImageAttachment && "ImageAttachment missing from ImageActor" );
321   return *mImageAttachment;
322 }
323
324 ImageActor::ImageActor()
325 : RenderableActor(),
326   mUsingNaturalSize(true),
327   mInternalSetSize(false),
328   mFadeIn( false ),
329   mFadeInitial( true ),
330   mLoadedConnection( this ),
331   mFadeInDuration( 1.0f )
332 {
333 }
334
335 ImageActor::~ImageActor()
336 {
337   // ScopedConnection disconnects automatically
338 }
339
340 void ImageActor::OnImageSet( Image& image )
341 {
342   // observe image loaded
343   if( Dali::ResourceLoading == image.GetLoadingState() && ! image.GetFilename().empty() )
344   {
345     image.LoadingFinishedSignal().Connect( mLoadedConnection, &ImageActor::ImageLoaded );
346   }
347   else
348   {
349     // image already loaded, or generated
350     ImageLoaded( Dali::Image(&image) );
351   }
352 }
353
354 void ImageActor::SetNaturalSize( Image& image )
355 {
356   if( mUsingNaturalSize )
357   {
358     Vector2 size;
359     if( IsPixelAreaSet() )
360     {
361       PixelArea area(GetPixelArea());
362       size.width = area.width;
363       size.height = area.height;
364     }
365     else
366     {
367       size = image.GetNaturalSize();
368     }
369
370     mInternalSetSize = true;
371     SetSize( size );
372     mInternalSetSize = false;
373   }
374 }
375
376 void ImageActor::OnSizeSet( const Vector3& targetSize )
377 {
378   if( !mInternalSetSize )
379   {
380     mUsingNaturalSize = false;
381   }
382 }
383
384 void ImageActor::OnSizeAnimation(Animation& animation, const Vector3& targetSize)
385 {
386   mUsingNaturalSize = false;
387 }
388
389 void ImageActor::OnStageConnectionInternal()
390 {
391   FadeIn();
392
393   mImageNext.OnStageConnect();
394 }
395
396 void ImageActor::OnStageDisconnectionInternal()
397 {
398   mImageNext.OnStageDisconnect();
399 }
400
401 void ImageActor::ImageLoaded( Dali::Image image )
402 {
403   DALI_ASSERT_DEBUG (image && "Image handle empty!");
404
405   // TODO: Handle case where image loading failed
406
407   // Need to keep mUploadedConnection connected as image may change later through Reload
408   // Note: Reloaded images may have changed size.
409
410   // Set the attachment's image once we know the image has loaded to prevent
411   // blank frames during load / reload.
412   mImageAttachment->SetImage( &GetImplementation( image ) );
413
414   // If size has never been set by application
415   if( mUsingNaturalSize )
416   {
417     // If a pixel area has been set, use this size
418     if( IsPixelAreaSet() )
419     {
420       const PixelArea& area = GetPixelArea();
421       mInternalSetSize = true;
422       SetSize(area.width, area.height);
423       mInternalSetSize = false;
424     }
425     else
426     {
427       mInternalSetSize = true;
428       SetSize( GetImplementation(image).GetNaturalSize() );
429       mInternalSetSize = false;
430     }
431   }
432
433   // fade in if required
434   FadeIn();
435 }
436
437 void ImageActor::FadeIn()
438 {
439   // only fade in if enabled and newly displayed on screen
440   if( mFadeIn && mFadeInitial && ( mFadeInDuration > 0.0f ) )
441   {
442     // need to set opacity immediately to 0 otherwise child actors might get rendered
443     SetOpacity( 0.0f );
444
445     Dali::Image image = mImageAttachment->GetImage();
446
447     // Fade-in when on-stage & the image is loaded
448     if (OnStage() &&
449         image &&
450         image.GetLoadingState() == Dali::ResourceLoadingSucceeded)
451     {
452       // fire and forget animation; will clean up after it's finished
453       Dali::Animation animation = Dali::Animation::New( mFadeInDuration );
454       animation.OpacityTo( Dali::Actor( this ), 1.0f, AlphaFunctions::EaseOut );
455       animation.Play();
456       mFadeInitial = false;
457     }
458   }
459 }
460
461 unsigned int ImageActor::GetDefaultPropertyCount() const
462 {
463   return RenderableActor::GetDefaultPropertyCount() + DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT;
464 }
465
466 void ImageActor::GetDefaultPropertyIndices( Property::IndexContainer& indices ) const
467 {
468   RenderableActor::GetDefaultPropertyIndices( indices ); // RenderableActor class properties
469
470   indices.reserve( indices.size() + DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT );
471
472   int index = DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT;
473   for ( int i = 0; i < DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT; ++i, ++index )
474   {
475     indices.push_back( index );
476   }
477 }
478
479 bool ImageActor::IsDefaultPropertyWritable( Property::Index index ) const
480 {
481   if(index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT)
482   {
483     return RenderableActor::IsDefaultPropertyWritable(index);
484   }
485   else
486   {
487     return true;
488   }
489 }
490
491 bool ImageActor::IsDefaultPropertyAnimatable( Property::Index index ) const
492 {
493   if(index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT)
494   {
495     return RenderableActor::IsDefaultPropertyAnimatable(index);
496   }
497   else
498   {
499     return false;
500   }
501 }
502
503 bool ImageActor::IsDefaultPropertyAConstraintInput( Property::Index index ) const
504 {
505   if( index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT )
506   {
507     return RenderableActor::IsDefaultPropertyAConstraintInput(index);
508   }
509   return true; // Our properties can be used as input to constraints.
510 }
511
512 Property::Type ImageActor::GetDefaultPropertyType( Property::Index index ) const
513 {
514   if(index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT)
515   {
516     return RenderableActor::GetDefaultPropertyType(index);
517   }
518   else
519   {
520     index -= DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT;
521
522     if ( ( index >= 0 ) && ( index < DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT ) )
523     {
524       return DEFAULT_IMAGE_ACTOR_PROPERTY_TYPES[index];
525     }
526     else
527     {
528       // index out-of-bounds
529       return Property::NONE;
530     }
531   }
532 }
533
534 const std::string& ImageActor::GetDefaultPropertyName( Property::Index index ) const
535 {
536   if(index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT)
537   {
538     return RenderableActor::GetDefaultPropertyName(index);
539   }
540   else
541   {
542     index -= DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT;
543
544     if ( ( index >= 0 ) && ( index < DEFAULT_IMAGE_ACTOR_PROPERTY_COUNT ) )
545     {
546       return DEFAULT_IMAGE_ACTOR_PROPERTY_NAMES[index];
547     }
548     else
549     {
550       // index out-of-bounds
551       static const std::string INVALID_PROPERTY_NAME;
552       return INVALID_PROPERTY_NAME;
553     }
554   }
555 }
556
557 Property::Index ImageActor::GetDefaultPropertyIndex(const std::string& name) const
558 {
559   Property::Index index = Property::INVALID_INDEX;
560
561   DALI_ASSERT_DEBUG( NULL != mDefaultImageActorPropertyLookup );
562
563   // Look for name in current class' default properties
564   DefaultPropertyLookup::const_iterator result = mDefaultImageActorPropertyLookup->find( name );
565   if ( mDefaultImageActorPropertyLookup->end() != result )
566   {
567     index = result->second;
568   }
569   else
570   {
571     // If not found, check in base class
572     index = RenderableActor::GetDefaultPropertyIndex( name );
573   }
574
575   return index;
576 }
577
578 void ImageActor::SetDefaultProperty( Property::Index index, const Property::Value& propertyValue )
579 {
580   if(index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT)
581   {
582     RenderableActor::SetDefaultProperty(index, propertyValue);
583   }
584   else
585   {
586     switch(index)
587     {
588       case Dali::ImageActor::PIXEL_AREA:
589       {
590         SetPixelArea(propertyValue.Get<Rect<int> >());
591         break;
592       }
593       case Dali::ImageActor::FADE_IN:
594       {
595         SetFadeIn(propertyValue.Get<bool>());
596         break;
597       }
598       case Dali::ImageActor::FADE_IN_DURATION:
599       {
600         SetFadeInDuration(propertyValue.Get<float>());
601         break;
602       }
603       case Dali::ImageActor::STYLE:
604       {
605         SetStyle(StyleEnum(propertyValue.Get<std::string>()));
606         break;
607       }
608       case Dali::ImageActor::BORDER:
609       {
610         SetNinePatchBorder( propertyValue.Get<Vector4>(), true /*in pixels*/ );
611         break;
612       }
613       case Dali::ImageActor::IMAGE:
614       {
615         Dali::Image img = Scripting::NewImage( propertyValue );
616         if(img)
617         {
618           SetImage( &GetImplementation(img) );
619         }
620         else
621         {
622           DALI_LOG_WARNING("Cannot create image from property value\n");
623         }
624         break;
625       }
626       default:
627       {
628         DALI_LOG_WARNING("Unknown property (%d)\n", index);
629         break;
630       }
631     } // switch(index)
632
633   } // else
634 }
635
636 Property::Value ImageActor::GetDefaultProperty( Property::Index index ) const
637 {
638   Property::Value ret;
639   if(index < DEFAULT_RENDERABLE_ACTOR_PROPERTY_MAX_COUNT)
640   {
641     ret = RenderableActor::GetDefaultProperty(index);
642   }
643   else
644   {
645     switch(index)
646     {
647       case Dali::ImageActor::PIXEL_AREA:
648       {
649         Rect<int> r = GetPixelArea();
650         ret = r;
651         break;
652       }
653       case Dali::ImageActor::FADE_IN:
654       {
655         ret = GetFadeIn();
656         break;
657       }
658       case Dali::ImageActor::FADE_IN_DURATION:
659       {
660         ret = GetFadeInDuration();
661         break;
662       }
663       case Dali::ImageActor::STYLE:
664       {
665         ret = StyleString(GetStyle());
666         break;
667       }
668       case Dali::ImageActor::BORDER:
669       {
670         ret = GetNinePatchBorder();
671         break;
672       }
673       case Dali::ImageActor::IMAGE:
674       {
675         Property::Map map;
676         Scripting::CreatePropertyMap( mImageAttachment->GetImage(), map );
677         ret = Property::Value( map );
678         break;
679       }
680       default:
681       {
682         DALI_LOG_WARNING("Unknown property (%d)\n", index);
683         break;
684       }
685     } // switch(index)
686   }
687
688   return ret;
689 }
690
691
692 } // namespace Internal
693
694 } // namespace Dali