Refactoring Animated image loading 09/267009/12
authorseungho <sbsh.baek@samsung.com>
Wed, 24 Nov 2021 04:46:35 +0000 (13:46 +0900)
committerseungho <sbsh.baek@samsung.com>
Tue, 12 Apr 2022 06:05:05 +0000 (15:05 +0900)
 - Open file in the first call to request frame.
 - In case of WebP, load one more frame in the first loop to compute exact time interval.

Change-Id: I03a3b79af278c64d7911d4c65ad300bd09d76bfd
Signed-off-by: seungho <sbsh.baek@samsung.com>
dali/internal/imaging/common/gif-loading.cpp
dali/internal/imaging/common/webp-loading.cpp
dali/internal/imaging/common/webp-loading.h

index 685f206eeb16e11496af57b27f00f7134cb25c73..e72e60031f0be6da845d6330f939221ed904c7ee 100644 (file)
@@ -871,12 +871,10 @@ on_error:
  *
  * @param[in] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
  * @param[out] prop A ImageProperties structure for storing information about GIF data.
- * @param[out] error Error code
  * @return The true or false whether reading was successful or not.
  */
 bool ReadHeader(LoaderInfo&      loaderInfo,
-                ImageProperties& prop, //output struct
-                int*             error)
+                ImageProperties& prop)
 {
   GifAnimationData&     animated    = loaderInfo.animated;
   GifCachedColorData&   cachedColor = loaderInfo.cachedColor;
@@ -1077,9 +1075,6 @@ bool ReadHeader(LoaderInfo&      loaderInfo,
               cachedColor.globalCachedColor[i] = PixelLookup(colorMap, i);
             }
           }
-
-          // no errors in header scan etc. so set err and return value
-          *error = 0;
         }
       }
     }
@@ -1398,15 +1393,25 @@ struct GifLoading::Impl
 public:
   Impl(const std::string& url, bool isLocalResource)
   : mUrl(url),
-    mLoadSucceeded(true),
+    mLoadSucceeded(false),
     mMutex()
   {
     loaderInfo.gifAccessor = nullptr;
-    int error;
     loaderInfo.fileData.fileName        = mUrl.c_str();
     loaderInfo.fileData.isLocalResource = isLocalResource;
+  }
+
+  bool LoadGifInformation()
+  {
+    // Block to do not load this file again.
+    Mutex::ScopedLock lock(mMutex);
+    if(DALI_LIKELY(mLoadSucceeded))
+    {
+      return mLoadSucceeded;
+    }
 
-    mLoadSucceeded = ReadHeader(loaderInfo, imageProperties, &error);
+    mLoadSucceeded = ReadHeader(loaderInfo, imageProperties);
+    return mLoadSucceeded;
   }
 
   // Moveable but not copyable
@@ -1442,12 +1447,12 @@ bool GifLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vecto
   int  error;
   bool ret = false;
 
-  Mutex::ScopedLock lock(mImpl->mMutex);
-  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  if(DALI_UNLIKELY(!mImpl->LoadGifInformation()))
   {
     return false;
   }
 
+  Mutex::ScopedLock lock(mImpl->mMutex);
   const int bufferSize = mImpl->imageProperties.w * mImpl->imageProperties.h * sizeof(uint32_t);
 
   DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "LoadNextNFrames( frameStartIndex:%d, count:%d )\n", frameStartIndex, count);
@@ -1475,14 +1480,16 @@ Dali::Devel::PixelBuffer GifLoading::LoadFrame(uint32_t frameIndex)
 {
   int                      error;
   Dali::Devel::PixelBuffer pixelBuffer;
-  if(!mImpl->mLoadSucceeded)
+
+  DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
+
+  // If Gif file is still not loaded, Load the information.
+  if(DALI_UNLIKELY(!mImpl->LoadGifInformation()))
   {
     return pixelBuffer;
   }
 
   Mutex::ScopedLock lock(mImpl->mMutex);
-  DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
-
   pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->imageProperties.w, mImpl->imageProperties.h, Dali::Pixel::RGBA8888);
 
   mImpl->loaderInfo.animated.currentFrame = 1 + (frameIndex % mImpl->loaderInfo.animated.frameCount);
@@ -1497,17 +1504,35 @@ Dali::Devel::PixelBuffer GifLoading::LoadFrame(uint32_t frameIndex)
 
 ImageDimensions GifLoading::GetImageSize() const
 {
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  {
+    mImpl->LoadGifInformation();
+  }
   return ImageDimensions(mImpl->imageProperties.w, mImpl->imageProperties.h);
 }
 
 uint32_t GifLoading::GetImageCount() const
 {
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  {
+    mImpl->LoadGifInformation();
+  }
   return mImpl->loaderInfo.animated.frameCount;
 }
 
 uint32_t GifLoading::GetFrameInterval(uint32_t frameIndex) const
 {
-  return mImpl->loaderInfo.animated.frames[frameIndex].info.delay * 10;
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  {
+    mImpl->LoadGifInformation();
+  }
+
+  uint32_t interval = 0u;
+  if(DALI_LIKELY(mImpl->mLoadSucceeded))
+  {
+    interval = mImpl->loaderInfo.animated.frames[frameIndex].info.delay * 10;
+  }
+  return interval;
 }
 
 std::string GifLoading::GetUrl() const
