support image loader extension
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / image-loader.cpp
1 /*
2  * Copyright (c) 2017 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 <dali/internal/imaging/common/image-loader.h>
18
19 #include <dali/devel-api/common/ref-counted-dali-vector.h>
20 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
21
22 #include <dali/internal/imaging/common/loader-astc.h>
23 #include <dali/internal/imaging/common/loader-bmp.h>
24 #include <dali/internal/imaging/common/loader-gif.h>
25 #include <dali/internal/imaging/common/loader-ico.h>
26 #include <dali/internal/imaging/common/loader-jpeg.h>
27 #include <dali/internal/imaging/common/loader-ktx.h>
28 #include <dali/internal/imaging/common/loader-png.h>
29 #include <dali/internal/imaging/common/loader-wbmp.h>
30 #include <dali/internal/imaging/common/image-operations.h>
31 #include <dali/devel-api/adaptor-framework/image-loader-input.h>
32 #include <dali/internal/imaging/common/image-loader-plugin-proxy.h>
33 #include <dali/internal/system/common/file-reader.h>
34
35 using namespace Dali::Integration;
36
37 namespace Dali
38 {
39 namespace TizenPlatform
40 {
41
42 namespace
43 {
44 #if defined(DEBUG_ENABLED)
45 Integration::Log::Filter* gLogFilter = Debug::Filter::New( Debug::Concise, false, "LOG_IMAGE_LOADING" );
46 #endif
47
48
49 /**
50  * Enum for file formats, has to be in sync with BITMAP_LOADER_LOOKUP_TABLE
51  */
52 enum FileFormats
53 {
54   // Unknown file format
55   FORMAT_UNKNOWN = -1,
56
57   // formats that use magic bytes
58   FORMAT_PNG = 0,
59   FORMAT_JPEG,
60   FORMAT_BMP,
61   FORMAT_GIF,
62   FORMAT_KTX,
63   FORMAT_ASTC,
64   FORMAT_ICO,
65   FORMAT_MAGIC_BYTE_COUNT,
66
67   // formats after this one do not use magic bytes
68   FORMAT_WBMP = FORMAT_MAGIC_BYTE_COUNT,
69   FORMAT_TOTAL_COUNT
70 };
71
72 /**
73  * A lookup table containing all the bitmap loaders with the appropriate information.
74  * Has to be in sync with enum FileFormats
75  */
76 const Dali::ImageLoader::BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
77 {
78   { Png::MAGIC_BYTE_1,  Png::MAGIC_BYTE_2,  LoadBitmapFromPng,  LoadPngHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
79   { Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
80   { Bmp::MAGIC_BYTE_1,  Bmp::MAGIC_BYTE_2,  LoadBitmapFromBmp,  LoadBmpHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
81   { Gif::MAGIC_BYTE_1,  Gif::MAGIC_BYTE_2,  LoadBitmapFromGif,  LoadGifHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
82   { Ktx::MAGIC_BYTE_1,  Ktx::MAGIC_BYTE_2,  LoadBitmapFromKtx,  LoadKtxHeader,  Bitmap::BITMAP_COMPRESSED       },
83   { Astc::MAGIC_BYTE_1, Astc::MAGIC_BYTE_2, LoadBitmapFromAstc, LoadAstcHeader, Bitmap::BITMAP_COMPRESSED       },
84   { Ico::MAGIC_BYTE_1,  Ico::MAGIC_BYTE_2,  LoadBitmapFromIco,  LoadIcoHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS },
85   { 0x0,                0x0,                LoadBitmapFromWbmp, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
86 };
87
88 const unsigned int MAGIC_LENGTH = 2;
89
90 /**
91  * This code tries to predict the file format from the filename to help with format picking.
92  */
93 struct FormatExtension
94 {
95   const std::string extension;
96   FileFormats format;
97 };
98
99 const FormatExtension FORMAT_EXTENSIONS[] =
100 {
101  { ".png",  FORMAT_PNG  },
102  { ".jpg",  FORMAT_JPEG },
103  { ".bmp",  FORMAT_BMP  },
104  { ".gif",  FORMAT_GIF  },
105  { ".ktx",  FORMAT_KTX  },
106  { ".astc", FORMAT_ASTC },
107  { ".ico",  FORMAT_ICO  },
108  { ".wbmp", FORMAT_WBMP }
109 };
110
111 const unsigned int FORMAT_EXTENSIONS_COUNT = sizeof(FORMAT_EXTENSIONS) / sizeof(FormatExtension);
112
113 FileFormats GetFormatHint( const std::string& filename )
114 {
115   FileFormats format = FORMAT_UNKNOWN;
116
117   for ( unsigned int i = 0; i < FORMAT_EXTENSIONS_COUNT; ++i )
118   {
119     unsigned int length = FORMAT_EXTENSIONS[i].extension.size();
120     if ( ( filename.size() > length ) &&
121          ( 0 == filename.compare( filename.size() - length, length, FORMAT_EXTENSIONS[i].extension ) ) )
122     {
123       format = FORMAT_EXTENSIONS[i].format;
124       break;
125     }
126   }
127
128   return format;
129 }
130
131 /**
132  * Checks the magic bytes of the file first to determine which Image decoder to use to decode the
133  * bitmap.
134  * @param[in]   fp      The file to decode
135  * @param[in]   format  Hint about what format to try first
136  * @param[out]  loader  Set with the function to use to decode the image
137  * @param[out]  header  Set with the function to use to decode the header
138  * @param[out]  profile The kind of bitmap to hold the bits loaded for the bitmap.
139  * @return true, if we can decode the image, false otherwise
140  */
141 bool GetBitmapLoaderFunctions( FILE *fp,
142                                FileFormats format,
143                                Dali::ImageLoader::LoadBitmapFunction& loader,
144                                Dali::ImageLoader::LoadBitmapHeaderFunction& header,
145                                Bitmap::Profile& profile,
146                                const std::string& filename )
147 {
148   unsigned char magic[MAGIC_LENGTH];
149   size_t read = fread(magic, sizeof(unsigned char), MAGIC_LENGTH, fp);
150
151   // Reset to the start of the file.
152   if( fseek(fp, 0, SEEK_SET) )
153   {
154     DALI_LOG_ERROR("Error seeking to start of file\n");
155   }
156
157   if (read != MAGIC_LENGTH)
158   {
159     return false;
160   }
161
162   bool loaderFound = false;
163   const Dali::ImageLoader::BitmapLoader *lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
164   Dali::ImageLoader::Input defaultInput( fp );
165
166   // try plugin image loader
167   const Dali::ImageLoader::BitmapLoader* data = Internal::Adaptor::ImageLoaderPluginProxy::BitmapLoaderLookup( filename );
168   if( data != NULL )
169   {
170     lookupPtr = data;
171     unsigned int width = 0;
172     unsigned int height = 0;
173     loaderFound = lookupPtr->header( fp, width, height );
174   }
175
176   // try hinted format
177   if ( false == loaderFound && 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 BitmapResourceType& resource, std::string path, FILE * const fp, Dali::Devel::PixelBuffer& pixelBuffer )
252 {
253   DALI_LOG_TRACE_METHOD( gLogFilter );
254
255   bool result = false;
256
257   if (fp != NULL)
258   {
259     Dali::ImageLoader::LoadBitmapFunction function;
260     Dali::ImageLoader::LoadBitmapHeaderFunction header;
261
262     Bitmap::Profile profile;
263
264     if ( GetBitmapLoaderFunctions( fp,
265                                    GetFormatHint( path ),
266                                    function,
267                                    header,
268                                    profile,
269                                    path ) )
270     {
271       const Dali::ImageLoader::ScalingParameters scalingParameters( resource.size, resource.scalingMode, resource.samplingMode );
272       const Dali::ImageLoader::Input input( fp, scalingParameters, resource.orientationCorrection );
273
274       // Run the image type decoder:
275       result = function( input, pixelBuffer );
276
277       if (!result)
278       {
279         DALI_LOG_WARNING( "Unable to convert %s\n", path.c_str() );
280         pixelBuffer.Reset();
281       }
282
283       pixelBuffer = Internal::Platform::ApplyAttributesToBitmap( pixelBuffer, resource.size, resource.scalingMode, resource.samplingMode );
284     }
285     else
286     {
287       DALI_LOG_WARNING( "Image Decoder for %s unavailable\n", path.c_str() );
288     }
289   }
290
291   return result;
292 }
293
294 ResourcePointer LoadImageSynchronously( const Integration::BitmapResourceType& resource, const std::string& path )
295 {
296   ResourcePointer result;
297   Dali::Devel::PixelBuffer bitmap;
298
299   Internal::Platform::FileReader fileReader( path );
300   FILE * const fp = fileReader.GetFile();
301   if( fp != NULL )
302   {
303     bool success = ConvertStreamToBitmap(resource, path, fp, bitmap);
304     if (success && bitmap)
305     {
306       Bitmap::Profile profile{Bitmap::Profile::BITMAP_2D_PACKED_PIXELS};
307
308       // For backward compatibility the Bitmap must be created
309       auto retval = Bitmap::New(profile, Dali::ResourcePolicy::OWNED_DISCARD);
310
311       DALI_LOG_SET_OBJECT_STRING( retval, path );
312
313       retval->GetPackedPixelsProfile()->ReserveBuffer(
314               bitmap.GetPixelFormat(),
315               bitmap.GetWidth(),
316               bitmap.GetHeight(),
317               bitmap.GetWidth(),
318               bitmap.GetHeight()
319             );
320
321       auto& impl = Dali::GetImplementation(bitmap);
322
323       std::copy( impl.GetBuffer(), impl.GetBuffer()+impl.GetBufferSize(), retval->GetBuffer());
324       result.Reset(retval);
325     }
326   }
327   return result;
328 }
329
330 ///@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?]
331 ImageDimensions  GetClosestImageSize( const std::string& filename,
332                                       ImageDimensions size,
333                                       FittingMode::Type fittingMode,
334                                       SamplingMode::Type samplingMode,
335                                       bool orientationCorrection )
336 {
337   unsigned int width = 0;
338   unsigned int height = 0;
339
340   Internal::Platform::FileReader fileReader( filename );
341   FILE *fp = fileReader.GetFile();
342   if (fp != NULL)
343   {
344     Dali::ImageLoader::LoadBitmapFunction loaderFunction;
345     Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
346     Bitmap::Profile profile;
347
348     if ( GetBitmapLoaderFunctions( fp,
349                                    GetFormatHint(filename),
350                                    loaderFunction,
351                                    headerFunction,
352                                    profile,
353                                    filename ) )
354     {
355       const Dali::ImageLoader::Input input( fp, Dali::ImageLoader::ScalingParameters( size, fittingMode, samplingMode ), orientationCorrection );
356
357       const bool read_res = headerFunction( input, width, height );
358       if(!read_res)
359       {
360         DALI_LOG_WARNING("Image Decoder failed to read header for %s\n", filename.c_str());
361       }
362     }
363     else
364     {
365       DALI_LOG_WARNING("Image Decoder for %s unavailable\n", filename.c_str());
366     }
367   }
368   return ImageDimensions( width, height );
369 }
370
371 ImageDimensions GetClosestImageSize( Integration::ResourcePointer resourceBuffer,
372                                      ImageDimensions size,
373                                      FittingMode::Type fittingMode,
374                                      SamplingMode::Type samplingMode,
375                                      bool orientationCorrection )
376 {
377   unsigned int width = 0;
378   unsigned int height = 0;
379
380   // Get the blob of binary data that we need to decode:
381   DALI_ASSERT_DEBUG( resourceBuffer );
382   Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>( resourceBuffer.Get() );
383
384   if( encodedBlob != 0 )
385   {
386     if( encodedBlob->GetVector().Size() )
387     {
388       // Open a file handle on the memory buffer:
389       Internal::Platform::FileReader fileReader( encodedBlob->GetVector() );
390       FILE *fp = fileReader.GetFile();
391       if ( fp != NULL )
392       {
393         Dali::ImageLoader::LoadBitmapFunction loaderFunction;
394         Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
395         Bitmap::Profile profile;
396
397         if ( GetBitmapLoaderFunctions( fp,
398                                        FORMAT_UNKNOWN,
399                                        loaderFunction,
400                                        headerFunction,
401                                        profile,
402                                        "" ) )
403         {
404           const Dali::ImageLoader::Input input( fp, Dali::ImageLoader::ScalingParameters( size, fittingMode, samplingMode ), orientationCorrection );
405           const bool read_res = headerFunction( input, width, height );
406           if( !read_res )
407           {
408             DALI_LOG_WARNING( "Image Decoder failed to read header for resourceBuffer\n" );
409           }
410         }
411       }
412     }
413   }
414   return ImageDimensions( width, height );
415 }
416
417 } // ImageLoader
418 } // TizenPlatform
419 } // Dali