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