[Tizen] Set the LoadSuccessflag to false after ReleaseResource() is called
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / webp-loading.cpp
index d3031fa..8efdc5b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <webp/demux.h>
 
 #if WEBP_DEMUX_ABI_VERSION > 0x0101
-#define DALI_WEBP_ENABLED 1
+#define DALI_ANIMATED_WEBP_ENABLED 1
 #endif
 
 #endif
 #include <dali/integration-api/debug.h>
 #include <dali/public-api/images/pixel-data.h>
 
-#include <sys/types.h>
-#include <sys/stat.h>
+#include <dali/devel-api/threading/mutex.h>
+#include <dali/internal/imaging/common/file-download.h>
+#include <dali/internal/system/common/file-reader.h>
 #include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 #include <cstring>
-#include <dali/internal/imaging/common/file-download.h>
-#include <dali/internal/system/common/file-reader.h>
+
+// INTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/image-loading.h>
 
 typedef unsigned char WebPByteType;
 
 namespace Dali
 {
-
 namespace Internal
 {
-
 namespace Adaptor
 {
-
 namespace
 {
-
 #if defined(DEBUG_ENABLED)
-Debug::Filter *gWebPLoadingLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_GIF_LOADING" );
+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
 
 struct WebPLoading::Impl
 {
 public:
-  Impl( const std::string& url, bool isLocalResource )
-  : mUrl( url ),
-    mLoadingFrame( 0 )
+  Impl(const std::string& url, bool isLocalResource)
+  : mUrl(url),
+    mFrameCount(1u),
+    mMutex(),
+    mBuffer(nullptr),
+    mBufferSize(0u),
+    mImageSize(),
+    mLoadSucceeded(false),
+    mIsLocalResource(isLocalResource)
   {
-#ifdef DALI_WEBP_ENABLED
-    if( ReadWebPInformation( isLocalResource ) )
+  }
+
+  bool LoadWebPInformation()
+  {
+    // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
+    if(ReadWebPInformation())
     {
+      mLoadSucceeded = true;
+#ifdef DALI_ANIMATED_WEBP_ENABLED
+      WebPDataInit(&mWebPData);
+      mWebPData.size  = mBufferSize;
+      mWebPData.bytes = mBuffer;
       WebPAnimDecoderOptions webPAnimDecoderOptions;
-      WebPAnimDecoderOptionsInit( &webPAnimDecoderOptions );
+      WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
       webPAnimDecoderOptions.color_mode = MODE_RGBA;
-      mWebPAnimDecoder = WebPAnimDecoderNew( &mWebPData, &webPAnimDecoderOptions );
-      WebPAnimDecoderGetInfo( mWebPAnimDecoder, &mWebPAnimInfo );
-      mTimeStamp.assign( mWebPAnimInfo.frame_count, 0 );
-    }
+      mWebPAnimDecoder                  = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
+      WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
+      mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
+      mFrameCount = mWebPAnimInfo.frame_count;
+      mImageSize  = ImageDimensions(mWebPAnimInfo.canvas_width, mWebPAnimInfo.canvas_height);
+#elif DALI_WEBP_AVAILABLE
+      int32_t imageWidth, imageHeight;
+      if(WebPGetInfo(mBuffer, mBufferSize, &imageWidth, &imageHeight))
+      {
+        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
+    }
+    else
+    {
+      mFrameCount    = 0u;
+      mLoadSucceeded = false;
+      DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
+    }
+
+    return mLoadSucceeded;
   }
 
-  bool ReadWebPInformation( bool isLocalResource )
+  bool ReadWebPInformation()
   {
-#ifdef DALI_WEBP_ENABLED
-    WebPDataInit( &mWebPData );
-    if( isLocalResource )
+    FILE* fp = nullptr;
+    if(mIsLocalResource)
     {
-      Internal::Platform::FileReader fileReader( mUrl );
-      FILE *fp = fileReader.GetFile();
-      if( fp == NULL )
+      Internal::Platform::FileReader fileReader(mUrl);
+      fp = fileReader.GetFile();
+      if(fp == nullptr)
       {
         return false;
       }
 
-      if( fseek( fp, 0, SEEK_END ) <= -1 )
+      if(fseek(fp, 0, SEEK_END) <= -1)
       {
         return false;
       }
 
-      mWebPData.size = ftell( fp );
-      if( ( ! fseek( fp, 0, SEEK_SET ) ) )
+      mBufferSize = ftell(fp);
+      if(!fseek(fp, 0, SEEK_SET))
       {
-        unsigned char *WebPDataBuffer;
-        WebPDataBuffer = reinterpret_cast<WebPByteType*>( malloc(sizeof( WebPByteType ) * mWebPData.size ) );
-        mWebPData.size = fread( WebPDataBuffer, sizeof( WebPByteType ), mWebPData.size, fp );
-        mWebPData.bytes = WebPDataBuffer;
-      }
-      else
-      {
-        return false;
+        mBuffer     = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
+        mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
+        return true;
       }
     }
     else
     {
       // remote file
-      bool succeeded;
+      bool                  succeeded;
       Dali::Vector<uint8_t> dataBuffer;
-      size_t dataSize;
+      size_t                dataSize;
 
-      succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory( mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE );
-      if( succeeded )
+      succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE);
+      if(succeeded)
       {
-        size_t blobSize = dataBuffer.Size();
-        if( blobSize > 0U )
+        mBufferSize = dataBuffer.Size();
+        if(mBufferSize > 0U)
         {
           // Open a file handle on the memory buffer:
-          Dali::Internal::Platform::FileReader fileReader( dataBuffer, blobSize );
-          FILE * const fp = fileReader.GetFile();
-          if ( NULL != fp )
+          Internal::Platform::FileReader fileReader(dataBuffer, mBufferSize);
+          fp = fileReader.GetFile();
+          if(fp != nullptr)
           {
-            if( ( ! fseek( fp, 0, SEEK_SET ) ) )
-            {
-              unsigned char *WebPDataBuffer;
-              WebPDataBuffer = reinterpret_cast<WebPByteType*>( malloc(sizeof( WebPByteType ) * blobSize ) );
-              mWebPData.size = fread( WebPDataBuffer, sizeof( WebPByteType ), mWebPData.size, fp );
-              mWebPData.bytes = WebPDataBuffer;
-            }
-            else
+            if(!fseek(fp, 0, SEEK_SET))
             {
-              DALI_LOG_ERROR( "Error seeking within file\n" );
+              mBuffer     = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
+              mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
+              return true;
             }
           }
-          else
-          {
-            DALI_LOG_ERROR( "Error reading file\n" );
-          }
         }
       }
     }
-    return true;
-#else
     return false;
-#endif
   }
 
-  // Moveable but not copyable
-
-  Impl( const Impl& ) = delete;
-  Impl& operator=( const Impl& ) = delete;
-  Impl( Impl&& ) = default;
-  Impl& operator=( Impl&& ) = default;
-
-  ~Impl()
+  void ReleaseResource()
   {
-#ifdef DALI_WEBP_ENABLED
-    if( &mWebPData != NULL )
+#ifdef DALI_ANIMATED_WEBP_ENABLED
+    if(&mWebPData != nullptr)
     {
-      free( (void*)mWebPData.bytes );
       mWebPData.bytes = nullptr;
-      WebPDataInit( &mWebPData );
+      WebPDataInit(&mWebPData);
     }
-    if( mWebPAnimDecoder )
+    if(mWebPAnimDecoder != nullptr)
     {
       WebPAnimDecoderDelete(mWebPAnimDecoder);
+      mWebPAnimDecoder = nullptr;
     }
 #endif
+    if(mBuffer != nullptr)
+    {
+      free((void*)mBuffer);
+      mBuffer = nullptr;
+    }
+
+    // Set the flag so that webp information can be reloaded when visual is re-attached to scene.
+    mLoadSucceeded = false;
   }
 
-  std::string mUrl;
-  std::vector<uint32_t> mTimeStamp;
-  uint32_t mLoadingFrame;
+  // Moveable but not copyable
+
+  Impl(const Impl&) = delete;
+  Impl& operator=(const Impl&) = delete;
+  Impl(Impl&&)                 = default;
+  Impl& operator=(Impl&&) = default;
+
+  ~Impl()
+  {
+    ReleaseResource();
+  }
 
-#ifdef DALI_WEBP_ENABLED
-  WebPData mWebPData;
-  WebPAnimDecoder* mWebPAnimDecoder;
-  WebPAnimInfo mWebPAnimInfo;
+  std::string           mUrl;
+  std::vector<uint32_t> mTimeStamp;
+  int32_t               mLatestLoadedFrame{INITIAL_INDEX};
+  uint32_t              mFrameCount;
+  Mutex                 mMutex;
+  // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
+  unsigned char*  mBuffer;
+  uint32_t        mBufferSize;
+  ImageDimensions mImageSize;
+  bool            mLoadSucceeded;
+  bool            mIsLocalResource;
+
+#ifdef DALI_ANIMATED_WEBP_ENABLED
+  WebPData                 mWebPData{0};
+  WebPAnimDecoder*         mWebPAnimDecoder{nullptr};
+  WebPAnimInfo             mWebPAnimInfo{0};
+  Dali::Devel::PixelBuffer mPreLoadedFrame{};
 #endif
 };
 
-AnimatedImageLoadingPtr WebPLoading::New( const std::string &url, bool isLocalResource )
+AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
 {
-#ifndef DALI_WEBP_ENABLED
-  DALI_LOG_ERROR( "The system does not support Animated WebP format.\n" );
+#ifndef DALI_ANIMATED_WEBP_ENABLED
+  DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
 #endif
-  return AnimatedImageLoadingPtr( new WebPLoading( url, isLocalResource ) );
+  return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
 }
 
-WebPLoading::WebPLoading( const std::string &url, bool isLocalResource )
-: mImpl( new WebPLoading::Impl( url, isLocalResource ) )
+WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
+: mImpl(new WebPLoading::Impl(url, isLocalResource))
 {
 }
 
@@ -207,96 +248,203 @@ WebPLoading::~WebPLoading()
   delete mImpl;
 }
 
-bool WebPLoading::LoadNextNFrames( uint32_t frameStartIndex, int count, std::vector<Dali::PixelData> &pixelData )
+bool WebPLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vector<Dali::PixelData>& pixelData)
 {
-#ifdef DALI_WEBP_ENABLED
-  if( frameStartIndex  >= mImpl->mWebPAnimInfo.frame_count )
+  for(int i = 0; i < count; ++i)
   {
-    return false;
-  }
-
-  DALI_LOG_INFO( gWebPLoadingLogFilter, Debug::Concise, "LoadNextNFrames( frameStartIndex:%d, count:%d )\n", frameStartIndex, count );
+    Dali::Devel::PixelBuffer pixelBuffer = LoadFrame((frameStartIndex + i) % mImpl->mFrameCount);
+    if(!pixelBuffer)
+    {
+      return false;
+    }
 
-  if( mImpl->mLoadingFrame > frameStartIndex  )
+    Dali::PixelData imageData = Devel::PixelBuffer::Convert(pixelBuffer);
+    pixelData.push_back(imageData);
+  }
+  if(pixelData.size() != static_cast<uint32_t>(count))
   {
-    mImpl->mLoadingFrame = 0;
-    WebPAnimDecoderReset( mImpl->mWebPAnimDecoder );
+    return false;
   }
+  return true;
+}
 
