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