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