-  for( ; mImpl->mLoadingFrame < frameStartIndex ; ++mImpl->mLoadingFrame )
+Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
+{
+  Dali::Devel::PixelBuffer pixelBuffer;
+
+  // If WebP file is still not loaded, Load the information.
+  if(!mImpl->mLoadSucceeded)
   {
-    uint8_t* frameBuffer;
-    int timestamp;
-    WebPAnimDecoderGetNext( mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp );
-    mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
+    if(!mImpl->LoadWebPInformation())
+    {
+      return pixelBuffer;
+    }
   }
 
-  for( int i = 0; i < count; ++i )
+  // 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
+  if(mImpl->mFrameCount == 1)
   {
-    const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof( uint32_t );
-    uint8_t* frameBuffer;
-    int timestamp;
-    WebPAnimDecoderGetNext( mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp );
+    int32_t width, height;
+    if(!WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height))
+    {
+      return pixelBuffer;
+    }
 
-    auto pixelBuffer = new uint8_t[ bufferSize ];
-    memcpy( pixelBuffer, frameBuffer, bufferSize );
-    mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
+    WebPBitstreamFeatures features;
+    if(VP8_STATUS_NOT_ENOUGH_DATA == WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features))
+    {
+      return pixelBuffer;
+    }
 
-    if( pixelBuffer )
+    uint32_t      channelNumber = (features.has_alpha) ? 4 : 3;
+    Pixel::Format pixelFormat   = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
+    pixelBuffer                 = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
+    uint8_t* frameBuffer        = nullptr;
+    if(channelNumber == 4)
     {
-      pixelData.push_back( Dali::PixelData::New( pixelBuffer, bufferSize,
-                                                 mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height,
-                                                 Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY) );
+      frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
+    }
+    else
+    {
+      frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
     }
 
