From 546811f60e6682dff9cbf11cacf41481de765b9e Mon Sep 17 00:00:00 2001 From: Andrew Cox Date: Thu, 4 Dec 2014 18:01:25 +0000 Subject: [PATCH] Image Scaling and filtering - Core API & Cache Change-Id: Ibe28044b78a4ba7b44f69bce59eeffd3355137d6 Signed-off-by: Andrew Cox --- dali/internal/event/images/image-factory.cpp | 54 ++++++++++++------------- dali/public-api/images/image-attributes.cpp | 60 ++++++++++++++++++---------- dali/public-api/images/image-attributes.h | 46 +++++++++++++++++++++ 3 files changed, 110 insertions(+), 50 deletions(-) diff --git a/dali/internal/event/images/image-factory.cpp b/dali/internal/event/images/image-factory.cpp index 8b17a27..66a1c64 100644 --- a/dali/internal/event/images/image-factory.cpp +++ b/dali/internal/event/images/image-factory.cpp @@ -44,7 +44,7 @@ namespace Internal ImageFactory::ImageFactory( ResourceClient& resourceClient ) : mResourceClient(resourceClient), - mMaxScale(0.5f), + mMaxScale( 4 / 1024.0f ), ///< Only allow a very tiny fudge factor in matching new requests to existing resource transactions: 4 pixels at a dimension of 1024, 2 at 512, ... mReqIdCurrent(0) { } @@ -78,37 +78,32 @@ ResourceTicketPtr ImageFactory::Load( Request& request ) { ResourceTicketPtr ticket; + // See if any resource transaction has already been associated with this request: const ResourceId resId = request.resourceId; - if( resId == 0 ) + if( resId != 0 ) { - // Not yet associated with a ticketed async resource transaction, so attempt to - // find a cached one and issue a new load if there isn't one in-flight: - + // An IO operation has been started at some time for the request so recover the + // ticket that was created for that: + ticket = mResourceClient.RequestResourceTicket( resId ); ///@note Always succeeds in normal use. + } + else + { + // Request not yet associated with a ticketed async resource transaction, so + // attempt to find a compatible cached one: const std::size_t urlHash = GetHashForCachedRequest( request ); ticket = FindCompatibleResource( request.url, urlHash, request.attributes ); - - if( !ticket ) - { - // Didn't find compatible resource already being loaded - ticket = IssueLoadRequest( request.url, request.attributes ); - } - - request.resourceId = ticket->GetId(); } - else + + // Start a new resource IO transaction for the request if none is already happening: + if( !ticket ) { - ticket = mResourceClient.RequestResourceTicket( resId ); - if( !ticket ) - { - // Resource has been discarded since last load - ticket = IssueLoadRequest( request.url, request.attributes ); - request.resourceId = ticket->GetId(); - } - DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap || - ticket->GetTypePath().type->id == ResourceNativeImage || - ticket->GetTypePath().type->id == ResourceTargetImage ); + ticket = IssueLoadRequest( request.url, request.attributes ); } + request.resourceId = ticket->GetId(); + DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap || + ticket->GetTypePath().type->id == ResourceNativeImage || + ticket->GetTypePath().type->id == ResourceTargetImage ); return ticket; } @@ -255,16 +250,19 @@ void ImageFactory::FlushReleaseQueue() bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested, const Dali::ImageAttributes& actual ) const { - // do not load image resource again if there is a similar resource loaded - // eg. if size is less than 50% different of what we have + // do not load image resource again if there is a similar resource loaded: // see explanation in image.h of what is deemed compatible return (requested.GetScalingMode() == actual.GetScalingMode()) && + ( + (requested.GetFilterMode() == actual.GetFilterMode()) || + (requested.GetFilterMode() == ImageAttributes::DontCare) + ) && (requested.GetPixelFormat() == actual.GetPixelFormat()) && (requested.GetFieldBorder() == actual.GetFieldBorder()) && (fabs(actual.GetFieldRadius() - requested.GetFieldRadius()) <= FLT_EPSILON) && (requested.IsDistanceField() == actual.IsDistanceField()) && - (fabsf(requested.GetWidth() - actual.GetWidth()) < actual.GetWidth() * mMaxScale) && - (fabsf(requested.GetHeight() - actual.GetHeight()) < actual.GetHeight() * mMaxScale); + (fabsf(requested.GetWidth() - actual.GetWidth()) <= actual.GetWidth() * mMaxScale) && + (fabsf(requested.GetHeight() - actual.GetHeight()) <= actual.GetHeight() * mMaxScale); } Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr ) diff --git a/dali/public-api/images/image-attributes.cpp b/dali/public-api/images/image-attributes.cpp index 12c8374..1bbe8e5 100644 --- a/dali/public-api/images/image-attributes.cpp +++ b/dali/public-api/images/image-attributes.cpp @@ -18,10 +18,8 @@ // CLASS HEADER #include -// INTERNAL INCLUDES -#include -#include -#include +// EXTERNAL INCLUDES +#include namespace Dali { @@ -31,14 +29,15 @@ const ImageAttributes ImageAttributes::DEFAULT_ATTRIBUTES; struct ImageAttributes::ImageAttributesImpl { ImageAttributesImpl() - : width(0), + : fieldRadius(4.0f), + fieldBorder(4), + width(0), height(0), scaling(ShrinkToFit), + filtering(Box), pixelformat(Pixel::RGBA8888), mOrientationCorrection(false), - isDistanceField(false), - fieldRadius(4.0f), - fieldBorder(4) + isDistanceField(false) { } @@ -47,14 +46,15 @@ struct ImageAttributes::ImageAttributesImpl } ImageAttributesImpl(const ImageAttributesImpl& rhs) - : width( rhs.width ), + : fieldRadius( rhs.fieldRadius ), + fieldBorder( rhs.fieldBorder ), + width( rhs.width ), height( rhs.height ), scaling( rhs.scaling ), + filtering( rhs.filtering ), pixelformat( rhs.pixelformat ), mOrientationCorrection( rhs.mOrientationCorrection ), - isDistanceField( rhs.isDistanceField ), - fieldRadius( rhs.fieldRadius ), - fieldBorder( rhs.fieldBorder ) + isDistanceField( rhs.isDistanceField ) { } @@ -65,6 +65,8 @@ struct ImageAttributes::ImageAttributesImpl width = rhs.width; height = rhs.height; scaling = rhs.scaling; + filtering = rhs.filtering; + pixelformat = rhs.pixelformat; mOrientationCorrection = rhs.mOrientationCorrection; isDistanceField = rhs.isDistanceField; @@ -75,17 +77,15 @@ struct ImageAttributes::ImageAttributesImpl return *this; } - unsigned int width; ///< image width in pixels - unsigned int height; ///< image height in pixels - ScalingMode scaling; ///< scaling option, ShrinkToFit is default - Pixel::Format pixelformat; ///< pixel format, default is RGBA8888 - - bool mOrientationCorrection; ///< If true, image pixels are reordered according to orientation metadata on load. - - // For distance fields : - bool isDistanceField; ///< true, if the image is a distancefield. Default is false. float fieldRadius; ///< The minimum search radius to check for differing pixels - int fieldBorder; ///< The amount of distancefield cells to add around the data (for glow/shadow effects) + int fieldBorder : 16; ///< The amount of distancefield cells to add around the data (for glow/shadow effects) + unsigned int width : 16; ///< image width in pixels + unsigned int height : 16; ///< image height in pixels + ScalingMode scaling : 3; ///< scaling option, ShrinkToFit is default + FilterMode filtering : 3; ///< filtering option. Box is the default + Pixel::Format pixelformat : 5; ///< pixel format, default is RGBA8888 + bool mOrientationCorrection : 1; ///< If true, image pixels are reordered according to orientation metadata on load. + bool isDistanceField : 1; ///< true, if the image is a distancefield. Default is false. }; @@ -133,6 +133,11 @@ void ImageAttributes::SetScalingMode(ScalingMode scale) impl->scaling = scale; } +void ImageAttributes::SetFilterMode( FilterMode filtering ) +{ + impl->filtering = filtering; +} + void ImageAttributes::SetOrientationCorrection(const bool enabled) { impl->mOrientationCorrection = enabled; @@ -163,6 +168,11 @@ ImageAttributes::ScalingMode ImageAttributes::GetScalingMode() const return impl->scaling; } +ImageAttributes::FilterMode ImageAttributes::GetFilterMode() const +{ + return impl->filtering; +} + bool ImageAttributes::IsDistanceField() const { return impl->isDistanceField; @@ -253,6 +263,11 @@ bool operator<(const ImageAttributes& a, const ImageAttributes& b) return a.impl->scaling < b.impl->scaling; } + if (a.impl->filtering != b.impl->filtering) + { + return a.impl->filtering < b.impl->filtering; + } + if (a.impl->isDistanceField && b.impl->isDistanceField) { if (fabs(a.impl->fieldRadius - b.impl->fieldRadius) > Math::MACHINE_EPSILON_0) @@ -282,6 +297,7 @@ bool operator==(const ImageAttributes& a, const ImageAttributes& b) a.impl->mOrientationCorrection == b.impl->mOrientationCorrection && a.impl->pixelformat == b.impl->pixelformat && a.impl->scaling == b.impl->scaling && + a.impl->filtering == b.impl->filtering && a.impl->isDistanceField == b.impl->isDistanceField && fabs(a.impl->fieldRadius - b.impl->fieldRadius) < Math::MACHINE_EPSILON_0 && a.impl->fieldBorder == b.impl->fieldBorder; diff --git a/dali/public-api/images/image-attributes.h b/dali/public-api/images/image-attributes.h index 3563be1..d2b3a29 100644 --- a/dali/public-api/images/image-attributes.h +++ b/dali/public-api/images/image-attributes.h @@ -70,6 +70,8 @@ public: /** * @brief Scaling options, used when resizing images on load to fit desired dimensions. * + * A scaling mode controls the region of a loaded image to be mapped to the + * desired image rectangle specified using ImageAttributes.SetSize(). * All scaling modes preserve the aspect ratio of the image contents. */ enum ScalingMode @@ -80,6 +82,35 @@ public: FitHeight ///< Image fills whole height. Width is scaled proportionately to maintain aspect ratio. }; + /** + * @brief Filtering options, used when resizing images on load to sample original pixels. + * + * A FilterMode controls how pixels in the raw image on-disk are sampled and + * combined to generate each pixel of the destination loaded image. + * + * @note NoFilter and Box modes do not guarantee that the loaded pixel array + * exactly matches the rectangle specified by the desired dimensions and + * ScalingMode, but all other filter modes do if the desired dimensions are + * `<=` the raw dimensions of the image file. + */ + enum FilterMode + { + Box, ///< Iteratively box filter to generate an image of 1/2, 1/4, 1/8, ... width and height and + /// approximately the desired size, then if the ScaleToFill scaling mode is enabled, cut away the + /// top/bottom or left/right borders of the image to match the aspect ratio of desired dimensions. + /// This is the default. + Nearest, ///< For each output pixel, read one input pixel. + Linear, ///< For each output pixel, read a quad of four input pixels and write a weighted average of them. + BoxThenNearest, ///< Iteratively box filter to generate an image of 1/2, 1/4, 1/8, ... width and height and + /// approximately the desired size, then for each output pixel, read one pixel from the last level + /// of box filtering. + BoxThenLinear, ///< Iteratively box filter to almost the right size, then for each output pixel, read four pixels + /// from the last level of box filtering and write their weighted average. + NoFilter, ///< No filtering is performed. If the ScaleToFill scaling mode is enabled, the borders of the + /// image may be trimmed to match the aspect ratio of the desired dimensions. + DontCare ///< For when the client strongly prefers a cache-hit. Defaults to Box. + }; + static const ImageAttributes DEFAULT_ATTRIBUTES; ///< Default attributes have no size /** @@ -181,6 +212,13 @@ public: void SetScalingMode(ScalingMode scalingMode); /** + * @brief Setter for the FilterMode. + * By default, Box is set. + * @param [in] filterMode The desired filter mode. + */ + void SetFilterMode( FilterMode filterMode ); + + /** * @brief Set whether the image will be rotated/flipped back into portrait orientation. * * This will only be necessary if metadata indicates that the @@ -236,6 +274,14 @@ public: ScalingMode GetScalingMode() const; /** + * @brief Getter for the FilterMode + * + * @return The FilterMode previously set, or the default value if none has + * been. + */ + FilterMode GetFilterMode() const; + + /** * @brief Return if the attribute set up as a distance field. * * @return true, if the attribute is a distance field. -- 2.7.4