Image Scaling and filtering - Core API & Cache 26/31726/7
authorAndrew Cox <andrew.cox@partner.samsung.com>
Thu, 4 Dec 2014 18:01:25 +0000 (18:01 +0000)
committerAndrew Cox <andrew.cox@partner.samsung.com>
Mon, 15 Dec 2014 15:32:27 +0000 (15:32 +0000)
Change-Id: Ibe28044b78a4ba7b44f69bce59eeffd3355137d6
Signed-off-by: Andrew Cox <andrew.cox@partner.samsung.com>
dali/internal/event/images/image-factory.cpp
dali/public-api/images/image-attributes.cpp
dali/public-api/images/image-attributes.h

index 8b17a27..66a1c64 100644 (file)
@@ -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 )
index 12c8374..1bbe8e5 100644 (file)
 // CLASS HEADER
 #include <dali/public-api/images/image-attributes.h>
 
-// INTERNAL INCLUDES
-#include <dali/public-api/common/constants.h>
-#include <dali/public-api/common/dali-common.h>
-#include <dali/public-api/math/math-utils.h>
+// EXTERNAL INCLUDES
+#include <cmath>
 
 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;
index 3563be1..d2b3a29 100644 (file)
@@ -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.