Change decoding error log level
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / image-loader.cpp
1 /*
2  * Copyright (c) 2021 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/system/common/file-reader.h>
34
35 using namespace Dali::Integration;
36
37 namespace Dali
38 {
39 namespace TizenPlatform
40 {
41 namespace
42 {
43 #if defined(DEBUG_ENABLED)
44 Integration::Log::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_IMAGE_LOADING");
45 #endif
46
47 static unsigned int gMaxTextureSize = 4096;
48
49 static bool gMaxTextureSizeUpdated = false;
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 // clang-format off
79 const Dali::ImageLoader::BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
80   {
81     {Png::MAGIC_BYTE_1,  Png::MAGIC_BYTE_2,  LoadBitmapFromPng,  LoadPngHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
82     {Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
83     {Bmp::MAGIC_BYTE_1,  Bmp::MAGIC_BYTE_2,  LoadBitmapFromBmp,  LoadBmpHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
84     {Gif::MAGIC_BYTE_1,  Gif::MAGIC_BYTE_2,  LoadBitmapFromGif,  LoadGifHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
85     {Ktx::MAGIC_BYTE_1,  Ktx::MAGIC_BYTE_2,  LoadBitmapFromKtx,  LoadKtxHeader,  Bitmap::BITMAP_COMPRESSED      },
86     {Astc::MAGIC_BYTE_1, Astc::MAGIC_BYTE_2, LoadBitmapFromAstc, LoadAstcHeader, Bitmap::BITMAP_COMPRESSED      },
87     {Ico::MAGIC_BYTE_1,  Ico::MAGIC_BYTE_2,  LoadBitmapFromIco,  LoadIcoHeader,  Bitmap::BITMAP_2D_PACKED_PIXELS},
88     {0x0,                0x0,                LoadBitmapFromWbmp, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
89   };
90 // clang-format on
91
92 const unsigned int MAGIC_LENGTH = 2;
93
94 /**
95  * This code tries to predict the file format from the filename to help with format picking.
96  */
97 struct FormatExtension
98 {
99   const std::string extension;
100   FileFormats       format;
101 };
102
103 // clang-format off
104 const FormatExtension FORMAT_EXTENSIONS[] =
105   {
106     {".png",  FORMAT_PNG },
107     {".jpg",  FORMAT_JPEG},
108     {".bmp",  FORMAT_BMP },
109     {".gif",  FORMAT_GIF },
110     {".ktx",  FORMAT_KTX },
111     {".astc", FORMAT_ASTC},
112     {".ico",  FORMAT_ICO },
113     {".wbmp", FORMAT_WBMP}
114   };
115 // clang-format on
116
117 const unsigned int FORMAT_EXTENSIONS_COUNT = sizeof(FORMAT_EXTENSIONS) / sizeof(FormatExtension);
118
119 FileFormats GetFormatHint(const std::string& filename)
120 {
121   FileFormats format = FORMAT_UNKNOWN;
122
123   for(unsigned int i = 0; i < FORMAT_EXTENSIONS_COUNT; ++i)
124   {
125     unsigned int length = FORMAT_EXTENSIONS[i].extension.size();
126     if((filename.size() > length) &&
127        (0 == filename.compare(filename.size() - length, length, FORMAT_EXTENSIONS[i].extension)))
128     {
129       format = FORMAT_EXTENSIONS[i].format;
130       break;
131     }
132   }
133
134   return format;
135 }
136
137 /**
138  * Checks the magic bytes of the file first to determine which Image decoder to use to decode the
139  * bitmap.
140  * @param[in]   fp      The file to decode
141  * @param[in]   format  Hint about what format to try first
142  * @param[out]  loader  Set with the function to use to decode the image
143  * @param[out]  header  Set with the function to use to decode the header
144  * @param[out]  profile The kind of bitmap to hold the bits loaded for the bitmap.
145  * @return true, if we can decode the image, false otherwise
146  */
147 bool GetBitmapLoaderFunctions(FILE*                                        fp,
148                               FileFormats                                  format,
149                               Dali::ImageLoader::LoadBitmapFunction&       loader,
150                               Dali::ImageLoader::LoadBitmapHeaderFunction& header,
151                               Bitmap::Profile&                             profile,
152                               const std::string&                           filename)
153 {
154   unsigned char magic[MAGIC_LENGTH];
155   size_t        read = fread(magic, sizeof(unsigned char), MAGIC_LENGTH, fp);
156
157   // Reset to the start of the file.
158   if(fseek(fp, 0, SEEK_SET))
159   {
160     DALI_LOG_ERROR("Error seeking to start of file\n");
161   }
162
163   if(read != MAGIC_LENGTH)
164   {
165     return false;
166   }
167
168   bool                                   loaderFound = false;
169   const Dali::ImageLoader::BitmapLoader* lookupPtr   = BITMAP_LOADER_LOOKUP_TABLE;
170   Dali::ImageLoader::Input               defaultInput(fp);
171
172   // try plugin image loader
173   const Dali::ImageLoader::BitmapLoader* data = Internal::Adaptor::ImageLoaderPluginProxy::BitmapLoaderLookup(filename);
174   if(data != NULL)
175   {
176     lookupPtr           = data;
177     unsigned int width  = 0;
178     unsigned int height = 0;
179     loaderFound         = lookupPtr->header(fp, width, height);
180   }
181
182   // try hinted format
183   if(false == loaderFound && format != FORMAT_UNKNOWN)
184   {
185     lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + format;
186     if(format >= FORMAT_MAGIC_BYTE_COUNT ||
187        (lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1]))
188     {
189       unsigned int width  = 0;
190       unsigned int height = 0;
191       loaderFound         = lookupPtr->header(fp, width, height);
192     }
193   }
194
195   // then try to get a match with formats that have magic bytes
196   if(false == loaderFound)
197   {
198     for(lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
199         lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
200         ++lookupPtr)
201     {
202       if(lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1])
203       {
204         // to seperate ico file format and wbmp file format
205         unsigned int width  = 0;
206         unsigned int height = 0;
207         loaderFound         = lookupPtr->header(fp, width, height);
208       }
209       if(loaderFound)
210       {
211         break;
212       }
213     }
214   }
215
216   // finally try formats that do not use magic bytes
217   if(false == loaderFound)
218   {
219     for(lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
220         lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_TOTAL_COUNT;
221         ++lookupPtr)
222     {
223       // to seperate ico file format and wbmp file format
224       unsigned int width  = 0;
225       unsigned int height = 0;
226       loaderFound         = lookupPtr->header(fp, width, height);
227       if(loaderFound)
228       {
229         break;
230       }
231     }
232   }
233
234   // if a loader was found set the outputs
235   if(loaderFound)
236   {
237     loader  = lookupPtr->loader;
238     header  = lookupPtr->header;
239     profile = lookupPtr->profile;
240   }
241
242   // Reset to the start of the file.
243   if(fseek(fp, 0, SEEK_SET))
244   {
245     DALI_LOG_ERROR("Error seeking to start of file\n");
246   }
247
248   return loaderFound;
249 }
250
251 } // anonymous namespace
252
253 namespace ImageLoader
254 {
255 bool ConvertStreamToBitmap(const BitmapResourceType& resource, std::string path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer)
256 {
257   DALI_LOG_TRACE_METHOD(gLogFilter);
258
259   bool result = false;
260
261   if(fp != NULL)
262   {
263     Dali::ImageLoader::LoadBitmapFunction       function;
264     Dali::ImageLoader::LoadBitmapHeaderFunction header;
265
266     Bitmap::Profile profile;
267
268     if(GetBitmapLoaderFunctions(fp,
269                                 GetFormatHint(path),
270                                 function,
271                                 header,
272                                 profile,
273                                 path))
274     {
275       const Dali::ImageLoader::ScalingParameters scalingParameters(resource.size, resource.scalingMode, resource.samplingMode);
276       const Dali::ImageLoader::Input             input(fp, scalingParameters, resource.orientationCorrection);
277
278       // Run the image type decoder:
279       result = function(input, pixelBuffer);
280
281       if(!result)
282       {
283         DALI_LOG_ERROR("Unable to convert %s\n", path.c_str());
284         pixelBuffer.Reset();
285       }
286
287       pixelBuffer = Internal::Platform::ApplyAttributesToBitmap(pixelBuffer, resource.size, resource.scalingMode, resource.samplingMode);
288     }
289     else
290     {
291       DALI_LOG_ERROR("Image Decoder for %s unavailable\n", path.c_str());
292     }
293   }
294
295   return result;
296 }
297
298 ResourcePointer LoadImageSynchronously(const Integration::BitmapResourceType& resource, const std::string& path)
299 {
300   ResourcePointer          result;
301   Dali::Devel::PixelBuffer bitmap;
302
303   Internal::Platform::FileReader fileReader(path);
304   FILE* const                    fp = fileReader.GetFile();
305   if(fp != NULL)
306   {
307     bool success = ConvertStreamToBitmap(resource, path, fp, bitmap);
308     if(success && bitmap)
309     {
310       Bitmap::Profile profile{Bitmap::Profile::BITMAP_2D_PACKED_PIXELS};
311
312       // For backward compatibility the Bitmap must be created
313       auto retval = Bitmap::New(profile, Dali::ResourcePolicy::OWNED_DISCARD);
314
315       DALI_LOG_SET_OBJECT_STRING(retval, path);
316
317       retval->GetPackedPixelsProfile()->ReserveBuffer(
318         bitmap.GetPixelFormat(),
319         bitmap.GetWidth(),
320         bitmap.GetHeight(),
321         bitmap.GetWidth(),
322         bitmap.GetHeight());
323
324       auto& impl = Dali::GetImplementation(bitmap);
325
326       std::copy(impl.GetBuffer(), impl.GetBuffer() + impl.GetBufferSize(), retval->GetBuffer());
327       result.Reset(retval);
328     }
329   }
330   return result;
331 }
332
333 ///@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?]
334 ImageDimensions GetClosestImageSize(const std::string& filename,
335                                     ImageDimensions    size,
336                                     FittingMode::Type  fittingMode,
337                                     SamplingMode::Type samplingMode,
338                                     bool               orientationCorrection)
339 {
340   unsigned int width  = 0;
341   unsigned int height = 0;
342
343   Internal::Platform::FileReader fileReader(filename);
344   FILE*                          fp = fileReader.GetFile();
345   if(fp != NULL)
346   {
347     Dali::ImageLoader::LoadBitmapFunction       loaderFunction;
348     Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
349     Bitmap::Profile                             profile;
350
351     if(GetBitmapLoaderFunctions(fp,
352                                 GetFormatHint(filename),
353                                 loaderFunction,
354                                 headerFunction,
355                                 profile,
356                                 filename))
357     {
358       const Dali::ImageLoader::Input input(fp, Dali::ImageLoader::ScalingParameters(size, fittingMode, samplingMode), orientationCorrection);
359
360       const bool read_res = headerFunction(input, width, height);
361       if(!read_res)
362       {
363         DALI_LOG_ERROR("Image Decoder failed to read header for %s\n", filename.c_str());
364       }
365     }
366     else
367     {
368       DALI_LOG_ERROR("Image Decoder for %s unavailable\n", filename.c_str());
369     }
370   }
371   return ImageDimensions(width, height);
372 }
373
374 ImageDimensions GetClosestImageSize(Integration::ResourcePointer resourceBuffer,
375                                     ImageDimensions              size,
376                                     FittingMode::Type            fittingMode,
377                                     SamplingMode::Type           samplingMode,
378                                     bool                         orientationCorrection)
379 {
380   unsigned int width  = 0;
381   unsigned int height = 0;
382
383   // Get the blob of binary data that we need to decode:
384   DALI_ASSERT_DEBUG(resourceBuffer);
385   Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>(resourceBuffer.Get());
386
387   if(encodedBlob != 0)
388   {
389     if(encodedBlob->GetVector().Size())
390     {
391       // Open a file handle on the memory buffer:
392       Internal::Platform::FileReader fileReader(encodedBlob->GetVector());
393       FILE*                          fp = fileReader.GetFile();
394       if(fp != NULL)
395       {
396         Dali::ImageLoader::LoadBitmapFunction       loaderFunction;
397         Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
398         Bitmap::Profile                             profile;
399
400         if(GetBitmapLoaderFunctions(fp,
401                                     FORMAT_UNKNOWN,
402                                     loaderFunction,
403                                     headerFunction,
404                                     profile,
405                                     ""))
406         {
407           const Dali::ImageLoader::Input input(fp, Dali::ImageLoader::ScalingParameters(size, fittingMode, samplingMode), orientationCorrection);
408           const bool                     read_res = headerFunction(input, width, height);
409           if(!read_res)
410           {
411             DALI_LOG_ERROR("Image Decoder failed to read header for resourceBuffer\n");
412           }
413         }
414       }
415     }
416   }
417   return ImageDimensions(width, height);
418 }
419
420 void SetMaxTextureSize(unsigned int size)
421 {
422   gMaxTextureSize        = size;
423   gMaxTextureSizeUpdated = true;
424 }
425
426 unsigned int GetMaxTextureSize()
427 {
428   return gMaxTextureSize;
429 }
430
431 bool MaxTextureSizeUpdated()
432 {
433   return gMaxTextureSizeUpdated;
434 }
435
436 } // namespace ImageLoader
437 } // namespace TizenPlatform
438 } // namespace Dali