[AT-SPI] Rework intercepting key events
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / webp-loading.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
18 // CLASS HEADER
19 #include <dali/internal/imaging/common/webp-loading.h>
20
21 // EXTERNAL INCLUDES
22 #ifdef DALI_WEBP_AVAILABLE
23 #include <webp/decode.h>
24 #include <webp/demux.h>
25
26 #if WEBP_DEMUX_ABI_VERSION > 0x0101
27 #define DALI_ANIMATED_WEBP_ENABLED 1
28 #endif
29
30 #endif
31 #include <dali/integration-api/debug.h>
32 #include <dali/public-api/images/pixel-data.h>
33
34 #include <dali/devel-api/threading/mutex.h>
35 #include <dali/internal/imaging/common/file-download.h>
36 #include <dali/internal/system/common/file-reader.h>
37 #include <fcntl.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <cstring>
42
43 // INTERNAL INCLUDES
44 #include <dali/devel-api/adaptor-framework/image-loading.h>
45 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
46
47 typedef uint8_t WebPByteType;
48
49 namespace Dali
50 {
51 namespace Internal
52 {
53 namespace Adaptor
54 {
55 namespace
56 {
57 #if defined(DEBUG_ENABLED)
58 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
59 #endif
60
61 static constexpr int32_t INITIAL_INDEX               = -1;
62 static constexpr size_t  MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
63
64 } // namespace
65
66 struct WebPLoading::Impl
67 {
68 public:
69   Impl(const std::string& url, bool isLocalResource)
70   : mFile(nullptr),
71     mUrl(url),
72     mFrameCount(1u),
73     mMutex(),
74     mBuffer(nullptr),
75     mBufferSize(0u),
76     mImageSize(),
77     mLoadSucceeded(false),
78     mIsAnimatedImage(false),
79     mIsLocalResource(isLocalResource)
80   {
81   }
82
83   Impl(FILE* const fp)
84   : mFile(fp),
85     mUrl(),
86     mFrameCount(1u),
87     mMutex(),
88     mBuffer(nullptr),
89     mBufferSize(0u),
90     mImageSize(),
91     mLoadSucceeded(false),
92     mIsAnimatedImage(false),
93     mIsLocalResource(true)
94   {
95   }
96
97   bool LoadWebPInformation()
98   {
99     // Block to do not load this file again.
100     Mutex::ScopedLock lock(mMutex);
101     if(DALI_UNLIKELY(mLoadSucceeded))
102     {
103       return mLoadSucceeded;
104     }
105
106 #ifndef DALI_WEBP_AVAILABLE
107     // If the system doesn't support webp, loading will be failed.
108     mFrameCount    = 0u;
109     mLoadSucceeded = false;
110     return mLoadSucceeded;
111 #endif
112
113     // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
114     if(DALI_LIKELY(ReadWebPInformation()))
115     {
116 #ifdef DALI_WEBP_AVAILABLE
117       WebPDataInit(&mWebPData);
118       mWebPData.size  = mBufferSize;
119       mWebPData.bytes = mBuffer;
120
121       WebPDemuxer* demuxer = WebPDemux(&mWebPData);
122       uint32_t     flags   = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
123       if(flags & ANIMATION_FLAG)
124       {
125         mIsAnimatedImage = true;
126       }
127       WebPDemuxDelete(demuxer);
128
129       if(!mIsAnimatedImage)
130       {
131         int32_t imageWidth, imageHeight;
132         if(WebPGetInfo(mBuffer, mBufferSize, &imageWidth, &imageHeight))
133         {
134           mImageSize = ImageDimensions(imageWidth, imageHeight);
135         }
136       }
137 #endif
138 #ifdef DALI_ANIMATED_WEBP_ENABLED
139       if(mIsAnimatedImage)
140       {
141         WebPAnimDecoderOptions webPAnimDecoderOptions;
142         WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
143         webPAnimDecoderOptions.color_mode = MODE_RGBA;
144         mWebPAnimDecoder                  = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
145         WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
146         mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
147         mFrameCount = mWebPAnimInfo.frame_count;
148         mImageSize  = ImageDimensions(mWebPAnimInfo.canvas_width, mWebPAnimInfo.canvas_height);
149       }
150 #endif
151       mLoadSucceeded = true;
152     }
153     else
154     {
155       mFrameCount    = 0u;
156       mLoadSucceeded = false;
157       DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
158     }
159
160     return mLoadSucceeded;
161   }
162
163   bool ReadWebPInformation()
164   {
165     mBufferSize = 0;
166
167     FILE*                                           fp = mFile;
168     std::unique_ptr<Internal::Platform::FileReader> fileReader;
169     Dali::Vector<uint8_t>                           dataBuffer;
170     if(fp == nullptr)
171     {
172       if(mIsLocalResource)
173       {
174         fileReader = std::make_unique<Internal::Platform::FileReader>(mUrl);
175       }
176       else
177       {
178         size_t dataSize;
179         if(DALI_LIKELY(TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE)))
180         {
181           mBufferSize = dataBuffer.Size();
182           if(DALI_LIKELY(mBufferSize > 0U))
183           {
184             // Open a file handle on the memory buffer:
185             fileReader = std::make_unique<Internal::Platform::FileReader>(dataBuffer, mBufferSize);
186           }
187         }
188       }
189
190       if(fileReader)
191       {
192         fp = fileReader->GetFile();
193       }
194     }
195
196     if(DALI_LIKELY(fp != nullptr))
197     {
198       if(mBufferSize == 0)
199       {
200         if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
201         {
202           return false;
203         }
204         mBufferSize = ftell(fp);
205       }
206
207       if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
208       {
209         mBuffer     = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
210         mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
211         return true;
212       }
213     }
214     return false;
215   }
216
217   void ReleaseResource()
218   {
219 #ifdef DALI_WEBP_AVAILABLE
220     mWebPData.bytes = nullptr;
221     WebPDataInit(&mWebPData);
222 #endif
223 #ifdef DALI_ANIMATED_WEBP_ENABLED
224     if(mIsAnimatedImage)
225     {
226       if(mWebPAnimDecoder != nullptr)
227       {
228         WebPAnimDecoderDelete(mWebPAnimDecoder);
229         mWebPAnimDecoder = nullptr;
230       }
231     }
232 #endif
233     if(mBuffer != nullptr)
234     {
235       free((void*)mBuffer);
236       mBuffer = nullptr;
237     }
238
239     mLoadSucceeded = false;
240   }
241
242   // Moveable but not copyable
243
244   Impl(const Impl&) = delete;
245   Impl& operator=(const Impl&) = delete;
246   Impl(Impl&&)                 = default;
247   Impl& operator=(Impl&&) = default;
248
249   ~Impl()
250   {
251     ReleaseResource();
252   }
253
254   FILE*                 mFile;
255   std::string           mUrl;
256   std::vector<uint32_t> mTimeStamp;
257   int32_t               mLatestLoadedFrame{INITIAL_INDEX};
258   uint32_t              mFrameCount;
259   Mutex                 mMutex;
260   // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
261   unsigned char*  mBuffer;
262   uint32_t        mBufferSize;
263   ImageDimensions mImageSize;
264   bool            mLoadSucceeded;
265   bool            mIsAnimatedImage;
266   bool            mIsLocalResource;
267
268 #ifdef DALI_WEBP_AVAILABLE
269   WebPData mWebPData{0};
270 #endif
271
272 #ifdef DALI_ANIMATED_WEBP_ENABLED
273   WebPAnimDecoder*         mWebPAnimDecoder{nullptr};
274   WebPAnimInfo             mWebPAnimInfo{0};
275   Dali::Devel::PixelBuffer mPreLoadedFrame{};
276 #endif
277 };
278
279 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
280 {
281 #ifndef DALI_ANIMATED_WEBP_ENABLED
282   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
283 #endif
284   return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
285 }
286
287 AnimatedImageLoadingPtr WebPLoading::New(FILE* const fp)
288 {
289 #ifndef DALI_ANIMATED_WEBP_ENABLED
290   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
291 #endif
292   return AnimatedImageLoadingPtr(new WebPLoading(fp));
293 }
294
295 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
296 : mImpl(new WebPLoading::Impl(url, isLocalResource))
297 {
298 }
299
300 WebPLoading::WebPLoading(FILE* const fp)
301 : mImpl(new WebPLoading::Impl(fp))
302 {
303 }
304
305 WebPLoading::~WebPLoading()
306 {
307   delete mImpl;
308 }
309
310 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex, ImageDimensions desiredSize)
311 {
312   Dali::Devel::PixelBuffer pixelBuffer;
313
314   // If WebP file is still not loaded, Load the information.
315   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
316   {
317     if(DALI_UNLIKELY(!mImpl->LoadWebPInformation()))
318     {
319       mImpl->ReleaseResource();
320       return pixelBuffer;
321     }
322   }
323
324   // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
325   // If frame count is 1, use WebPDecodeRGBA api.
326 #ifdef DALI_WEBP_AVAILABLE
327   if(!mImpl->mIsAnimatedImage)
328   {
329     int32_t width, height;
330     if(DALI_LIKELY(WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height)))
331     {
332       WebPBitstreamFeatures features;
333       if(DALI_LIKELY(VP8_STATUS_NOT_ENOUGH_DATA != WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features)))
334       {
335         uint32_t channelNumber = (features.has_alpha) ? 4 : 3;
336         uint8_t* frameBuffer   = nullptr;
337
338         if(desiredSize.GetWidth() > 0 && desiredSize.GetHeight() > 0)
339         {
340           const int desiredWidth = desiredSize.GetWidth();
341           const int desiredHeight = desiredSize.GetHeight();
342
343           WebPDecoderConfig config;
344           if(!DALI_LIKELY(WebPInitDecoderConfig(&config)))
345           {
346             DALI_LOG_ERROR("WebPInitDecoderConfig is failed");
347             return pixelBuffer;
348           }
349
350           // Apply config for scaling
351           config.options.use_scaling = 1;
352           config.options.scaled_width = desiredWidth;
353           config.options.scaled_height = desiredHeight;
354
355           if(channelNumber == 4)
356           {
357             config.output.colorspace = MODE_RGBA;
358           }
359           else
360           {
361             config.output.colorspace = MODE_RGB;
362           }
363
364           if(WebPDecode(mImpl->mBuffer, mImpl->mBufferSize, &config) == VP8_STATUS_OK)
365           {
366             frameBuffer = config.output.u.RGBA.rgba;
367           }
368           else
369           {
370             DALI_LOG_ERROR("Webp Decoding with scaled size is failed \n");
371           }
372
373           if(frameBuffer != nullptr)
374           {
375             Pixel::Format                     pixelFormat = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
376             int32_t                           bufferSize  = desiredWidth * desiredHeight * Dali::Pixel::GetBytesPerPixel(pixelFormat);
377             pixelBuffer  = Dali::Devel::PixelBuffer::New(desiredWidth, desiredHeight, pixelFormat);
378             memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
379           }
380
381           WebPFreeDecBuffer(&config.output);
382         }
383         else
384         {
385           if(channelNumber == 4)
386           {
387             frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
388           }
389           else
390           {
391             frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
392           }
393
394           if(frameBuffer != nullptr)
395           {
396             Pixel::Format                     pixelFormat = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
397             int32_t                           bufferSize  = width * height * Dali::Pixel::GetBytesPerPixel(pixelFormat);
398             Internal::Adaptor::PixelBufferPtr internal =
399             Internal::Adaptor::PixelBuffer::New(frameBuffer, bufferSize, width, height, width, pixelFormat);
400             pixelBuffer = Devel::PixelBuffer(internal.Get());
401           }
402         }
403       }
404     }
405     // The single frame resource should be released after loading.
406     mImpl->ReleaseResource();
407   }
408 #endif
409
410 #ifdef DALI_ANIMATED_WEBP_ENABLED
411   if(mImpl->mIsAnimatedImage && mImpl->mBuffer != nullptr)
412   {
413     Mutex::ScopedLock lock(mImpl->mMutex);
414     if(DALI_LIKELY(frameIndex < mImpl->mWebPAnimInfo.frame_count && mImpl->mLoadSucceeded))
415     {
416       DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
417
418       if(mImpl->mPreLoadedFrame && mImpl->mLatestLoadedFrame == static_cast<int32_t>(frameIndex))
419       {
420         pixelBuffer = mImpl->mPreLoadedFrame;
421       }
422       else
423       {
424         pixelBuffer = DecodeFrame(frameIndex);
425       }
426       mImpl->mPreLoadedFrame.Reset();
427
428       // If time stamp of next frame is unknown, load a frame more to know it.
429       if(frameIndex + 1 < mImpl->mWebPAnimInfo.frame_count && mImpl->mTimeStamp[frameIndex + 1] == 0u)
430       {
431         mImpl->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
432       }
433     }
434     else
435     {
436       mImpl->ReleaseResource();
437     }
438   }
439 #endif
440   return pixelBuffer;
441 }
442
443 Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
444 {
445   Dali::Devel::PixelBuffer pixelBuffer;
446 #ifdef DALI_ANIMATED_WEBP_ENABLED
447   if(mImpl->mLatestLoadedFrame >= static_cast<int32_t>(frameIndex))
448   {
449     mImpl->mLatestLoadedFrame = INITIAL_INDEX;
450     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
451   }
452
453   uint8_t* frameBuffer = nullptr;
454   int32_t  timestamp   = 0u;
455   for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
456   {
457     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
458     mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
459   }
460
461   if(frameBuffer != nullptr)
462   {
463     const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
464     pixelBuffer          = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
465     memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
466   }
467
468 #endif
469   return pixelBuffer;
470 }
471
472 ImageDimensions WebPLoading::GetImageSize() const
473 {
474   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
475   {
476     mImpl->LoadWebPInformation();
477   }
478   return mImpl->mImageSize;
479 }
480
481 uint32_t WebPLoading::GetImageCount() const
482 {
483   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
484   {
485     mImpl->LoadWebPInformation();
486   }
487   return mImpl->mFrameCount;
488 }
489
490 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
491 {
492   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
493   {
494     DALI_LOG_ERROR("WebP file is still not loaded, this frame interval could not be correct value.\n");
495   }
496   if(frameIndex >= GetImageCount())
497   {
498     DALI_LOG_ERROR("Input frameIndex exceeded frame count of the WebP.");
499     return 0u;
500   }
501   else
502   {
503     int32_t interval = 0u;
504     if(GetImageCount() == 1u)
505     {
506       return 0u;
507     }
508     else if(frameIndex + 1 == GetImageCount())
509     {
510       // For the interval between last frame and first frame, use last interval again.
511       interval = mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
512     }
513     else
514     {
515       interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
516     }
517
518     if(DALI_UNLIKELY(interval < 0))
519     {
520       DALI_LOG_ERROR("This interval value is not correct, because the frame still hasn't ever been decoded.");
521       return 0u;
522     }
523     return static_cast<uint32_t>(interval);
524   }
525 }
526
527 std::string WebPLoading::GetUrl() const
528 {
529   return mImpl->mUrl;
530 }
531
532 bool WebPLoading::HasLoadingSucceeded() const
533 {
534   return mImpl->mLoadSucceeded;
535 }
536
537 } // namespace Adaptor
538
539 } // namespace Internal
540
541 } // namespace Dali