index cd829d723fa7234bb73eceaea83a8c04ff92b85d..308bd9e7972044a83a242bf83c304b6f546d63fd 100644 (file)
@@ -57,7 +57,8 @@ namespace
 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
 #endif
 
-constexpr size_t MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
+static constexpr int32_t INITIAL_INDEX               = -1;
+static constexpr size_t  MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
 
 } // namespace
 
@@ -71,10 +72,29 @@ public:
     mBuffer(nullptr),
     mBufferSize(0u),
     mImageSize(),
-    mLoadSucceeded(true)
+    mLoadSucceeded(false),
+    mIsLocalResource(isLocalResource)
   {
+  }
+
+  bool LoadWebPInformation()
+  {
+    // Block to do not load this file again.
+    Mutex::ScopedLock lock(mMutex);
+    if(DALI_UNLIKELY(mLoadSucceeded))
+    {
+      return mLoadSucceeded;
+    }
+
+#ifndef DALI_WEBP_AVAILABLE
+    // If the system doesn't support webp, loading will be failed.
+    mFrameCount    = 0u;
+    mLoadSucceeded = false;
+    return mLoadSucceeded;
+#endif
+
     // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
-    if(DALI_LIKELY(ReadWebPInformation(isLocalResource)))
+    if(DALI_LIKELY(ReadWebPInformation()))
     {
 #ifdef DALI_ANIMATED_WEBP_ENABLED
       WebPDataInit(&mWebPData);
@@ -95,11 +115,7 @@ public:
         mImageSize = ImageDimensions(imageWidth, imageHeight);
       }
 #endif
-#ifndef DALI_WEBP_AVAILABLE
-      // If the system doesn't support webp, loading will be failed.
-      mFrameCount    = 0u;
-      mLoadSucceeded = false;
-#endif
+      mLoadSucceeded = true;
     }
     else
     {
@@ -107,12 +123,14 @@ public:
       mLoadSucceeded = false;
       DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
     }
+
+    return mLoadSucceeded;
   }
 
