Image scaling operations - FilterMode::Box implemented for all pixel formats
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / slp / image-loaders / image-loader.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 #include "image-loader.h"
18
19 #include <dali/public-api/common/ref-counted-dali-vector.h>
20 #include <dali/public-api/images/image-attributes.h>
21 #include <dali/integration-api/bitmap.h>
22 #include <dali/integration-api/debug.h>
23
24 #include "loader-bmp.h"
25 #include "loader-gif.h"
26 #include "loader-jpeg.h"
27 #include "loader-png.h"
28 #include "loader-ico.h"
29 #include "loader-ktx.h"
30 #include "loader-wbmp.h"
31 #include "image-operations.h"
32
33 using namespace Dali::Integration;
34
35 namespace Dali
36 {
37 namespace SlpPlatform
38 {
39
40 namespace
41 {
42 typedef bool (*LoadBitmapFunction)( FILE*, Bitmap&, ImageAttributes&, const ResourceLoadingClient& );
43 typedef bool (*LoadBitmapHeaderFunction)(FILE*, const ImageAttributes& attrs, unsigned int& width, unsigned int& height );
44
45 #if defined(DEBUG_ENABLED)
46 Integration::Log::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_IMAGE_LOADING");
47 #endif
48
49 /**
50  * Stores the magic bytes, and the loader and header functions used for each image loader.
51  */
52 struct BitmapLoader
53 {
54   unsigned char magicByte1;        ///< The first byte in the file should be this
55   unsigned char magicByte2;        ///< The second byte in the file should be this
56   LoadBitmapFunction loader;       ///< The function which decodes the file
57   LoadBitmapHeaderFunction header; ///< The function which decodes the header of the file
58   Bitmap::Profile profile;         ///< The kind of bitmap to be created
59                                    ///  (addressable packed pixels or an opaque compressed blob).
60 };
61
62 /**
63  * Enum for file formats, has to be in sync with BITMAP_LOADER_LOOKUP_TABLE
64  */
65 enum FileFormats
66 {
67   // Unknown file format
68   FORMAT_UNKNOWN = -1,
69
70   // formats that use magic bytes
71   FORMAT_PNG = 0,
72   FORMAT_JPEG,
73   FORMAT_BMP,
74   FORMAT_GIF,
75   FORMAT_KTX,
76   FORMAT_ICO,
77   FORMAT_MAGIC_BYTE_COUNT,
78
79   // formats after this one do not use magic bytes
80   FORMAT_WBMP = FORMAT_MAGIC_BYTE_COUNT,
81   FORMAT_TOTAL_COUNT
82 };
83
84 /**
85  * A lookup table containing all the bitmap loaders with the appropriate information.
86  * Has to be in sync with enum FileFormats
87  */
88 const BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
89 {
90   { Png::MAGIC_BYTE_1,  Png::MAGIC_BYTE_2,  LoadBitmapFromPng,  LoadPngHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
91   { Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
92   { Bmp::MAGIC_BYTE_1,  Bmp::MAGIC_BYTE_2,  LoadBitmapFromBmp,  LoadBmpHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
93   { Gif::MAGIC_BYTE_1,  Gif::MAGIC_BYTE_2,  LoadBitmapFromGif,  LoadGifHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
94   { Ktx::MAGIC_BYTE_1,  Ktx::MAGIC_BYTE_2,  LoadBitmapFromKtx,  LoadKtxHeader,  Bitmap::BITMAP_COMPRESSED       },
95   { Ico::MAGIC_BYTE_1,  Ico::MAGIC_BYTE_2,  LoadBitmapFromIco,  LoadIcoHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
96   { 0x0,                0x0,                LoadBitmapFromWbmp, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
97 };
98
99 const unsigned int MAGIC_LENGTH = 2;
100
101 /**
102  * This code tries to predict the file format from the filename to help with format picking.
103  */
104 struct FormatExtension
105 {
106   const std::string extension;
107   FileFormats format;
108 };
109
110 const FormatExtension FORMAT_EXTENSIONS[] =
111 {
112  { ".png",  FORMAT_PNG  },
113  { ".jpg",  FORMAT_JPEG },
114  { ".bmp",  FORMAT_BMP  },
115  { ".gif",  FORMAT_GIF  },
116  { ".ktx",  FORMAT_KTX  },
117  { ".ico",  FORMAT_ICO  },
118  { ".wbmp", FORMAT_WBMP }
119 };
120
121 const unsigned int FORMAT_EXTENSIONS_COUNT = sizeof(FORMAT_EXTENSIONS) / sizeof(FormatExtension);
122
123 FileFormats GetFormatHint( const std::string& filename )
124 {
125   FileFormats format = FORMAT_UNKNOWN;
126
127   for ( unsigned int i = 0; i < FORMAT_EXTENSIONS_COUNT; ++i )
128   {
129     unsigned int length = FORMAT_EXTENSIONS[i].extension.size();
130     if ( ( filename.size() > length ) &&
131          ( 0 == filename.compare( filename.size() - length, length, FORMAT_EXTENSIONS[i].extension ) ) )
132     {
133       format = FORMAT_EXTENSIONS[i].format;
134       break;
135     }
136   }
137
138   return format;
139 }
140
141 /**
142  * Checks the magic bytes of the file first to determine which Image decoder to use to decode the
143  * bitmap.
144  * @param[in]   fp      The file to decode
145  * @param[in]   format  Hint about what format to try first
146  * @param[out]  loader  Set with the function to use to decode the image
147  * @param[out]  header  Set with the function to use to decode the header
148  * @param[out]  profile The kind of bitmap to hold the bits loaded for the bitmap.
149  * @return true, if we can decode the image, false otherwise
150  */
151 bool GetBitmapLoaderFunctions( FILE *fp,
152                                FileFormats format,
153                                LoadBitmapFunction& loader,
154                                LoadBitmapHeaderFunction& header,
155                                Bitmap::Profile& profile )
156 {
157   unsigned char magic[MAGIC_LENGTH];
158   size_t read = fread(magic, sizeof(unsigned char), MAGIC_LENGTH, fp);
159
160   // Reset to the start of the file.
161   if( fseek(fp, 0, SEEK_SET) )
162   {
163     DALI_LOG_ERROR("Error seeking to start of file\n");
164   }
165
166   if (read != MAGIC_LENGTH)
167   {
168     return false;
169   }
170
171   bool loaderFound = false;
172   const BitmapLoader *lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
173   ImageAttributes attrs;
174
175   // try hinted format first
176   if ( format != FORMAT_UNKNOWN )
177   {
178     lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + format;
179     if ( format >= FORMAT_MAGIC_BYTE_COUNT ||
180          ( lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1] ) )
181     {
182       unsigned int width = 0;
183       unsigned int height = 0;
184       loaderFound = lookupPtr->header(fp, attrs, width, height );
185     }
186   }
187
188   // then try to get a match with formats that have magic bytes
189   if ( false == loaderFound )
190   {
191     for ( lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
192           lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
193           ++lookupPtr )
194     {
195       if ( lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1] )
196       {
197         // to seperate ico file format and wbmp file format
198         unsigned int width = 0;
199         unsigned int height = 0;
200         loaderFound = lookupPtr->header(fp, attrs, width, height);
201       }
202       if (loaderFound)
203       {
204         break;
205       }
206     }
207   }
208
209   // finally try formats that do not use magic bytes
210   if ( false == loaderFound )
211   {
212     for ( lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
213           lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_TOTAL_COUNT;
214           ++lookupPtr )
215     {
216       // to seperate ico file format and wbmp file format
217       unsigned int width = 0;
218       unsigned int height = 0;
219       loaderFound = lookupPtr->header(fp, attrs, width, height);
220       if (loaderFound)
221       {
222         break;
223       }
224     }
225   }
226
227   // if a loader was found set the outputs
228   if ( loaderFound )
229   {
230     loader  = lookupPtr->loader;
231     header  = lookupPtr->header;
232     profile = lookupPtr->profile;
233   }
234
235   // Reset to the start of the file.
236   if( fseek(fp, 0, SEEK_SET) )
237   {
238     DALI_LOG_ERROR("Error seeking to start of file\n");
239   }
240
241   return loaderFound;
242 }
243
244 } // anonymous namespace
245
246
247 namespace ImageLoader
248 {
249
250 bool ConvertStreamToBitmap(const ResourceType& resourceType, std::string path, FILE * const fp, const ResourceLoadingClient& client, BitmapPtr& ptr)
251 {
252   DALI_LOG_TRACE_METHOD(gLogFilter);
253   DALI_ASSERT_DEBUG( ResourceBitmap == resourceType.id );
254
255   bool result = false;
256   BitmapPtr bitmap = 0;
257
258   if (fp != NULL)
259   {
260     LoadBitmapFunction function;
261     LoadBitmapHeaderFunction header;
262     Bitmap::Profile profile;
263
264     if ( GetBitmapLoaderFunctions( fp,
265                                    GetFormatHint( path ),
266                                    function,
267                                    header,
268                                    profile ) )
269     {
270       bitmap = Bitmap::New(profile, ResourcePolicy::DISCARD );
271
272       DALI_LOG_SET_OBJECT_STRING(bitmap, path);
273       const BitmapResourceType& resType = static_cast<const BitmapResourceType&>(resourceType);
274       const ImageAttributes& requestedAttributes = resType.imageAttributes; //< Original attributes.
275       ImageAttributes attributes  = resType.imageAttributes; //< r/w copy of the attributes.
276
277       // Check for cancellation now we have hit the filesystem, done some allocation, and burned some cycles:
278       // This won't do anything from synchronous API, it's only useful when called from another thread.
279       client.InterruptionPoint(); // Note: By design, this can throw an exception
280
281       // Run the image type decoder:
282       // Note, this can overwrite the attributes parameter.
283       result = function( fp, *bitmap, attributes, client );
284
285       if (!result)
286       {
287         DALI_LOG_WARNING( "Unable to convert %s\n", path.c_str() );
288         bitmap = 0;
289       }
290
291       // Apply the requested image attributes in best-effort fashion:
292       client.InterruptionPoint(); // Note: By design, this can throw an exception
293       bitmap = Internal::Platform::ApplyAttributesToBitmap( bitmap, requestedAttributes );
294     }
295     else
296     {
297       DALI_LOG_WARNING("Image Decoder for %s unavailable\n", path.c_str());
298     }
299   }
300
301   ptr.Reset( bitmap.Get() );
302   return result;
303 }
304
305 ResourcePointer LoadResourceSynchronously( const Integration::ResourceType& resourceType, const std::string& resourcePath )
306 {
307   ResourcePointer resource;
308   BitmapPtr bitmap = 0;
309
310   FILE * const fp = fopen( resourcePath.c_str(), "rb" );
311   if( fp != NULL )
312   {
313     bool result = ConvertStreamToBitmap( resourceType, resourcePath, fp, StubbedResourceLoadingClient(), bitmap );
314     if( result && bitmap )
315     {
316       resource.Reset(bitmap.Get());
317     }
318     fclose(fp);
319   }
320   return resource;
321 }
322
323
324 void GetClosestImageSize( const std::string& filename,
325                           const ImageAttributes& attributes,
326                           Vector2 &closestSize )
327 {
328   FILE *fp = fopen(filename.c_str(), "rb");
329   if (fp != NULL)
330   {
331     LoadBitmapFunction loaderFunction;
332     LoadBitmapHeaderFunction headerFunction;
333     Bitmap::Profile profile;
334
335     if ( GetBitmapLoaderFunctions( fp,
336                                    GetFormatHint(filename),
337                                    loaderFunction,
338                                    headerFunction,
339                                    profile ) )
340     {
341       unsigned int width;
342       unsigned int height;
343
344       const bool read_res = headerFunction(fp, attributes, width, height);
345       if(!read_res)
346       {
347         DALI_LOG_WARNING("Image Decoder failed to read header for %s\n", filename.c_str());
348       }
349
350       closestSize.width = (float)width;
351       closestSize.height = (float)height;
352     }
353     else
354     {
355       DALI_LOG_WARNING("Image Decoder for %s unavailable\n", filename.c_str());
356     }
357     fclose(fp);
358   }
359 }
360
361
362 void GetClosestImageSize( ResourcePointer resourceBuffer,
363                           const ImageAttributes& attributes,
364                           Vector2 &closestSize )
365 {
366   BitmapPtr bitmap = 0;
367
368   // Get the blob of binary data that we need to decode:
369   DALI_ASSERT_DEBUG( resourceBuffer );
370   Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>( resourceBuffer.Get() );
371
372   if( encodedBlob != 0 )
373   {
374     const size_t blobSize     = encodedBlob->GetVector().Size();
375     uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]);
376     DALI_ASSERT_DEBUG( blobSize > 0U );
377     DALI_ASSERT_DEBUG( blobBytes != 0U );
378
379     if( blobBytes != 0 && blobSize > 0U )
380     {
381       // Open a file handle on the memory buffer:
382       FILE * const fp = fmemopen(blobBytes, blobSize, "rb");
383       if ( fp != NULL )
384       {
385         LoadBitmapFunction loaderFunction;
386         LoadBitmapHeaderFunction headerFunction;
387         Bitmap::Profile profile;
388
389         if ( GetBitmapLoaderFunctions( fp,
390                                        FORMAT_UNKNOWN,
391                                        loaderFunction,
392                                        headerFunction,
393                                        profile ) )
394         {
395           unsigned int width;
396           unsigned int height;
397           const bool read_res = headerFunction(fp, attributes, width, height);
398           if(!read_res)
399           {
400             DALI_LOG_WARNING("Image Decoder failed to read header for resourceBuffer\n");
401           }
402
403           closestSize.width = (float) width;
404           closestSize.height = (float) height;
405         }
406         fclose(fp);
407       }
408     }
409   }
410 }
411
412 } // ImageLoader
413 } // SlpPlatform
414 } // Dali