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