-  bool ReadWebPInformation(bool isLocalResource)
+  bool ReadWebPInformation()
   {
     FILE* fp = nullptr;
-    if(isLocalResource)
+    if(mIsLocalResource)
     {
       Internal::Platform::FileReader fileReader(mUrl);
       fp = fileReader.GetFile();
@@ -200,7 +218,7 @@ public:
 
   std::string           mUrl;
   std::vector<uint32_t> mTimeStamp;
-  uint32_t              mLoadingFrame{0};
+  int32_t               mLatestLoadedFrame{INITIAL_INDEX};
   uint32_t              mFrameCount;
   Mutex                 mMutex;
   // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
@@ -208,11 +226,13 @@ public:
   uint32_t        mBufferSize;
   ImageDimensions mImageSize;
   bool            mLoadSucceeded;
+  bool            mIsLocalResource;
 
 #ifdef DALI_ANIMATED_WEBP_ENABLED
-  WebPData         mWebPData{0};
-  WebPAnimDecoder* mWebPAnimDecoder{nullptr};
-  WebPAnimInfo     mWebPAnimInfo{0};
+  WebPData                 mWebPData{0};
+  WebPAnimDecoder*         mWebPAnimDecoder{nullptr};
+  WebPAnimInfo             mWebPAnimInfo{0};
+  Dali::Devel::PixelBuffer mPreLoadedFrame{};
 #endif
 };
 
@@ -239,7 +259,12 @@ bool WebPLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vect
   for(int i = 0; i < count; ++i)
   {
     Dali::Devel::PixelBuffer pixelBuffer = LoadFrame((frameStartIndex + i) % mImpl->mFrameCount);
-    Dali::PixelData          imageData   = Devel::PixelBuffer::Convert(pixelBuffer);
+    if(DALI_UNLIKELY(!pixelBuffer))
+    {
+      return false;
+    }
+
+    Dali::PixelData imageData = Devel::PixelBuffer::Convert(pixelBuffer);
     pixelData.push_back(imageData);
   }
   if(DALI_UNLIKELY(pixelData.size() != static_cast<uint32_t>(count)))
@@ -253,6 +278,15 @@ Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
 {
   Dali::Devel::PixelBuffer pixelBuffer;
 
+  // If WebP file is still not loaded, Load the information.
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  {
+    if(DALI_UNLIKELY(!mImpl->LoadWebPInformation()))
+    {
+      return pixelBuffer;
+    }
+  }
+
   // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
   // If frame count is 1, use WebPDecodeRGBA api.
 #ifdef DALI_WEBP_AVAILABLE
@@ -303,63 +337,103 @@ Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
 
   DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
 
-  if(mImpl->mLoadingFrame > frameIndex)
+  if(mImpl->mPreLoadedFrame && mImpl->mLatestLoadedFrame == static_cast<int32_t>(frameIndex))
+  {
+    pixelBuffer = mImpl->mPreLoadedFrame;
+  }
+  else
+  {
+    pixelBuffer = DecodeFrame(frameIndex);
+  }
+  mImpl->mPreLoadedFrame.Reset();
+
+  // If time stamp of next frame is unknown, load a frame more to know it.
+  if(frameIndex + 1 < mImpl->mWebPAnimInfo.frame_count && mImpl->mTimeStamp[frameIndex + 1] == 0u)
   {
-    mImpl->mLoadingFrame = 0;
+    mImpl->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
+  }
+
+#endif
+  return pixelBuffer;
+}
+
+Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
+{
+  Dali::Devel::PixelBuffer pixelBuffer;
+#ifdef DALI_ANIMATED_WEBP_ENABLED
+  if(mImpl->mLatestLoadedFrame > static_cast<int32_t>(frameIndex))
+  {
+    mImpl->mLatestLoadedFrame = INITIAL_INDEX;
     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
   }
 
-  for(; mImpl->mLoadingFrame < frameIndex; ++mImpl->mLoadingFrame)
+  uint8_t* frameBuffer;
+  int32_t timestamp;
+  for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
   {
-    uint8_t* frameBuffer;
-    int      timestamp;
     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
-    mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
+    mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
   }
-
   const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
-  uint8_t*  frameBuffer;
-  int       timestamp;
-  WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
-
   pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
   memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
-  mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
 
-  mImpl->mLoadingFrame++;
-  if(mImpl->mLoadingFrame >= mImpl->mWebPAnimInfo.frame_count)
-  {
-    mImpl->mLoadingFrame = 0;
-    WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
-  }
 #endif
   return pixelBuffer;
 }
 
 ImageDimensions WebPLoading::GetImageSize() const
 {
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  {
+    mImpl->LoadWebPInformation();
+  }
   return mImpl->mImageSize;
 }
 
 uint32_t WebPLoading::GetImageCount() const
 {
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+  {
+    mImpl->LoadWebPInformation();
+  }
   return mImpl->mFrameCount;
 }
 
 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
 {
-  // If frameIndex is above the value of ImageCount or current frame is not loading yet, return 0u.
-  if(frameIndex >= GetImageCount() || (frameIndex > 0 && mImpl->mTimeStamp[frameIndex - 1] > mImpl->mTimeStamp[frameIndex]))
+  if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
   {
+    DALI_LOG_ERROR("WebP file is still not loaded, this frame interval could not be correct value.\n");
+  }
+  if(frameIndex >= GetImageCount())
+  {
+    DALI_LOG_ERROR("Input frameIndex exceeded frame count of the WebP.");
     return 0u;
   }
   else
   {
-    if(frameIndex > 0)
+    int32_t interval = 0u;
+    if(GetImageCount() == 1u)
+    {
+      return 0u;
+    }
+    else if(frameIndex + 1 == GetImageCount())
+    {
+      // For the interval between last frame and first frame, use last interval again.
+      interval = mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
+    }
+    else
+    {
+      interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
+    }
+
+    if(DALI_UNLIKELY(interval < 0))
     {
-      return mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
+      DALI_LOG_ERROR("This interval value is not correct, because the frame still hasn't ever been decoded.");
+      return 0u;
     }
-    return mImpl->mTimeStamp[frameIndex];
+    return static_cast<uint32_t>(interval);
   }
 }
 
index ecf8bbb6ea694f97a3003e8b63297c61280647de..f3428e6da6392fd21274a65abf90d0aad1c5d309 100644 (file)
@@ -89,7 +89,6 @@ public:
    * @param[in] frameIndex The frame counter to load. Will usually be the next frame.
    * @return Dali::Devel::PixelBuffer The loaded PixelBuffer. If loading is fail, return empty handle.
    */
-
   Dali::Devel::PixelBuffer LoadFrame(uint32_t frameIndex) override;
 
   /**
@@ -109,7 +108,7 @@ public:
    *
    * @note The frame is needed to be loaded before this function is called.
    *
-   * @return The time interval of the frame(microsecond).
+   * @return The time interval between frameIndex and frameIndex + 1(microsecond).
    */
   uint32_t GetFrameInterval(uint32_t frameIndex) const override;
 
@@ -127,6 +126,15 @@ public:
    */
   bool HasLoadingSucceeded() const override;
 
+private:
+  /**
+   * @brief Decode Frame of the animated image.
+   *
+   * @param[in] frameIndex The frame counter to load. Will usually be the next frame.
+   * @return Dali::Devel::PixelBuffer The loaded PixelBuffer. If loading is fail, return empty handle.
+   */
+  Dali::Devel::PixelBuffer DecodeFrame(uint32_t frameIndex);
+
 private:
   struct Impl;
   Impl* mImpl;