Do not try to load bitmap for unsupported file format.
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / image-loader.cpp
1 /*
2  * Copyright (c) 2023 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/devel-api/adaptor-framework/image-loader-input.h>
23 #include <dali/internal/imaging/common/image-loader-plugin-proxy.h>
24 #include <dali/internal/imaging/common/image-operations.h>
25 #include <dali/internal/imaging/common/loader-astc.h>
26 #include <dali/internal/imaging/common/loader-bmp.h>
27 #include <dali/internal/imaging/common/loader-gif.h>
28 #include <dali/internal/imaging/common/loader-ico.h>
29 #include <dali/internal/imaging/common/loader-jpeg.h>
30 #include <dali/internal/imaging/common/loader-ktx.h>
31 #include <dali/internal/imaging/common/loader-png.h>
32 #include <dali/internal/imaging/common/loader-wbmp.h>
33 #include <dali/internal/imaging/common/loader-webp.h>
34 #include <dali/internal/system/common/file-reader.h>
35
36 using namespace Dali::Integration;
37
38 namespace Dali
39 {
40 namespace TizenPlatform
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 static unsigned int gMaxTextureSize = 4096;
49
50 static bool gMaxTextureSizeUpdated = false;
51
52 /**
53  * Enum for file formats, has to be in sync with BITMAP_LOADER_LOOKUP_TABLE
54  */
55 enum FileFormats
56 {
57   // Unsupported file format that we should not process image loader.
58   FORMAT_UNSUPPORTED = -2,
59
60   // Unknown file format
61   FORMAT_UNKNOWN = -1,
62
63   // formats that use magic bytes
64   FORMAT_PNG = 0,
65   FORMAT_JPEG,
66   FORMAT_BMP,
67   FORMAT_GIF,
68   FORMAT_WEBP,
69   FORMAT_KTX,
70   FORMAT_ASTC,
71   FORMAT_ICO,
72   FORMAT_MAGIC_BYTE_COUNT,
73
74   // formats after this one do not use magic bytes
75   FORMAT_WBMP = FORMAT_MAGIC_BYTE_COUNT,
76   FORMAT_TOTAL_COUNT
77 };
78
79 /**
80  * A lookup table containing all the bitmap loaders with the appropriate information.
81  * Has to be in sync with enum FileFormats
82  */
83 // clang-format off
84 const Dali::ImageLoader::BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
85   {
86     {Png::MAGIC_BYTE_1,  Png::MAGIC_BYTE_2,  LoadBitmapFromPng,  nullptr, LoadPngHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
87     {Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadPlanesFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
88     {Bmp::MAGIC_BYTE_1,  Bmp::MAGIC_BYTE_2,  LoadBitmapFromBmp,  nullptr, LoadBmpHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
89     {Gif::MAGIC_BYTE_1,  Gif::MAGIC_BYTE_2,  LoadBitmapFromGif,  nullptr, LoadGifHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
90     {Webp::MAGIC_BYTE_1, Webp::MAGIC_BYTE_2, LoadBitmapFromWebp, nullptr, LoadWebpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
91     {Ktx::MAGIC_BYTE_1,  Ktx::MAGIC_BYTE_2,  LoadBitmapFromKtx,  nullptr, LoadKtxHeader,  Bitmap::BITMAP_COMPRESSED      },
92     {Astc::MAGIC_BYTE_1, Astc::MAGIC_BYTE_2, LoadBitmapFromAstc, nullptr, LoadAstcHeader, Bitmap::BITMAP_COMPRESSED      },
93     {Ico::MAGIC_BYTE_1,  Ico::MAGIC_BYTE_2,  LoadBitmapFromIco,  nullptr, LoadIcoHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
94     {0x0,                0x0,                LoadBitmapFromWbmp, nullptr, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
95   };
96 // clang-format on
97
98 const unsigned int MAGIC_LENGTH = 2;
99
100 /**
101  * This code tries to predict the file format from the filename to help with format picking.
102  */
103 struct FormatExtension
104 {
105   const std::string_view extension;
106   FileFormats            format;
107 };
108
109 // clang-format off
110 constexpr FormatExtension FORMAT_EXTENSIONS[] =
111   {
112     {".png",  FORMAT_PNG },
113     {".jpg",  FORMAT_JPEG},
114     {".bmp",  FORMAT_BMP },
115     {".gif",  FORMAT_GIF },
116     {".webp", FORMAT_WEBP },
117     {".ktx",  FORMAT_KTX },
118     {".astc", FORMAT_ASTC},
119     {".ico",  FORMAT_ICO },
120     {".wbmp", FORMAT_WBMP},
121     {".svg",  FORMAT_UNSUPPORTED}, // SVG
122     {".tvg",  FORMAT_UNSUPPORTED}, // ThorVG
123     {".json", FORMAT_UNSUPPORTED}, // Lottie
124   };
125 // clang-format on
126
127 const unsigned int FORMAT_EXTENSIONS_COUNT = sizeof(FORMAT_EXTENSIONS) / sizeof(FormatExtension);
128
129 FileFormats GetFormatHint(const std::string& filename)
130 {
131   FileFormats format = FORMAT_UNKNOWN;
132
133   for(unsigned int i = 0; i < FORMAT_EXTENSIONS_COUNT; ++i)
134   {
135     unsigned int length = FORMAT_EXTENSIONS[i].extension.size();
136     if((filename.size() > length) &&
137        (0 == filename.compare(filename.size() - length, length, FORMAT_EXTENSIONS[i].extension)))
138     {
139       format = FORMAT_EXTENSIONS[i].format;
140       break;
141     }
142   }
143
144   return format;
145 }
146
147 /**
148  * Checks the magic bytes of the file first to determine which Image decoder to use to decode the
149  * bitmap.
150  * @param[in]   fp      The file to decode
151  * @param[in]   format  Hint about what format to try first
152  * @param[out]  loader  Set with the function to use to decode the image
153  * @param[out]  header  Set with the function to use to decode the header
154  * @param[out]  profile The kind of bitmap to hold the bits loaded for the bitmap.
155  * @return true, if we can decode the image, false otherwise
156  */
157 bool GetBitmapLoaderFunctions(FILE*                                        fp,
158                               FileFormats                                  format,
159                               Dali::ImageLoader::LoadBitmapFunction&       loader,
160                               Dali::ImageLoader::LoadPlanesFunction&       planeLoader,
161                               Dali::ImageLoader::LoadBitmapHeaderFunction& header,
162                               Bitmap::Profile&                             profile,
163                               const std::string&                           filename)
164 {
165   // Fast out if image loader doesn't support the format.
166   if(DALI_UNLIKELY(format == FORMAT_UNSUPPORTED))
167   {
168     return false;
169   }
170
171   unsigned char magic[MAGIC_LENGTH];
172   size_t        read = fread(magic, sizeof(unsigned char), MAGIC_LENGTH, fp);
173
174   // Reset to the start of the file.
175   if(fseek(fp, 0, SEEK_SET))
176   {
177     DALI_LOG_ERROR("Error seeking to start of file\n");
178   }
179
180   if(read != MAGIC_LENGTH)
181   {
182     return false;
183   }
184
185   bool                                   loaderFound = false;
186   const Dali::ImageLoader::BitmapLoader* lookupPtr   = BITMAP_LOADER_LOOKUP_TABLE;
187   Dali::ImageLoader::Input               defaultInput(fp);
188
189   // try plugin image loader
190   const Dali::ImageLoader::BitmapLoader* data = Internal::Adaptor::ImageLoaderPluginProxy::BitmapLoaderLookup(filename);
191   if(data != NULL)
192   {
193     lookupPtr           = data;
194     unsigned int width  = 0;
195     unsigned int height = 0;
196     loaderFound         = lookupPtr->header(fp, width, height);
197   }
198
199   // try hinted format
200   if(false == loaderFound && format != FORMAT_UNKNOWN)
201   {
202     lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + format;
203     if(format >= FORMAT_MAGIC_BYTE_COUNT ||
204        (lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1]))
205     {
206       unsigned int width  = 0;
207       unsigned int height = 0;
208       loaderFound         = lookupPtr->header(fp, width, height);
209     }
210   }
211
212   // then try to get a match with formats that have magic bytes
213   if(false == loaderFound)
214   {
215     for(lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
216         lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
217         ++lookupPtr)
218     {
219       if(lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1])
220       {
221         // to seperate ico file format and wbmp file format
222         unsigned int width  = 0;
223         unsigned int height = 0;
224         loaderFound         = lookupPtr->header(fp, width, height);
225       }
226       if(loaderFound)
227       {
228         break;
229       }
230     }
231   }
232
233   // finally try formats that do not use magic bytes
234   if(false == loaderFound)
235   {
236     for(lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
237         lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_TOTAL_COUNT;
238         ++lookupPtr)
239     {
240       // to seperate ico file format and wbmp file format
241       unsigned int width  = 0;
242       unsigned int height = 0;
243       loaderFound         = lookupPtr->header(fp, width, height);
244       if(loaderFound)
245       {
246         break;
247       }
248     }
249   }
250
251   // if a loader was found set the outputs
252   if(loaderFound)
253   {
254     loader      = lookupPtr->loader;
255     planeLoader = lookupPtr->planeLoader;
256     header      = lookupPtr->header;
257     profile     = lookupPtr->profile;
258   }
259
260   // Reset to the start of the file.
261   if(fseek(fp, 0, SEEK_SET))
262   {
263     DALI_LOG_ERROR("Error seeking to start of file\n");
264   }
265
266   return loaderFound;
267 }
268
269 } // anonymous namespace
270
271 namespace ImageLoader
272 {
273 bool ConvertStreamToBitmap(const BitmapResourceType& resource, const std::string& path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer)
274 {
275   DALI_LOG_TRACE_METHOD(gLogFilter);
276
277   bool result = false;
278
279   if(fp != NULL)
280   {
281     Dali::ImageLoader::LoadBitmapFunction       function;
282     Dali::ImageLoader::LoadPlanesFunction       planeLoader;
283     Dali::ImageLoader::LoadBitmapHeaderFunction header;
284
285     Bitmap::Profile profile;
286
287     if(GetBitmapLoaderFunctions(fp,
288                                 GetFormatHint(path),
289                                 function,
290                                 planeLoader,
291                                 header,
292                                 profile,
293                                 path))
294     {
295       const Dali::ImageLoader::ScalingParameters scalingParameters(resource.size, resource.scalingMode, resource.samplingMode);
296       const Dali::ImageLoader::Input             input(fp, scalingParameters, resource.orientationCorrection);
297
298       // Run the image type decoder:
299       result = function(input, pixelBuffer);
300
301       if(!result)
302       {
303         DALI_LOG_ERROR("Unable to convert %s\n", path.c_str());
304         pixelBuffer.Reset();
305       }
306
307       pixelBuffer = Internal::Platform::ApplyAttributesToBitmap(pixelBuffer, resource.size, resource.scalingMode, resource.samplingMode);
308     }
309     else
310     {
311       DALI_LOG_ERROR("Image Decoder for %s unavailable\n", path.c_str());
312     }
313   }
314
315   return result;
316 }
317
318 bool ConvertStreamToPlanes(const Integration::BitmapResourceType& resource, const std::string& path, FILE* const fp, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
319 {
320   DALI_LOG_TRACE_METHOD(gLogFilter);
321
322   bool result = false;
323
324   if(fp != NULL)
325   {
326     Dali::ImageLoader::LoadBitmapFunction       loader;
327     Dali::ImageLoader::LoadPlanesFunction       planeLoader;
328     Dali::ImageLoader::LoadBitmapHeaderFunction header;
329
330     Bitmap::Profile profile;
331
332     if(GetBitmapLoaderFunctions(fp,
333                                 GetFormatHint(path),
334                                 loader,
335                                 planeLoader,
336                                 header,
337                                 profile,
338                                 path))
339     {
340       const Dali::ImageLoader::ScalingParameters scalingParameters(resource.size, resource.scalingMode, resource.samplingMode);
341       const Dali::ImageLoader::Input             input(fp, scalingParameters, resource.orientationCorrection);
342
343       pixelBuffers.clear();
344
345       // Run the image type decoder:
346       if(planeLoader)
347       {
348         result = planeLoader(input, pixelBuffers);
349         if(!result)
350         {
351           DALI_LOG_ERROR("Unable to convert %s\n", path.c_str());
352         }
353       }
354       else
355       {
356         Dali::Devel::PixelBuffer pixelBuffer;
357         result = loader(input, pixelBuffer);
358         if(!result)
359         {
360           DALI_LOG_ERROR("Unable to convert %s\n", path.c_str());
361           return false;
362         }
363
364         pixelBuffer = Internal::Platform::ApplyAttributesToBitmap(pixelBuffer, resource.size, resource.scalingMode, resource.samplingMode);
365         if(pixelBuffer)
366         {
367           pixelBuffers.push_back(pixelBuffer);
368         }
369         else
370         {
371           DALI_LOG_ERROR("ApplyAttributesToBitmap is failed [%s]\n", path.c_str());
372           return false;
373         }
374       }
375     }
376     else
377     {
378       DALI_LOG_ERROR("Image Decoder for %s unavailable\n", path.c_str());
379     }
380   }
381
382   return result;
383 }
384
385 ResourcePointer LoadImageSynchronously(const Integration::BitmapResourceType& resource, const std::string& path)
386 {
387   ResourcePointer          result;
388   Dali::Devel::PixelBuffer bitmap;
389
390   Internal::Platform::FileReader fileReader(path);
391   FILE* const                    fp = fileReader.GetFile();
392   if(fp != NULL)
393   {
394     bool success = ConvertStreamToBitmap(resource, path, fp, bitmap);
395     if(success && bitmap)
396     {
397       Bitmap::Profile profile{Bitmap::Profile::BITMAP_2D_PACKED_PIXELS};
398
399       // For backward compatibility the Bitmap must be created
400       auto retval = Bitmap::New(profile, Dali::ResourcePolicy::OWNED_DISCARD);
401
402       DALI_LOG_SET_OBJECT_STRING(retval, path);
403
404       retval->GetPackedPixelsProfile()->ReserveBuffer(
405         bitmap.GetPixelFormat(),
406         bitmap.GetWidth(),
407         bitmap.GetHeight(),
408         bitmap.GetWidth(),
409         bitmap.GetHeight());
410
411       auto& impl = Dali::GetImplementation(bitmap);
412
413       std::copy(impl.GetBuffer(), impl.GetBuffer() + impl.GetBufferSize(), retval->GetBuffer());
414       result.Reset(retval);
415     }
416   }
417   return result;
418 }
419
420 ///@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?]
421 ImageDimensions GetClosestImageSize(const std::string& filename,
422                                     ImageDimensions    size,
423                                     FittingMode::Type  fittingMode,
424                                     SamplingMode::Type samplingMode,
425                                     bool               orientationCorrection)
426 {
427   unsigned int width  = 0;
428   unsigned int height = 0;
429
430   const FileFormats formatHint = GetFormatHint(filename);
431
432   // Fast out if image loader doesn't support the format.
433   if(formatHint == FileFormats::FORMAT_UNSUPPORTED)
434   {
435     return ImageDimensions(width, height);
436   }
437
438   Internal::Platform::FileReader fileReader(filename);
439   FILE*                          fp = fileReader.GetFile();
440   if(fp != NULL)
441   {
442     Dali::ImageLoader::LoadBitmapFunction       loaderFunction;
443     Dali::ImageLoader::LoadPlanesFunction       planeLoader;
444     Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
445     Bitmap::Profile                             profile;
446
447     if(GetBitmapLoaderFunctions(fp,
448                                 formatHint,
449                                 loaderFunction,
450                                 planeLoader,
451                                 headerFunction,
452                                 profile,
453                                 filename))
454     {
455       const Dali::ImageLoader::Input input(fp, Dali::ImageLoader::ScalingParameters(size, fittingMode, samplingMode), orientationCorrection);
456
457       const bool read_res = headerFunction(input, width, height);
458       if(!read_res)
459       {
460         DALI_LOG_ERROR("Image Decoder failed to read header for %s\n", filename.c_str());
461       }
462     }
463     else
464     {
465       DALI_LOG_ERROR("Image Decoder for %s unavailable\n", filename.c_str());
466     }
467   }
468   return ImageDimensions(width, height);
469 }
470
471 ImageDimensions GetClosestImageSize(Integration::ResourcePointer resourceBuffer,
472                                     ImageDimensions              size,
473                                     FittingMode::Type            fittingMode,
474                                     SamplingMode::Type           samplingMode,
475                                     bool                         orientationCorrection)
476 {
477   unsigned int width  = 0;
478   unsigned int height = 0;
479
480   // Get the blob of binary data that we need to decode:
481   DALI_ASSERT_DEBUG(resourceBuffer);
482   Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>(resourceBuffer.Get());
483
484   if(encodedBlob != 0)
485   {
486     if(encodedBlob->GetVector().Size())
487     {
488       // Open a file handle on the memory buffer:
489       Internal::Platform::FileReader fileReader(encodedBlob->GetVector());
490       FILE*                          fp = fileReader.GetFile();
491       if(fp != NULL)
492       {
493         Dali::ImageLoader::LoadBitmapFunction       loaderFunction;
494         Dali::ImageLoader::LoadPlanesFunction       planeLoader;
495         Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
496         Bitmap::Profile                             profile;
497
498         if(GetBitmapLoaderFunctions(fp,
499                                     FORMAT_UNKNOWN,
500                                     loaderFunction,
501                                     planeLoader,
502                                     headerFunction,
503                                     profile,
504                                     ""))
505         {
506           const Dali::ImageLoader::Input input(fp, Dali::ImageLoader::ScalingParameters(size, fittingMode, samplingMode), orientationCorrection);
507           const bool                     read_res = headerFunction(input, width, height);
508           if(!read_res)
509           {
510             DALI_LOG_ERROR("Image Decoder failed to read header for resourceBuffer\n");
511           }
512         }
513       }
514     }
515   }
516   return ImageDimensions(width, height);
517 }
518
519 void SetMaxTextureSize(unsigned int size)
520 {
521   gMaxTextureSize        = size;
522   gMaxTextureSizeUpdated = true;
523 }
524
525 unsigned int GetMaxTextureSize()
526 {
527   return gMaxTextureSize;
528 }
529
530 bool MaxTextureSizeUpdated()
531 {
532   return gMaxTextureSizeUpdated;
533 }
534
535 } // namespace ImageLoader
536 } // namespace TizenPlatform
537 } // namespace Dali