Merge "Implemented the Handle assignment operators properly" into tizen
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / slp / resource-loader / resource-thread-image.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 #include "resource-thread-image.h"
19 #include <dali/public-api/common/ref-counted-dali-vector.h>
20 #include <dali/integration-api/bitmap.h>
21 #include <dali/integration-api/debug.h>
22 #include <dali/integration-api/resource-cache.h>
23 #include <dali/integration-api/resource-types.h>
24
25 #include "loader-bmp.h"
26 #include "loader-gif.h"
27 #include "loader-jpeg.h"
28 #include "loader-png.h"
29 #include "loader-ico.h"
30 #include "loader-ktx.h"
31 #include "loader-wbmp.h"
32
33 #include "file-closer.h"
34
35 using namespace std;
36 using namespace Dali::Integration;
37 using boost::mutex;
38 using boost::unique_lock;
39
40 namespace Dali
41 {
42
43 namespace SlpPlatform
44 {
45
46 namespace
47 {
48
49 typedef bool (*LoadBitmapFunction)( FILE*, Bitmap&, ImageAttributes&, const ResourceLoadingClient& ); ///@ToDo: Make attributes a const reference?
50 typedef bool (*LoadBitmapHeaderFunction)(FILE*, const ImageAttributes& attrs, unsigned int& width, unsigned int& height );
51
52 /**
53  * Stores the magic bytes, and the loader and header functions used for each image loader.
54  */
55 struct BitmapLoader
56 {
57   unsigned char magicByte1;        ///< The first byte in the file should be this
58   unsigned char magicByte2;        ///< The second byte in the file should be this
59   LoadBitmapFunction loader;       ///< The function which decodes the file
60   LoadBitmapHeaderFunction header; ///< The function which decodes the header of the file
61   Bitmap::Profile profile;         ///< The kind of bitmap to be created
62                                    ///  (addressable packed pixels or an opaque compressed blob).
63 };
64
65 /**
66  * Enum for file formats, has to be in sync with BITMAP_LOADER_LOOKUP_TABLE
67  */
68 enum FileFormats
69 {
70   // Unknown file format
71   FORMAT_UNKNOWN = -1,
72
73   // formats that use magic bytes
74   FORMAT_PNG = 0,
75   FORMAT_JPEG,
76   FORMAT_BMP,
77   FORMAT_GIF,
78   FORMAT_KTX,
79   FORMAT_ICO,
80   FORMAT_MAGIC_BYTE_COUNT,
81
82   // formats after this one do not use magic bytes
83   FORMAT_WBMP = FORMAT_MAGIC_BYTE_COUNT,
84   FORMAT_TOTAL_COUNT
85 };
86
87 /**
88  * A lookup table containing all the bitmap loaders with the appropriate information.
89  * Has to be in sync with enum FileFormats
90  */
91 const BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
92 {
93   { Png::MAGIC_BYTE_1,  Png::MAGIC_BYTE_2,  LoadBitmapFromPng,  LoadPngHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
94   { Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
95   { Bmp::MAGIC_BYTE_1,  Bmp::MAGIC_BYTE_2,  LoadBitmapFromBmp,  LoadBmpHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
96   { Gif::MAGIC_BYTE_1,  Gif::MAGIC_BYTE_2,  LoadBitmapFromGif,  LoadGifHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
97   { Ktx::MAGIC_BYTE_1,  Ktx::MAGIC_BYTE_2,  LoadBitmapFromKtx,  LoadKtxHeader,  Bitmap::BITMAP_COMPRESSED       },
98   { Ico::MAGIC_BYTE_1,  Ico::MAGIC_BYTE_2,  LoadBitmapFromIco,  LoadIcoHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
99   { 0x0,                0x0,                LoadBitmapFromWbmp, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
100 };
101
102 const unsigned int MAGIC_LENGTH = 2;
103
104 /**
105  * This code tries to predict the file format from the filename to help with format picking.
106  */
107 struct FormatExtension
108 {
109   const std::string extension;
110   FileFormats format;
111 };
112
113 const FormatExtension FORMAT_EXTENSIONS[] =
114 {
115  { ".png",  FORMAT_PNG  },
116  { ".jpg",  FORMAT_JPEG },
117  { ".bmp",  FORMAT_BMP  },
118  { ".gif",  FORMAT_GIF  },
119  { ".ktx",  FORMAT_KTX  },
120  { ".ico",  FORMAT_ICO  },
121  { ".wbmp", FORMAT_WBMP }
122 };
123
124 const unsigned int FORMAT_EXTENSIONS_COUNT = sizeof(FORMAT_EXTENSIONS) / sizeof(FormatExtension);
125
126 FileFormats GetFormatHint( const std::string& filename )
127 {
128   FileFormats format = FORMAT_UNKNOWN;
129
130   for ( unsigned int i = 0; i < FORMAT_EXTENSIONS_COUNT; ++i )
131   {
132     unsigned int length = FORMAT_EXTENSIONS[i].extension.size();
133     if ( ( filename.size() > length ) &&
134          ( 0 == filename.compare( filename.size() - length, length, FORMAT_EXTENSIONS[i].extension ) ) )
135     {
136       format = FORMAT_EXTENSIONS[i].format;
137       break;
138     }
139   }
140
141   return format;
142 }
143
144 /**
145  * Checks the magic bytes of the file first to determine which Image decoder to use to decode the
146  * bitmap.
147  * @param[in]   fp      The file to decode
148  * @param[in]   format  Hint about what format to try first
149  * @param[out]  loader  Set with the function to use to decode the image
150  * @param[out]  header  Set with the function to use to decode the header
151  * @param[out]  profile The kind of bitmap to hold the bits loaded for the bitmap.
152  * @return true, if we can decode the image, false otherwise
153  */
154 bool GetBitmapLoaderFunctions( FILE *fp,
155                                FileFormats format,
156                                LoadBitmapFunction& loader,
157                                LoadBitmapHeaderFunction& header,
158                                Bitmap::Profile& profile )
159 {
160   unsigned char magic[MAGIC_LENGTH];
161   size_t read = fread(magic, sizeof(unsigned char), MAGIC_LENGTH, fp);
162
163   // Reset to the start of the file.
164   if( fseek(fp, 0, SEEK_SET) )
165   {
166     DALI_LOG_ERROR("Error seeking to start of file\n");
167   }
168
169   if (read != MAGIC_LENGTH)
170   {
171     return false;
172   }
173
174   bool loaderFound = false;
175   const BitmapLoader *lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
176   ImageAttributes attrs;
177
178   // try hinted format first
179   if ( format != FORMAT_UNKNOWN )
180   {
181     lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + format;
182     if ( format >= FORMAT_MAGIC_BYTE_COUNT ||
183          ( lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1] ) )
184     {
185       unsigned int width = 0;
186       unsigned int height = 0;
187       loaderFound = lookupPtr->header(fp, attrs, width, height );
188     }
189   }
190
191   // then try to get a match with formats that have magic bytes
192   if ( false == loaderFound )
193   {
194     for ( lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
195           lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
196           ++lookupPtr )
197     {
198       if ( lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1] )
199       {
200         // to seperate ico file format and wbmp file format
201         unsigned int width = 0;
202         unsigned int height = 0;
203         loaderFound = lookupPtr->header(fp, attrs, width, height);
204       }
205       if (loaderFound)
206       {
207         break;
208       }
209     }
210   }
211
212   // finally try formats that do not use magic bytes
213   if ( false == loaderFound )
214   {
215     for ( lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
216           lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_TOTAL_COUNT;
217           ++lookupPtr )
218     {
219       // to seperate ico file format and wbmp file format
220       unsigned int width = 0;
221       unsigned int height = 0;
222       loaderFound = lookupPtr->header(fp, attrs, width, height);
223       if (loaderFound)
224       {
225         break;
226       }
227     }
228   }
229
230   // if a loader was found set the outputs
231   if ( loaderFound )
232   {
233     loader  = lookupPtr->loader;
234     header  = lookupPtr->header;
235     profile = lookupPtr->profile;
236   }
237
238   // Reset to the start of the file.
239   if( fseek(fp, 0, SEEK_SET) )
240   {
241     DALI_LOG_ERROR("Error seeking to start of file\n");
242   }
243
244   return loaderFound;
245 }
246
247 } // namespace - empty
248
249 ResourceThreadImage::ResourceThreadImage(ResourceLoader& resourceLoader)
250 : ResourceThreadBase(resourceLoader)
251 {
252 }
253
254 ResourceThreadImage::~ResourceThreadImage()
255 {
256 }
257
258 ResourcePointer ResourceThreadImage::LoadResourceSynchronously( const Integration::ResourceType& resourceType, const std::string& resourcePath )
259 {
260   ResourcePointer resource;
261   BitmapPtr bitmap = 0;
262
263   FILE * const fp = fopen( resourcePath.c_str(), "rb" );
264   if( fp != NULL )
265   {
266     bool result = ConvertStreamToBitmap( resourceType, resourcePath, fp, StubbedResourceLoadingClient(), bitmap );
267     if( result && bitmap )
268     {
269       resource.Reset(bitmap.Get());
270     }
271     fclose(fp);
272   }
273   return resource;
274 }
275
276
277 void ResourceThreadImage::GetClosestImageSize( const std::string& filename,
278                                                const ImageAttributes& attributes,
279                                                Vector2 &closestSize )
280 {
281   FILE *fp = fopen(filename.c_str(), "rb");
282   if (fp != NULL)
283   {
284     LoadBitmapFunction loaderFunction;
285     LoadBitmapHeaderFunction headerFunction;
286     Bitmap::Profile profile;
287
288     if ( GetBitmapLoaderFunctions( fp,
289                                    GetFormatHint(filename),
290                                    loaderFunction,
291                                    headerFunction,
292                                    profile ) )
293     {
294       unsigned int width;
295       unsigned int height;
296
297       const bool read_res = headerFunction(fp, attributes, width, height);
298       if(!read_res)
299       {
300         DALI_LOG_WARNING("Image Decoder failed to read header for %s\n", filename.c_str());
301       }
302
303       closestSize.width = (float)width;
304       closestSize.height = (float)height;
305     }
306     else
307     {
308       DALI_LOG_WARNING("Image Decoder for %s unavailable\n", filename.c_str());
309     }
310     fclose(fp);
311   }
312 }
313
314
315 void ResourceThreadImage::GetClosestImageSize( ResourcePointer resourceBuffer,
316                                                const ImageAttributes& attributes,
317                                                Vector2 &closestSize )
318 {
319   BitmapPtr bitmap = 0;
320
321   // Get the blob of binary data that we need to decode:
322   DALI_ASSERT_DEBUG( resourceBuffer );
323   Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>( resourceBuffer.Get() );
324
325   if( encodedBlob != 0 )
326   {
327     const size_t blobSize     = encodedBlob->GetVector().Size();
328     uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]);
329     DALI_ASSERT_DEBUG( blobSize > 0U );
330     DALI_ASSERT_DEBUG( blobBytes != 0U );
331
332     if( blobBytes != 0 && blobSize > 0U )
333     {
334       // Open a file handle on the memory buffer:
335       FILE * const fp = fmemopen(blobBytes, blobSize, "rb");
336       if ( fp != NULL )
337       {
338         LoadBitmapFunction loaderFunction;
339         LoadBitmapHeaderFunction headerFunction;
340         Bitmap::Profile profile;
341
342         if ( GetBitmapLoaderFunctions( fp,
343                                        FORMAT_UNKNOWN,
344                                        loaderFunction,
345                                        headerFunction,
346                                        profile ) )
347         {
348           unsigned int width;
349           unsigned int height;
350           const bool read_res = headerFunction(fp, attributes, width, height);
351           if(!read_res)
352           {
353             DALI_LOG_WARNING("Image Decoder failed to read header for resourceBuffer\n");
354           }
355
356           closestSize.width = (float) width;
357           closestSize.height = (float) height;
358         }
359         fclose(fp);
360       }
361     }
362   }
363 }
364
365
366 //----------------- Called from separate thread (mThread) -----------------
367
368 void ResourceThreadImage::Load(const ResourceRequest& request)
369 {
370   DALI_LOG_TRACE_METHOD( mLogFilter );
371   DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str() );
372
373   bool fileNotFound = false;
374   BitmapPtr bitmap = 0;
375   bool result = false;
376
377   Dali::Internal::Platform::FileCloser fileCloser( request.GetPath().c_str(), "rb" );
378   FILE * const fp = fileCloser.GetFile();
379
380   if( fp != NULL )
381   {
382     result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, *this, bitmap );
383     // Last chance to interrupt a cancelled load before it is reported back to clients
384     // which have already stopped tracking it:
385     InterruptionPoint(); // Note: This can throw an exception.
386     if( result && bitmap )
387     {
388       // Construct LoadedResource and ResourcePointer for image data
389       LoadedResource resource( request.GetId(), request.GetType()->id, ResourcePointer( bitmap.Get() ) );
390       // Queue the loaded resource
391       mResourceLoader.AddLoadedResource( resource );
392     }
393     else
394     {
395       DALI_LOG_WARNING( "Unable to decode %s\n", request.GetPath().c_str() );
396     }
397   }
398   else
399   {
400     DALI_LOG_WARNING( "Failed to open file to load \"%s\"\n", request.GetPath().c_str() );
401     fileNotFound = true;
402   }
403
404   if ( !bitmap )
405   {
406     if( fileNotFound )
407     {
408       FailedResource resource(request.GetId(), FailureFileNotFound  );
409       mResourceLoader.AddFailedLoad(resource);
410     }
411     else
412     {
413       FailedResource resource(request.GetId(), FailureUnknown);
414       mResourceLoader.AddFailedLoad(resource);
415     }
416   }
417 }
418
419 void ResourceThreadImage::Decode(const ResourceRequest& request)
420 {
421   DALI_LOG_TRACE_METHOD( mLogFilter );
422   DALI_LOG_INFO(mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str());
423
424   BitmapPtr bitmap = 0;
425
426   // Get the blob of binary data that we need to decode:
427   DALI_ASSERT_DEBUG( request.GetResource() );
428
429   DALI_ASSERT_DEBUG( 0 != dynamic_cast<Dali::RefCountedVector<uint8_t>*>( request.GetResource().Get() ) && "Only blobs of binary data can be decoded." );
430   Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>( request.GetResource().Get() );
431
432   if( encodedBlob != 0 )
433   {
434     const size_t blobSize     = encodedBlob->GetVector().Size();
435     uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]);
436     DALI_ASSERT_DEBUG( blobSize > 0U );
437     DALI_ASSERT_DEBUG( blobBytes != 0U );
438
439     if( blobBytes != 0 && blobSize > 0U )
440     {
441       // Open a file handle on the memory buffer:
442       Dali::Internal::Platform::FileCloser fileCloser( blobBytes, blobSize, "rb" );
443       FILE * const fp = fileCloser.GetFile();
444       if ( fp != NULL )
445       {
446         bool result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, StubbedResourceLoadingClient(), bitmap );
447
448         if ( result && bitmap )
449         {
450           // Construct LoadedResource and ResourcePointer for image data
451           LoadedResource resource( request.GetId(), request.GetType()->id, ResourcePointer( bitmap.Get() ) );
452           // Queue the loaded resource
453           mResourceLoader.AddLoadedResource( resource );
454         }
455         else
456         {
457           DALI_LOG_WARNING( "Unable to decode bitmap supplied as in-memory blob.\n" );
458         }
459       }
460     }
461   }
462
463   if (!bitmap)
464   {
465     FailedResource resource(request.GetId(), FailureUnknown);
466     mResourceLoader.AddFailedLoad(resource);
467   }
468 }
469
470 void ResourceThreadImage::Save(const Integration::ResourceRequest& request)
471 {
472   DALI_LOG_TRACE_METHOD( mLogFilter );
473   DALI_ASSERT_DEBUG( request.GetType()->id == ResourceBitmap );
474   DALI_LOG_WARNING( "Image saving not supported on background resource threads." );
475 }
476
477 bool ResourceThreadImage::ConvertStreamToBitmap(const ResourceType& resourceType, std::string path, FILE * const fp, const ResourceLoadingClient& client, BitmapPtr& ptr)
478 {
479   DALI_LOG_TRACE_METHOD(mLogFilter);
480   DALI_ASSERT_DEBUG( ResourceBitmap == resourceType.id );
481
482   bool result = false;
483   BitmapPtr bitmap = 0;
484
485   if (fp != NULL)
486   {
487     LoadBitmapFunction function;
488     LoadBitmapHeaderFunction header;
489     Bitmap::Profile profile;
490
491     if ( GetBitmapLoaderFunctions( fp,
492                                    GetFormatHint( path ),
493                                    function,
494                                    header,
495                                    profile ) )
496     {
497       bitmap = Bitmap::New(profile, true);
498
499       DALI_LOG_SET_OBJECT_STRING(bitmap, path);
500       const BitmapResourceType& resType = static_cast<const BitmapResourceType&>(resourceType);
501       ImageAttributes attributes  = resType.imageAttributes;
502
503       // Check for cancellation now we have hit the filesystem, done some allocation, and burned some cycles:
504       client.InterruptionPoint(); // Note: This can throw an exception.
505
506       result = function( fp, *bitmap, attributes, client );
507
508       if (!result)
509       {
510         DALI_LOG_WARNING("Unable to convert %s\n", path.c_str());
511         bitmap = 0;
512       }
513
514       // Apply the requested image attributes in best-effort fashion:
515       const ImageAttributes& requestedAttributes = resType.imageAttributes;
516       // Cut the bitmap according to the desired width and height so that the
517       // resulting bitmap has the same aspect ratio as the desired dimensions:
518       if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
519       {
520         const unsigned loadedWidth = bitmap->GetImageWidth();
521         const unsigned loadedHeight = bitmap->GetImageHeight();
522         const unsigned desiredWidth = requestedAttributes.GetWidth();
523         const unsigned desiredHeight = requestedAttributes.GetHeight();
524
525         if( desiredWidth < 1U || desiredHeight < 1U )
526         {
527           DALI_LOG_WARNING( "Image scaling aborted for image %s as desired dimensions too small (%u, %u)\n.", path.c_str(), desiredWidth, desiredHeight );
528         }
529         else if( loadedWidth != desiredWidth || loadedHeight != desiredHeight )
530         {
531           const Vector2 desiredDims( desiredWidth, desiredHeight );
532
533           // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
534           // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
535           const float widthsRatio = loadedWidth / float(desiredWidth);
536           const Vector2 scaledByWidth = desiredDims * widthsRatio;
537           const float heightsRatio = loadedHeight / float(desiredHeight);
538           const Vector2 scaledByHeight = desiredDims * heightsRatio;
539           // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
540           const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
541           const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
542
543           // Work out how many pixels to trim from top and bottom, and left and right:
544           // (We only ever do one dimension)
545           const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - loadedHeight) * 0.5f ) : 0;
546           const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - loadedWidth) * 0.5f );
547
548           DALI_LOG_INFO( mLogFilter, Debug::Concise, "ImageAttributes::ScaleToFill - Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, loadedWidth, loadedHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
549
550           // Make a new bitmap with the central part of the loaded one if required:
551           if( scanlinesToTrim > 0 || columnsToTrim > 0 ) ///@ToDo: Make this test a bit fuzzy (allow say a 5% difference).
552           {
553             client.InterruptionPoint(); // Note: This can throw an exception.
554
555             const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
556             const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
557             BitmapPtr croppedBitmap = Bitmap::New( Bitmap::BITMAP_2D_PACKED_PIXELS, true );
558             Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
559             DALI_ASSERT_DEBUG( packedView );
560             const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
561             packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
562
563             const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
564
565             const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
566             PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
567             DALI_ASSERT_DEBUG( srcPixels && destPixels );
568
569             // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
570             if( trimTopAndBottom )
571             {
572               memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
573             }
574             else
575             {
576               for( unsigned y = 0; y < newHeight; ++y )
577               {
578                 memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * loadedWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
579               }
580             }
581
582             // Overwrite the loaded bitmap with the cropped version:
583             bitmap = croppedBitmap;
584           }
585         }
586       }
587     }
588     else
589     {
590       DALI_LOG_WARNING("Image Decoder for %s unavailable\n", path.c_str());
591     }
592   }
593
594   ptr.Reset( bitmap.Get() );
595   return result;
596 }
597
598 } // namespace SlpPlatform
599
600 } // namespace Dali