-    mImpl->mLoadingFrame++;
-    if( mImpl->mLoadingFrame >= mImpl->mWebPAnimInfo.frame_count )
+    if(frameBuffer != nullptr)
     {
-      mImpl->mLoadingFrame = 0;
-      WebPAnimDecoderReset( mImpl->mWebPAnimDecoder );
+      const int32_t imageBufferSize = width * height * sizeof(uint8_t) * channelNumber;
+      memcpy(pixelBuffer.GetBuffer(), frameBuffer, imageBufferSize);
+      free((void*)frameBuffer);
     }
+    mImpl->ReleaseResource();
+
+    return pixelBuffer;
+  }
+#endif
+
+#ifdef DALI_ANIMATED_WEBP_ENABLED
+  Mutex::ScopedLock lock(mImpl->mMutex);
+  if(frameIndex >= mImpl->mWebPAnimInfo.frame_count || !mImpl->mLoadSucceeded)
+  {
+    return pixelBuffer;
+  }
+
+  DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", 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->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
   }
 
-  return true;
-#else
-  return false;
 #endif
+  return pixelBuffer;
 }
 
-ImageDimensions WebPLoading::GetImageSize() const
+Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
 {
-#ifdef DALI_WEBP_ENABLED
-  return ImageDimensions( mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height );
-#else
-  return ImageDimensions();
+  Dali::Devel::PixelBuffer pixelBuffer;
+#ifdef DALI_ANIMATED_WEBP_ENABLED
+  if(mImpl->mLatestLoadedFrame >= static_cast<int32_t>(frameIndex))
+  {
+    mImpl->mLatestLoadedFrame = INITIAL_INDEX;
+    WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
+  }
+
+  int32_t timestamp;
+  uint8_t* frameBuffer = nullptr;
+  for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
+  {
+    WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
+    mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
+  }
+
+  if(frameBuffer != nullptr)
+  {
+    const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
+    pixelBuffer          = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
+    memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
+  }
+
 #endif
+  return pixelBuffer;
+}
+
+ImageDimensions WebPLoading::GetImageSize() const
+{
+  if(!mImpl->mLoadSucceeded)
+  {
+    DALI_LOG_ERROR("WebP file is still not loaded, this image size could not be correct value.\n");
+  }
+  return mImpl->mImageSize;
 }
 
 uint32_t WebPLoading::GetImageCount() const
 {
-#ifdef DALI_WEBP_ENABLED
-  return mImpl->mWebPAnimInfo.frame_count;
-#else
-  return 0u;
-#endif
+  if(!mImpl->mLoadSucceeded)
+  {
+    DALI_LOG_ERROR("WebP file is still not loaded, this image count could not be correct value.\n");
+  }
+  return mImpl->mFrameCount;
 }
 
-uint32_t WebPLoading::GetFrameInterval( uint32_t frameIndex ) const
+uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
 {
-  if( frameIndex >= GetImageCount() )
+  if(!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
     {
-      return mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
+      interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
     }
-    return mImpl->mTimeStamp[frameIndex];
+
+    if(interval < 0)
+    {
+      return 0u;
+    }
+    return static_cast<uint32_t>(interval);
   }
 }
 
+std::string WebPLoading::GetUrl() const
+{
+  return mImpl->mUrl;
+}
+
+bool WebPLoading::HasLoadingSucceeded() const
+{
+  return mImpl->mLoadSucceeded;
+}
+
 } // namespace Adaptor
 
 } // namespace Internal