From 0bd05f2a6d1ad20fc7b92065d7b7a1324ebf0113 Mon Sep 17 00:00:00 2001 From: seungho Date: Wed, 24 Nov 2021 13:46:35 +0900 Subject: [PATCH] Refactoring Animated image loading - 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 --- dali/internal/imaging/common/gif-loading.cpp | 55 +++++-- dali/internal/imaging/common/webp-loading.cpp | 150 +++++++++++++----- dali/internal/imaging/common/webp-loading.h | 12 +- 3 files changed, 162 insertions(+), 55 deletions(-) diff --git a/dali/internal/imaging/common/gif-loading.cpp b/dali/internal/imaging/common/gif-loading.cpp index 685f206ee..e72e60031 100644 --- a/dali/internal/imaging/common/gif-loading.cpp +++ b/dali/internal/imaging/common/gif-loading.cpp @@ -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 diff --git a/dali/internal/imaging/common/webp-loading.cpp b/dali/internal/imaging/common/webp-loading.cpp index cd829d723..308bd9e79 100644 --- a/dali/internal/imaging/common/webp-loading.cpp +++ b/dali/internal/imaging/common/webp-loading.cpp @@ -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 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(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(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(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(frameIndex);) { - uint8_t* frameBuffer; - int timestamp; WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, ×tamp); - 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, ×tamp); - 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(interval); } } diff --git a/dali/internal/imaging/common/webp-loading.h b/dali/internal/imaging/common/webp-loading.h index ecf8bbb6e..f3428e6da 100644 --- a/dali/internal/imaging/common/webp-loading.h +++ b/dali/internal/imaging/common/webp-loading.h @@ -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; -- 2.34.1