/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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 <dali/internal/imaging/common/webp-loading.h>
// EXTERNAL INCLUDES
+#ifdef DALI_WEBP_AVAILABLE
#include <webp/decode.h>
#include <webp/demux.h>
+
+#if WEBP_DEMUX_ABI_VERSION > 0x0101
+#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)
{
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- if( ReadWebPInformation( 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()))
{
+#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
+ mLoadSucceeded = true;
+ }
+ 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()
{
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- 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(DALI_UNLIKELY(fp == nullptr))
{
return false;
}
- if( fseek( fp, 0, SEEK_END ) <= -1 )
+ if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
{
return false;
}
- mWebPData.size = ftell( fp );
- if( ( ! fseek( fp, 0, SEEK_SET ) ) )
+ mBufferSize = ftell(fp);
+ if(DALI_LIKELY(!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(DALI_LIKELY(succeeded))
{
- size_t blobSize = dataBuffer.Size();
- if( blobSize > 0U )
+ mBufferSize = dataBuffer.Size();
+ if(DALI_LIKELY(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(DALI_LIKELY(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(DALI_LIKELY(!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()
{
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- 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;
+ }
}
- std::string mUrl;
- std::vector<uint32_t> mTimeStamp;
- uint32_t mLoadingFrame;
+ // Moveable but not copyable
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- WebPData mWebPData;
- WebPAnimDecoder* mWebPAnimDecoder;
- WebPAnimInfo mWebPAnimInfo;
+ Impl(const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+ Impl(Impl&&) = default;
+ Impl& operator=(Impl&&) = default;
+
+ ~Impl()
+ {
+ ReleaseResource();
+ }
+
+ 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)
{
-#if WEBP_DEMUX_ABI_VERSION <= 0x0101
- DALI_LOG_ERROR( "The system do 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))
{
}
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)
{
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- 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(DALI_UNLIKELY(!pixelBuffer))
+ {
+ return false;
+ }
- if( mImpl->mLoadingFrame > frameStartIndex )
+ Dali::PixelData imageData = Devel::PixelBuffer::Convert(pixelBuffer);
+ pixelData.push_back(imageData);
+ }
+ if(DALI_UNLIKELY(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(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
{
- uint8_t* frameBuffer;
- int timestamp;
- WebPAnimDecoderGetNext( mImpl->mWebPAnimDecoder, &frameBuffer, ×tamp );
- mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
+ if(DALI_UNLIKELY(!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, ×tamp );
+ int32_t width, height;
+ if(DALI_UNLIKELY(!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(DALI_UNLIKELY(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(DALI_UNLIKELY(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)
{
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- 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);
+ }
+
+ uint8_t* frameBuffer;
+ int32_t timestamp;
+ for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
+ {
+ WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, ×tamp);
+ mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
+ }
+ 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(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+ {
+ mImpl->LoadWebPInformation();
+ }
+ return mImpl->mImageSize;
}
uint32_t WebPLoading::GetImageCount() const
{
-#if WEBP_DEMUX_ABI_VERSION > 0x0101
- return mImpl->mWebPAnimInfo.frame_count;
-#else
- return 0u;
-#endif
+ if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
+ {
+ mImpl->LoadWebPInformation();
+ }
+ return mImpl->mFrameCount;
}
-uint32_t WebPLoading::GetFrameInterval( uint32_t frameIndex ) const
+uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
{
- if( frameIndex >= GetImageCount() )
+ 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);
}
}
+std::string WebPLoading::GetUrl() const
+{
+ return mImpl->mUrl;
+}
+
+bool WebPLoading::HasLoadingSucceeded() const
+{
+ return mImpl->mLoadSucceeded;
+}
+
} // namespace Adaptor
} // namespace Internal
-} // namespace Dali
\ No newline at end of file
+} // namespace Dali