Image scaling operations - FilterModes Nearest and BoxThenNearest 71/33371/11
authorAndrew Cox <andrew.cox@partner.samsung.com>
Thu, 8 Jan 2015 16:05:09 +0000 (16:05 +0000)
committerAndrew Cox <andrew.cox@partner.samsung.com>
Mon, 9 Mar 2015 14:45:14 +0000 (14:45 +0000)
Change-Id: I443a0b6da56fb7c63ccf9264cbccac5433900420
Signed-off-by: Andrew Cox <andrew.cox@partner.samsung.com>
automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp
platform-abstractions/portable/image-operations.cpp
platform-abstractions/portable/image-operations.h

index 7127312..9142fa7 100644 (file)
  */
 
 #include <dali-test-suite-utils.h>
-
 #include "platform-abstractions/portable/image-operations.h"
+#include <dali/public-api/common/ref-counted-dali-vector.h>
+
+#include <sys/mman.h>
 
 using namespace Dali::Internal::Platform;
 
@@ -344,16 +346,22 @@ int UtcDaliImageOperationsAveragePixelRGB565(void)
 /**
  * @brief Build a square bitmap, downscale it and assert the resulting bitmap has the right dimensions.
  */
-void TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::Format format, uint32_t sourceDimension, uint32_t targetDimension, uint32_t expectedDimension, const char * const location )
+void TestDownscaledBitmapHasRightDimensionsAndFormat(
+    Pixel::Format format,
+    uint32_t sourceDimension,
+    uint32_t targetDimension,
+    uint32_t expectedDimension,
+    const char * const location )
 {
   ImageAttributes attributes;
   attributes.SetScalingMode( ImageAttributes::ShrinkToFit );
+  attributes.SetFilterMode( ImageAttributes::Box );
   attributes.SetSize( targetDimension, targetDimension );
 
   Integration::BitmapPtr sourceBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
   sourceBitmap->GetPackedPixelsProfile()->ReserveBuffer( format, sourceDimension, sourceDimension, sourceDimension, sourceDimension );
 
-  Integration::BitmapPtr downScaled = DownscaleBitmap( *sourceBitmap, attributes );
+  Integration::BitmapPtr downScaled = DownscaleBitmap( *sourceBitmap, ImageDimensions( targetDimension, targetDimension ), attributes.GetScalingMode(), attributes.GetFilterMode() );
   DALI_TEST_EQUALS( downScaled->GetImageWidth(), expectedDimension, location );
   DALI_TEST_EQUALS( downScaled->GetImageHeight(), expectedDimension, location );
   DALI_TEST_EQUALS( downScaled->GetPixelFormat(), format, location );
@@ -1080,3 +1088,386 @@ int UtcDaliImageOperationsAverageScanlinesRGB565(void)
 
   END_TEST;
 }
+
+namespace
+{
+
+void MakeSingleColorImageRGBA8888( unsigned int width, unsigned int height, uint32_t *inputImage )
+{
+  const uint32_t inPixel = PixelRGBA8888( 255, 192, 128, 64 );
+  for( unsigned int i = 0; i < width * height; ++i )
+  {
+    inputImage[i] = inPixel;
+  }
+}
+
+/**
+ * @brief Allocate an image buffer with protected pages to top and tail it and
+ * SEGV if an operation strays into them.
+ */
+void MakeGuardedOutputImageRGBA8888( unsigned int desiredWidth,  unsigned int desiredHeight, uint32_t *& outputBuffer, uint32_t *& outputImage )
+{
+  const size_t outputBufferSize = getpagesize() + sizeof(uint32_t) * desiredWidth * desiredHeight + getpagesize();
+  outputBuffer = (uint32_t *) valloc( outputBufferSize );
+  mprotect( outputBuffer, getpagesize(), PROT_READ );
+  mprotect( ((char*) outputBuffer) + outputBufferSize - getpagesize(), getpagesize(), PROT_READ );
+  outputImage = outputBuffer + getpagesize() / sizeof(outputBuffer[0]);
+}
+
+/**
+ * @brief Allocate a buffer of pages that are read-only, that is big enough for the number of pixels passed-in.
+ */
+uint32_t* AllocateReadOnlyPagesRGBA( unsigned int numPixels )
+{
+  const unsigned int numWholePages = (numPixels * sizeof(uint32_t)) / getpagesize();
+  bool needExtraPage = (numPixels * sizeof(uint32_t)) % getpagesize() != 0;
+  const size_t outputBufferSize = (numWholePages + (needExtraPage ? 1 : 0)) * getpagesize();
+  uint32_t * outputBuffer = (uint32_t *) valloc( outputBufferSize );
+  mprotect( outputBuffer, outputBufferSize, PROT_READ );
+
+  return outputBuffer;
+}
+
+/**
+ * @brief Free a buffer of pages that are read-only.
+ */
+void FreeReadOnlyPagesRGBA( uint32_t * pages, unsigned int numPixels )
+{
+  const size_t bufferSize = numPixels * 4;
+  mprotect( pages, bufferSize, PROT_READ | PROT_WRITE );
+  free( pages );
+}
+
+/*
+ * @brief Make an image with a checkerboard pattern.
+ * @note This is an easy pattern to scan for correctness after a downscaling test.
+ */
+Dali::IntrusivePtr<Dali::RefCountedVector<uint32_t> > MakeCheckerboardImageRGBA8888( unsigned int width,  unsigned int height, unsigned int checkerSize )
+{
+  const unsigned int imageWidth = width * checkerSize;
+  const unsigned int imageHeight = height * checkerSize;
+  Dali::IntrusivePtr<Dali::RefCountedVector<uint32_t> > image = new Dali::RefCountedVector<uint32_t>;
+  image->GetVector().Resize( imageWidth * imageHeight );
+
+  uint32_t rowColor = 0xffffffff;
+  for( unsigned int cy = 0; cy < height; ++cy )
+  {
+    rowColor = rowColor == 0xffffffff ? 0xff000000 : 0xffffffff;
+    uint32_t checkColor = rowColor;
+    for( unsigned int cx = 0; cx < width; ++cx )
+    {
+      checkColor = checkColor == 0xffffffff ? 0xff000000 : 0xffffffff;
+      uint32_t paintedColor = checkColor;
+      // Draw 3 special case checks as r,g,b:
+      if(cx == 0 && cy == 0)
+      {
+        paintedColor = 0xff0000ff;// Red
+      }
+      else if(cx == 7 && cy == 0)
+      {
+        paintedColor = 0xff00ff00;// Green
+      }
+      else if(cx == 7 && cy == 7)
+      {
+        paintedColor = 0xffff0000;// blue
+      }
+      uint32_t * check = &image->GetVector()[ (cy * checkerSize * imageWidth) + (cx * checkerSize)];
+      for( unsigned int py = 0; py < checkerSize; ++py )
+      {
+        uint32_t * checkLine = check +  py * imageWidth;
+        for( unsigned int px = 0; px < checkerSize; ++px )
+        {
+          checkLine[px] = paintedColor;
+        }
+      }
+    }
+  }
+
+  return image;
+}
+
+}
+
+/**
+ * @brief Test that a scaling doesn't stray outside the bounds of the destination image.
+ *
+ * The test allocates a destination buffer that is an exact multiple of the page size
+ * with guard pages at either end.
+ */
+int UtcDaliImageOperationsPointSampleRGBA888InBounds(void)
+{
+  const unsigned int inputWidth = 163;
+  const unsigned int inputHeight = 691;
+  const unsigned int destinationBufferSize = 4096 * 4;
+  const unsigned int desiredWidth = 64;
+  const unsigned int desiredHeight = destinationBufferSize / desiredWidth; // (256)
+
+  uint32_t inputImage[ inputWidth * inputHeight ];
+
+  // Allocate an output image buffer with read-only guard pages at either end:
+  // The test will segfault if it strays into the guard pages.
+  uint32_t *outputBuffer, *outputImage;
+  MakeGuardedOutputImageRGBA8888( desiredWidth, desiredHeight, outputBuffer, outputImage );
+
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, inputWidth, inputHeight, (unsigned char*) outputImage, desiredWidth, desiredHeight );
+
+  FreeReadOnlyPagesRGBA( outputBuffer, desiredWidth * desiredHeight );
+
+  //! The only real test is whether the above code SEGVs, but do a fake test so we pass if that hasn't happened:
+  DALI_TEST_EQUALS( true, true, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the right pixels are generated when downsampling a checkerboard into a small image.
+ */
+int UtcDaliImageOperationsPointSampleCheckerboardRGBA888(void)
+{
+  Dali::IntrusivePtr<Dali::RefCountedVector<uint32_t> > image = MakeCheckerboardImageRGBA8888( 8, 8, 32 );
+  const unsigned int desiredWidth = 8;
+  const unsigned int desiredHeight = 8;
+
+  uint32_t outputImage[ desiredWidth * desiredHeight ];
+
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) &image->GetVector()[0], 256, 256, (unsigned char*) outputImage, desiredWidth, desiredHeight );
+
+  DALI_TEST_EQUALS( outputImage[0], 0xff0000ff, TEST_LOCATION ); // < Red corner pixel
+  DALI_TEST_EQUALS( outputImage[7], 0xff00ff00, TEST_LOCATION ); // < Green corner pixel
+  DALI_TEST_EQUALS( outputImage[8*8-1], 0xffff0000, TEST_LOCATION ); // < Blue corner pixel
+
+  DALI_TEST_EQUALS( outputImage[1], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[2], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[3], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[4], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[5], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[6], 0xffffffff, TEST_LOCATION ); // < white pixel
+
+  // Second scanline:
+  DALI_TEST_EQUALS( outputImage[8+0], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[8+1], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[8+2], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[8+3], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[8+4], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[8+5], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[8+6], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[8+7], 0xffffffff, TEST_LOCATION ); // < white pixel
+
+  // Third scanline:
+  DALI_TEST_EQUALS( outputImage[16+0], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[16+1], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[16+2], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[16+3], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[16+4], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[16+5], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[16+6], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[16+7], 0xff000000, TEST_LOCATION ); // < black pixel
+
+  // ... could do more scanlines (there are 8)
+
+  // Sample a few more pixels:
+
+  // Diagonals:
+  DALI_TEST_EQUALS( outputImage[24+3], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[32+4], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[40+5], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[48+6], 0xffffffff, TEST_LOCATION ); // < white pixel
+  DALI_TEST_EQUALS( outputImage[24+4], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[32+3], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[40+2], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[48+1], 0xff000000, TEST_LOCATION ); // < black pixel
+  DALI_TEST_EQUALS( outputImage[56+0], 0xff000000, TEST_LOCATION ); // < black pixel
+
+  END_TEST;
+}
+
+/**
+ * @brief Test that a scaling preserves input color in destination image.
+ */
+int UtcDaliImageOperationsPointSampleRGBA888PixelsCorrectColor(void)
+{
+  const unsigned int inputWidth = 137;
+  const unsigned int inputHeight = 571;
+  const unsigned int desiredWidth = 59;
+  const unsigned int desiredHeight = 257;
+
+  uint32_t inputImage[ inputWidth * inputHeight ];
+  MakeSingleColorImageRGBA8888( inputWidth, inputHeight, inputImage );
+
+  uint32_t *outputBuffer, *outputImage;
+  MakeGuardedOutputImageRGBA8888( desiredWidth, desiredHeight, outputBuffer, outputImage );
+
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, inputWidth, inputHeight, (unsigned char*) outputImage, desiredWidth, desiredHeight );
+
+  // Check that all the output pixels are the right color:
+  const uint32_t reference = inputImage[ inputWidth * inputHeight / 2];
+  unsigned int differentColorCount = 0;
+  for( unsigned int i = 0; i < desiredWidth * desiredHeight; ++i )
+  {
+    if( outputImage[i] != reference )
+    {
+      ++differentColorCount;
+    }
+  }
+
+  FreeReadOnlyPagesRGBA( outputBuffer, desiredWidth * desiredHeight );
+
+  DALI_TEST_EQUALS( 0U, differentColorCount, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test that scaling down to a 1x1 image works.
+ */
+int UtcDaliImageOperationsPointSampleRGBA888ScaleToSinglePixel(void)
+{
+  const unsigned int desiredWidth = 1;
+  const unsigned int desiredHeight = 1;
+
+  uint32_t inputImage[ 1024 * 1024 ];
+  MakeSingleColorImageRGBA8888( 1024, 1024, inputImage );
+  uint32_t outputImage = 0;
+
+  // Try several different starting image sizes:
+
+  // 1x1 -> 1x1:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,    1,    1, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+  outputImage = 0;
+
+  // Single-pixel wide tall stripe:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,    1, 1024, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+  outputImage = 0;
+
+  // Single-pixel tall, wide strip:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1024,    1, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+  outputImage = 0;
+
+  // Square mid-size image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,  103,  103, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+  outputImage = 0;
+
+  // Wide mid-size image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,  313,  79, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+  outputImage = 0;
+
+  // Tall mid-size image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,   53,  467, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+  outputImage = 0;
+
+  // 0 x 0 input image (make sure output not written to):
+  outputImage = 0xDEADBEEF;
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,    0,    0, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+  DALI_TEST_EQUALS( outputImage, 0xDEADBEEF, TEST_LOCATION );
+  outputImage = 0;
+
+  END_TEST;
+}
+
+/**
+ * @brief Test that downsampling to 0 - area images is a NOP and does not modify the destination.
+ * (edge-case)
+ */
+int UtcDaliImageOperationsPointSampleRGBA888ScaleToZeroDims(void)
+{
+  uint32_t inputImage[ 1024 * 1024 ];
+  MakeSingleColorImageRGBA8888( 1024, 1024, inputImage );
+  uint32_t* outputImage = AllocateReadOnlyPagesRGBA(1);
+
+  // Try several different starting image sizes:
+
+  // 1x1 -> 1x1:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,    1,    1, (unsigned char*) &outputImage, 0, 0 );
+
+  // Single-pixel wide tall stripe:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,    1, 1024, (unsigned char*) &outputImage, 0, 33 );
+
+  // Single-pixel tall, wide strip:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1024,    1, (unsigned char*) &outputImage, 0, 67 );
+
+  // Square mid-size image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,  103,  103, (unsigned char*) &outputImage, 21, 0 );
+
+  // Wide mid-size image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,  313,  79, (unsigned char*) &outputImage, 99, 0 );
+
+  // Tall mid-size image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,   53,  467, (unsigned char*) &outputImage, 9999, 0 );
+
+  // 0 x 0 input image:
+  Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage,    0,    0, (unsigned char*) &outputImage, 200, 99 );
+
+  FreeReadOnlyPagesRGBA( outputImage, getpagesize() / 4 );
+
+  //! The only real test is whether the above code SEGVs, but do a fake test so we pass if that hasn't happened:
+  DALI_TEST_EQUALS( true, true, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test that a scaling doesn't stray outside the bounds of the destination image.
+ *
+ * The test allocates a destination buffer that is an exact multiple of the page size
+ * with guard pages at either end.
+ */
+int UtcDaliImageOperationsPointSampleRGB88InBounds(void)
+{
+  const unsigned int inputWidth = 163;
+  const unsigned int inputHeight = 691;
+  const unsigned int desiredWidth = 32;
+  const unsigned int desiredHeight = 128;
+  const unsigned int outputBuffersizeInWords = desiredWidth * (desiredHeight / 4) * 3;
+
+  uint8_t inputImage[ inputWidth * inputHeight ][3];
+
+  // Allocate an output image buffer with read-only guard pages at either end:
+  // The test will segfault if it strays into the guard pages.
+  uint32_t *outputBuffer, *outputImage;
+
+  MakeGuardedOutputImageRGBA8888( desiredWidth * (desiredHeight / 4), 3, outputBuffer, outputImage );
+
+  Dali::Internal::Platform::PointSample3BPP( &inputImage[0][0], inputWidth, inputHeight, (uint8_t*) outputImage, desiredWidth, desiredHeight );
+
+  FreeReadOnlyPagesRGBA( outputBuffer, outputBuffersizeInWords );
+
+  //! The only real test is whether the above code SEGVs, but do a fake test so we pass if that hasn't happened:
+  DALI_TEST_EQUALS( true, true, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the small int (x,y) tuple.
+ */
+int UtcDaliImageOperationsVector2Uint16(void)
+{
+  Vector2Uint16 vec1( 2, 3 );
+
+  DALI_TEST_EQUALS( vec1.GetWidth(), 2, TEST_LOCATION );
+  DALI_TEST_EQUALS( vec1.GetX(),     2, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( vec1.GetHeight(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( vec1.GetY(),      3, TEST_LOCATION );
+
+  Vector2Uint16 vec1Copy = vec1;
+
+  DALI_TEST_EQUALS( vec1Copy.GetWidth(), 2, TEST_LOCATION );
+  DALI_TEST_EQUALS( vec1Copy.GetX(),     2, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( vec1Copy.GetHeight(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( vec1Copy.GetY(),      3, TEST_LOCATION );
+
+  Vector2Uint16 vec2( 65535u, 65535u );
+
+  DALI_TEST_EQUALS( vec2.GetX(), 65535u, TEST_LOCATION );
+  DALI_TEST_EQUALS( vec2.GetY(), 65535u, TEST_LOCATION );
+
+  END_TEST;
+}
index 5164d79..7910c22 100644 (file)
  *
  */
 
-// INTERNAL INCLUDES
 #include "image-operations.h"
-#include <dali/integration-api/debug.h>
-#include <dali/public-api/common/ref-counted-dali-vector.h>
-#include <dali/public-api/images/image-attributes.h>
-#include <dali/integration-api/bitmap.h>
 
 // EXTERNAL INCLUDES
 #include <cstring>
+#include <stddef.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
 
 namespace Dali
 {
@@ -60,9 +59,10 @@ inline unsigned int EvenDown( const unsigned int a )
 /**
  * @brief Log bad parameters.
  */
-void ValidateScalingParameters(
-  const unsigned int inputWidth,    const unsigned int inputHeight,
-  const unsigned int desiredWidth, const unsigned int desiredHeight )
+void ValidateScalingParameters( const unsigned int inputWidth,
+                                const unsigned int inputHeight,
+                                const unsigned int desiredWidth,
+                                const unsigned int desiredHeight )
 {
   if( desiredWidth > inputWidth || desiredHeight > inputHeight )
   {
@@ -84,9 +84,7 @@ void ValidateScalingParameters(
  * @brief Do debug assertions common to all scanline halving functions.
  * @note Inline and in anon namespace so should boil away in release builds.
  */
-inline void DebugAssertScanlineParameters(
-    unsigned char * const pixels,
-    const unsigned int width )
+inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
 {
   DALI_ASSERT_DEBUG( pixels && "Null pointer." );
   DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
@@ -95,12 +93,12 @@ inline void DebugAssertScanlineParameters(
 
 /**
  * @brief Assertions on params to functions averaging pairs of scanlines.
+ * @note Inline as intended to boil away in release.
  */
-inline void DebugAssertDualScanlineParameters(
-    const unsigned char * const scanline1,
-    const unsigned char * const scanline2,
-    unsigned char* const outputScanline,
-    const size_t widthInComponents )
+inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
+                                               const uint8_t * const scanline2,
+                                               uint8_t* const outputScanline,
+                                               const size_t widthInComponents )
 {
   DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
@@ -109,67 +107,288 @@ inline void DebugAssertDualScanlineParameters(
   DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
 }
 
+/**
+ * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class.
+ *
+ * @param[in] bitmapWidth Width of image before processing.
+ * @param[in] bitmapHeight Height of image before processing.
+ * @param[in] requestedWidth Width of area to scale image into. Can be zero.
+ * @param[in] requestedHeight Height of area to scale image into. Can be zero.
+ * @return Dimensions of area to scale image into after special rules are applied.
+ * @see ImageAttributes
+ */
+ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
+{
+  // If no dimensions have been requested, default to the source ones:
+  if( requestedWidth == 0 && requestedHeight == 0 )
+  {
+    return ImageDimensions( bitmapWidth, bitmapHeight );
+  }
+
+  // If both dimensions have values requested, use them both:
+  if( requestedWidth != 0 && requestedHeight != 0 )
+  {
+    return ImageDimensions( requestedWidth, requestedHeight );
+  }
+
+  // If only one of the dimensions has been requested, calculate the other from
+  // the requested one and the source image aspect ratio:
+
+  if( requestedWidth != 0 )
+  {
+    return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
+  }
+  return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
+}
+
+/**
+ * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
+ */
+BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
+{
+  BoxDimensionTest dimensionTest;
+  dimensionTest = BoxDimensionTestEither;
+
+  switch( scalingMode )
+  {
+    // Shrink to fit attempts to make one or zero dimensions smaller than the
+    // desired dimensions and one or two dimensions exactly the same as the desired
+    // ones, so as long as one dimension is larger than the desired size, box
+    // filtering can continue even if the second dimension is smaller than the
+    // desired dimensions:
+    case ImageAttributes::ShrinkToFit:
+    {
+      dimensionTest = BoxDimensionTestEither;
+      break;
+    }
+    // Scale to fill mode keeps both dimensions at least as large as desired:
+    case ImageAttributes::ScaleToFill:
+    {
+      dimensionTest = BoxDimensionTestBoth;
+      break;
+    }
+    // Y dimension is irrelevant when downscaling in FitWidth mode:
+    case ImageAttributes::FitWidth:
+    {
+      dimensionTest = BoxDimensionTestX;
+      break;
+    }
+    // X Dimension is ignored by definition in FitHeight mode:
+    case ImageAttributes::FitHeight:
+    {
+      dimensionTest = BoxDimensionTestY;
+      break;
+    }
+  }
+
+  return dimensionTest;
+}
+
+/**
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting ShinkToFit scaling mode.
+ */
+ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
+{
+  DALI_ASSERT_DEBUG( true && " " );
+  // Scale the input by the least extreme of the two dimensions:
+  const float widthScale  = target.GetX() / float(source.GetX());
+  const float heightScale = target.GetY() / float(source.GetY());
+  const float scale = widthScale < heightScale ? widthScale : heightScale;
+
+  // Do no scaling at all if the result would increase area:
+  if( scale >= 1.0f )
+  {
+    return source;
+  }
+
+  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
+}
+
+/**
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting ScaleToFill scaling mode.
+ * @note The output dimensions will need either top and bottom or left and right
+ * to be cropped away unless the source was pre-cropped to match the destination
+ * aspect ratio.
+ */
+ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
+{
+  DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0  && "Zero-area rectangles should not be passed-in" );
+  // Scale the input by the least extreme of the two dimensions:
+  const float widthScale  = target.GetX() / float(source.GetX());
+  const float heightScale = target.GetY() / float(source.GetY());
+  const float scale = widthScale > heightScale ? widthScale : heightScale;
+
+  // Do no scaling at all if the result would increase area:
+  if( scale >= 1.0f )
+  {
+    return source;
+  }
+
+  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
+}
+
+/**
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting FitWidth scaling mode.
+ */
+ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
+{
+  DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
+  const float scale  = target.GetX() / float(source.GetX());
+
+  // Do no scaling at all if the result would increase area:
+  if( scale >= 1.0f )
+  {
+   return source;
+  }
+  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
+}
+
+/**
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting FitHeight scaling mode.
+ */
+ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
+{
+  DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
+  const float scale = target.GetY() / float(source.GetY());
+
+  // Do no scaling at all if the result would increase area:
+  if( scale >= 1.0f )
+  {
+    return source;
+  }
+
+  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
+}
+
+/**
+ * @brief Generate the rectangle to use as the target of a pixel sampling pass
+ * (e.g., nearest or linear).
+ */
+ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, ImageAttributes::ScalingMode scalingMode )
+{
+  ImageDimensions fitDimensions;
+  switch( scalingMode )
+  {
+    case ImageAttributes::ShrinkToFit:
+    {
+      fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
+      break;
+    }
+    case ImageAttributes::ScaleToFill:
+    {
+      fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
+      break;
+    }
+    case ImageAttributes::FitWidth:
+    {
+      fitDimensions = FitForFitWidth( requestedSize, sourceSize );
+      break;
+    }
+    case ImageAttributes::FitHeight:
+    {
+      fitDimensions = FitForFitHeight( requestedSize, sourceSize );
+      break;
+    }
+  };
+
+  return fitDimensions;
+}
+
+/**
+ * @brief Construct a bitmap object from a copy of the pixel array passed in.
+ */
+BitmapPtr MakeBitmap(const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
+{
+  DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
+  DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
+
+  // Allocate a pixel buffer to hold the image passed in:
+  Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
+  newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
+
+  // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
+  memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
+  return newBitmap;
+}
+
 } // namespace - unnamed
 
 /**
- * @brief Implement ImageAttributes::ScaleTofill scaling mode.
+ * @brief Implement ImageAttributes::ScaleTofill scaling mode cropping.
  *
- * Implement the ImageAttributes::ScaleToFill mode, returning a new bitmap with the aspect ratio specified by the scaling mode.
- * @note This fakes the scaling with a crop and relies on the GPU scaling at
- * render time. If the input bitmap was previously maximally downscaled using a
+ * Implement the cropping required for ImageAttributes::ScaleToFill mode,
+ * returning a new bitmap with the aspect ratio specified by the scaling mode.
+ * This scaling mode selects the central portion of a source image so any spare
+ * pixels off one of either the top or bottom edge need to be removed.
+ *
+ * @note If the input bitmap was not previously downscaled to exactly encompass
+ * the desired output size, the resulting bitmap will have the correct aspect
+ * ratio but will have larger dimensions than requested. This can be used to
+ * fake the scaling mode by relying on the GPU scaling at render time.
+ * If the input bitmap was previously maximally downscaled using a
  * repeated box filter, this is a reasonable approach.
+ *
  * @return The bitmap passed in if no scaling is needed or possible, else a new,
- * smaller bitmap with the scaling mode applied.
+ * smaller bitmap with the cropping required for the scaling mode applied.
  */
-Integration::BitmapPtr ProcessBitmapScaleToFill( Integration::BitmapPtr bitmap, const ImageAttributes& requestedAttributes );
-
+Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
 
 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
 {
-  // If a different size than the raw one has been requested, resize the image
-  // maximally using a repeated box filter without making it smaller than the
-  // requested size in either dimension:
+  ///@ToDo: Optimisation - If Scaling Mode is ScaletoFill, either do cropping here at the front of the pipe, or equivalently modify all scaling functions to take a source rectangle and have the first one to be applied, pull in a subset of source pixels to crop on the fly. That would make every scaling slightly slower but would save the memcpy()s at the end for ScaleToFill.
+
   if( bitmap )
   {
-    bitmap = DownscaleBitmap( *bitmap, requestedAttributes );
-  }
+    // Calculate the desired box, accounting for a possible zero component:
+    const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), requestedAttributes.GetWidth(), requestedAttributes.GetHeight() );
 
-  // Cut the bitmap according to the desired width and height so that the
-  // resulting bitmap has the same aspect ratio as the desired dimensions:
-  if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
-  {
-    bitmap = ProcessBitmapScaleToFill( bitmap, requestedAttributes );
-  }
+    // If a different size than the raw one has been requested, resize the image
+    // maximally using a repeated box filter without making it smaller than the
+    // requested size in either dimension:
+    bitmap = DownscaleBitmap( *bitmap, desiredDimensions, requestedAttributes.GetScalingMode(), requestedAttributes.GetFilterMode() );
 
-  // Examine the image pixels remaining after cropping and scaling to see if all
-  // are opaque, allowing faster rendering, or some have non-1.0 alpha:
-  if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
-  {
-    bitmap->GetPackedPixelsProfile()->TestForTransparency();
+    // Cut the bitmap according to the desired width and height so that the
+    // resulting bitmap has the same aspect ratio as the desired dimensions:
+    if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
+    {
+      bitmap = CropForScaleToFill( bitmap, desiredDimensions );
+    }
+
+    // Examine the image pixels remaining after cropping and scaling to see if all
+    // are opaque, allowing faster rendering, or some have non-1.0 alpha:
+    if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
+    {
+      bitmap->GetPackedPixelsProfile()->TestForTransparency();
+    }
   }
+
   return bitmap;
 }
 
-BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
+BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
 {
-  const unsigned loadedWidth = bitmap->GetImageWidth();
-  const unsigned loadedHeight = bitmap->GetImageHeight();
-  const unsigned desiredWidth = requestedAttributes.GetWidth();
-  const unsigned desiredHeight = requestedAttributes.GetHeight();
+  const unsigned inputWidth = bitmap->GetImageWidth();
+  const unsigned inputHeight = bitmap->GetImageHeight();
+  const unsigned desiredWidth = desiredDimensions.GetWidth();
+  const unsigned desiredHeight = desiredDimensions.GetHeight();
 
   if( desiredWidth < 1U || desiredHeight < 1U )
   {
     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
   }
-  else if( loadedWidth != desiredWidth || loadedHeight != desiredHeight )
+  else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
   {
     const Vector2 desiredDims( desiredWidth, desiredHeight );
 
     // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
     // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
-    const float widthsRatio = loadedWidth / float(desiredWidth);
+    const float widthsRatio = inputWidth / float(desiredWidth);
     const Vector2 scaledByWidth = desiredDims * widthsRatio;
-    const float heightsRatio = loadedHeight / float(desiredHeight);
+    const float heightsRatio = inputHeight / float(desiredHeight);
     const Vector2 scaledByHeight = desiredDims * heightsRatio;
     // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
     const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
@@ -177,16 +396,16 @@ BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& req
 
     // Work out how many pixels to trim from top and bottom, and left and right:
     // (We only ever do one dimension)
-    const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - loadedHeight) * 0.5f ) : 0;
-    const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - loadedWidth) * 0.5f );
+    const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
+    const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
 
-    DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, loadedWidth, loadedHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
+    DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, inputWidth, inputHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
 
     // Make a new bitmap with the central part of the loaded one if required:
     if( scanlinesToTrim > 0 || columnsToTrim > 0 )
     {
-      const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
-      const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
+      const unsigned newWidth = inputWidth - 2 * columnsToTrim;
+      const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
       BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
       Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
       DALI_ASSERT_DEBUG( packedView );
@@ -195,7 +414,7 @@ BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& req
 
       const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
 
-      const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
+      const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
       PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
       DALI_ASSERT_DEBUG( srcPixels && destPixels );
 
@@ -208,7 +427,7 @@ BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& req
       {
         for( unsigned y = 0; y < newHeight; ++y )
         {
-          memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * loadedWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
+          memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
         }
       }
 
@@ -220,112 +439,54 @@ BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& req
   return bitmap;
 }
 
-namespace
-{
-
-/**
- * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
- */
-BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
-{
-  BoxDimensionTest dimensionTest;
-  dimensionTest = BoxDimensionTestEither;
-
-  switch( scalingMode )
-  {
-    // Shrink to fit attempts to make one or zero dimensions smaller than the
-    // desired dimensions and one or two dimensions exactly the same as the desired
-    // ones, so as long as one dimension is larger than the desired size, box
-    // filtering can continue even if the second dimension is smaller than the
-    // desired dimensions:
-    case ImageAttributes::ShrinkToFit:
-      dimensionTest = BoxDimensionTestEither;
-      break;
-    // Scale to fill mode keeps both dimensions at least as large as desired:
-    case ImageAttributes::ScaleToFill:
-      dimensionTest = BoxDimensionTestBoth;
-      break;
-    // Y dimension is irrelevant when downscaling in FitWidth mode:
-    case ImageAttributes::FitWidth:
-      dimensionTest = BoxDimensionTestX;
-      break;
-    // X Dimension is ignored by definition in FitHeight mode:
-    case ImageAttributes::FitHeight:
-      dimensionTest = BoxDimensionTestY;
-  }
-
-  return dimensionTest;
-}
-
-}
-
-// The top-level function to return a downscaled version of a bitmap:
-Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const ImageAttributes& requestedAttributes )
+Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
+                                        ImageDimensions desired,
+                                        ImageAttributes::ScalingMode scalingMode,
+                                        ImageAttributes::FilterMode filterMode )
 {
+  // Source dimensions as loaded from resources (e.g. filesystem):
   const unsigned int bitmapWidth  = bitmap.GetImageWidth();
   const unsigned int bitmapHeight = bitmap.GetImageHeight();
-  const Size requestedSize = requestedAttributes.GetSize();
+  // Desired dimensions (the rectangle to fit the source image to):
+  const unsigned int desiredWidth = desired.GetWidth();
+  const unsigned int desiredHeight = desired.GetHeight();
+
+  BitmapPtr outputBitmap( &bitmap );
 
   // If a different size than the raw one has been requested, resize the image:
   if( bitmap.GetPackedPixelsProfile() &&
-      (requestedSize.x > 0.0f) && (requestedSize.y > 0.0f) &&
-      (requestedSize.x < bitmapWidth) &&
-      (requestedSize.y < bitmapHeight) )
+      (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
+      ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
   {
     const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
-    const ImageAttributes::ScalingMode scalingMode = requestedAttributes.GetScalingMode();
-    const ImageAttributes::FilterMode filterMode = requestedAttributes.GetFilterMode();
 
-    // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
-    if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
-    {
-      // Check the pixel format is one that is supported:
-      if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
-      {
-        unsigned int shrunkWidth = -1, shrunkHeight = -1;
-        const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
+    // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
+    unsigned int shrunkWidth = -1, shrunkHeight = -1;
+    DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, scalingMode, filterMode, shrunkWidth, shrunkHeight );
 
-        if( pixelFormat == Pixel::RGBA8888 )
-        {
-          Internal::Platform::DownscaleInPlacePow2RGBA8888( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
-        }
-        else if( pixelFormat == Pixel::RGB888 )
-        {
-          Internal::Platform::DownscaleInPlacePow2RGB888( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
-        }
-        else if( pixelFormat == Pixel::RGB565 )
-        {
-          Internal::Platform::DownscaleInPlacePow2RGB565( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
-        }
-        else if( pixelFormat == Pixel::LA88 )
-        {
-          Internal::Platform::DownscaleInPlacePow2ComponentPair( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
-        }
-        else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
-        {
-          Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
-        }
+    // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
+    const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), scalingMode );
+    const unsigned int filteredWidth = filteredDimensions.GetWidth();
+    const unsigned int filteredHeight = filteredDimensions.GetHeight();
 
-        if( shrunkWidth != bitmapWidth && shrunkHeight != bitmapHeight )
-        {
-          // Allocate a pixel buffer to hold the shrunk image:
-          Integration::BitmapPtr shrunk = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
-          shrunk->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, shrunkWidth, shrunkHeight, shrunkWidth, shrunkHeight );
-
-          // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
-          DALI_ASSERT_DEBUG( bitmap.GetBuffer() && "Null loaded bitmap buffer." );
-          DALI_ASSERT_DEBUG( shrunk->GetBuffer() && "Null shrunk bitmap buffer." );
-          memcpy( shrunk->GetBuffer(), bitmap.GetBuffer(), shrunkWidth * shrunkHeight * Pixel::GetBytesPerPixel( pixelFormat ) );
-          return shrunk;
-        }
-      }
-      else
-      {
-        DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
-      }
+    // Run a filter to scale down the bitmap if it needs it:
+    if( (filteredWidth < shrunkWidth || filteredHeight < shrunkHeight) &&
+        (filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::Linear ||
+         filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear) )
+    {
+      ///@note If a linear filter is requested, we do our best with a point filter for now.
+      PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, bitmap.GetBuffer(), filteredWidth, filteredHeight );
+
+      outputBitmap =  MakeBitmap( bitmap.GetBuffer(), pixelFormat, filteredWidth, filteredHeight );
+    }
+    // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
+    else if( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight )
+    {
+      outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
     }
   }
-  return Integration::BitmapPtr(&bitmap);
+
+  return outputBitmap;
 }
 
 namespace
@@ -349,16 +510,25 @@ bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned
     switch( test )
     {
       case BoxDimensionTestEither:
+      {
         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
         break;
+      }
       case BoxDimensionTestBoth:
+      {
         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
         break;
+      }
       case BoxDimensionTestX:
+      {
         keepScaling = nextWidth >= desiredWidth;
         break;
+      }
       case BoxDimensionTestY:
+      {
         keepScaling = nextHeight >= desiredHeight;
+        break;
+      }
     }
   }
 
@@ -366,7 +536,8 @@ bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned
 }
 
 /**
- * @brief A shared implementation of the overall iterative downscaling algorithm.
+ * @brief A shared implementation of the overall iterative box filter
+ * downscaling algorithm.
  *
  * Specialise this for particular pixel formats by supplying the number of bytes
  * per pixel and two functions: one for averaging pairs of neighbouring pixels
@@ -378,12 +549,14 @@ template<
   void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
   void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
 >
-void DownscaleInPlacePow2Generic(
-    unsigned char * const pixels,
-    const unsigned int inputWidth, const unsigned int inputHeight,
-    const unsigned int desiredWidth, const unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned& outWidth, unsigned& outHeight )
+void DownscaleInPlacePow2Generic( unsigned char * const pixels,
+                                  const unsigned int inputWidth,
+                                  const unsigned int inputHeight,
+                                  const unsigned int desiredWidth,
+                                  const unsigned int desiredHeight,
+                                  BoxDimensionTest dimensionTest,
+                                  unsigned& outWidth,
+                                  unsigned& outHeight )
 {
   if( pixels == 0 )
   {
@@ -395,8 +568,6 @@ void DownscaleInPlacePow2Generic(
   // resulting height or width would be less than 1:
   unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
   while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
-      //scaledWidth >> 1u >= desiredWidth && scaledHeight >> 1u >= desiredHeight &&
-      //    scaledWidth >> 1u >= 1u           && scaledHeight >> 1u >= 1u )
   {
     const unsigned int lastWidth = scaledWidth;
     scaledWidth  >>= 1u;
@@ -416,7 +587,7 @@ void DownscaleInPlacePow2Generic(
       // Scale vertical pairs of pixels while the last two scanlines are still warm in
       // the CPU cache(s):
       // Note, better access patterns for cache-coherence are possible for very large
-      // images but even a 4k RGB888 image will use just 24kB of cache (4k pixels
+      // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
       // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
       AverageScanlines(
           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
@@ -433,9 +604,7 @@ void DownscaleInPlacePow2Generic(
 
 }
 
-void HalveScanlineInPlaceRGB888(
-    unsigned char * const pixels,
-    const unsigned int width )
+void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
 {
   DebugAssertScanlineParameters( pixels, width );
 
@@ -458,9 +627,7 @@ void HalveScanlineInPlaceRGB888(
   }
 }
 
-void HalveScanlineInPlaceRGBA8888(
-    unsigned char * const pixels,
-    const unsigned int width )
+void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
 {
   DebugAssertScanlineParameters( pixels, width );
   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
@@ -492,9 +659,7 @@ void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
   }
 }
 
-void HalveScanlineInPlace2Bytes(
-    unsigned char * const pixels,
-    const unsigned int width )
+void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
 {
   DebugAssertScanlineParameters( pixels, width );
 
@@ -514,9 +679,7 @@ void HalveScanlineInPlace2Bytes(
   }
 }
 
-void HalveScanlineInPlace1Byte(
-    unsigned char * const pixels,
-    const unsigned int width )
+void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
 {
   DebugAssertScanlineParameters( pixels, width );
 
@@ -537,11 +700,10 @@ void HalveScanlineInPlace1Byte(
  * @ToDo: Optimise for ARM using a 4 bytes at a time loop wrapped around the single ARMV6 instruction: UHADD8  R4, R0, R5. Note, this is not neon. It runs in the normal integer pipeline so there is no downside like a stall moving between integer and copro, or extra power for clocking-up the idle copro.
  * if (widthInComponents >= 7) { word32* aligned1 = scanline1 + 3 & 3; word32* aligned1_end = scanline1 + widthInPixels & 3; while(aligned1 < aligned1_end) { UHADD8  *aligned1++, *aligned2++, *alignedoutput++ } .. + 0 to 3 spare pixels at each end.
  */
-void AverageScanlines1(
-    const unsigned char * const scanline1,
-    const unsigned char * const __restrict__ scanline2,
-    unsigned char* const outputScanline,
-    const unsigned int width )
+void AverageScanlines1( const unsigned char * const scanline1,
+                        const unsigned char * const __restrict__ scanline2,
+                        unsigned char* const outputScanline,
+                        const unsigned int width )
 {
   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
 
@@ -551,11 +713,10 @@ void AverageScanlines1(
   }
 }
 
-void AverageScanlines2(
-    const unsigned char * const scanline1,
-    const unsigned char * const __restrict__ scanline2,
-    unsigned char* const outputScanline,
-    const unsigned int width )
+void AverageScanlines2( const unsigned char * const scanline1,
+                        const unsigned char * const __restrict__ scanline2,
+                        unsigned char* const outputScanline,
+                        const unsigned int width )
 {
   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
 
@@ -565,11 +726,10 @@ void AverageScanlines2(
   }
 }
 
-void AverageScanlines3(
-    const unsigned char * const scanline1,
-    const unsigned char * const __restrict__ scanline2,
-    unsigned char* const outputScanline,
-    const unsigned int width )
+void AverageScanlines3( const unsigned char * const scanline1,
+                        const unsigned char * const __restrict__ scanline2,
+                        unsigned char* const outputScanline,
+                        const unsigned int width )
 {
   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
 
@@ -579,11 +739,10 @@ void AverageScanlines3(
   }
 }
 
-void AverageScanlinesRGBA8888(
-    const unsigned char * const scanline1,
-    const unsigned char * const __restrict__ scanline2,
-    unsigned char * const outputScanline,
-    const unsigned int width )
+void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
+                               const unsigned char * const __restrict__ scanline2,
+                               unsigned char * const outputScanline,
+                               const unsigned int width )
 {
   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
@@ -600,11 +759,10 @@ void AverageScanlinesRGBA8888(
   }
 }
 
-void AverageScanlinesRGB565(
-    const unsigned char * const scanline1,
-    const unsigned char * const __restrict__ scanline2,
-    unsigned char * const outputScanline,
-    const unsigned int width )
+void AverageScanlinesRGB565( const unsigned char * const scanline1,
+                             const unsigned char * const __restrict__ scanline2,
+                             unsigned char * const outputScanline,
+                             const unsigned int width )
 {
   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
@@ -621,33 +779,89 @@ void AverageScanlinesRGB565(
   }
 }
 
-void DownscaleInPlacePow2RGB888(
-    unsigned char * const pixels,
-    const unsigned int inputWidth, const unsigned int inputHeight,
-    const unsigned int desiredWidth, const unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned& outWidth, unsigned& outHeight )
+/// Dispatch to pixel format appropriate box filter downscaling functions.
+void DownscaleInPlacePow2( unsigned char * const pixels,
+                           Pixel::Format pixelFormat,
+                           unsigned int inputWidth,
+                           unsigned int inputHeight,
+                           unsigned int desiredWidth,
+                           unsigned int desiredHeight,
+                           ImageAttributes::ScalingMode scalingMode,
+                           ImageAttributes::FilterMode filterMode,
+                           unsigned& outWidth,
+                           unsigned& outHeight )
+{
+  outWidth = inputWidth;
+  outHeight = inputHeight;
+  // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
+  if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
+  {
+    // Check the pixel format is one that is supported:
+    if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+    {
+      const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
+
+      if( pixelFormat == Pixel::RGBA8888 )
+      {
+        Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+      }
+      else if( pixelFormat == Pixel::RGB888 )
+      {
+        Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+      }
+      else if( pixelFormat == Pixel::RGB565 )
+      {
+        Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+      }
+      else if( pixelFormat == Pixel::LA88 )
+      {
+        Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+      }
+      else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
+      {
+        Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+      }
+    }
+  }
+  else
+  {
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+  }
+}
+
+void DownscaleInPlacePow2RGB888( unsigned char * const pixels,
+                                 const unsigned int inputWidth,
+                                 const unsigned int inputHeight,
+                                 const unsigned int desiredWidth,
+                                 const unsigned int desiredHeight,
+                                 BoxDimensionTest dimensionTest,
+                                 unsigned& outWidth,
+                                 unsigned& outHeight )
 {
   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
 }
 
-void DownscaleInPlacePow2RGBA8888(
-    unsigned char * const pixels,
-    const unsigned int inputWidth, const unsigned int inputHeight,
-    const unsigned int desiredWidth, const unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned& outWidth, unsigned& outHeight )
+void DownscaleInPlacePow2RGBA8888( unsigned char * const pixels,
+                                   const unsigned int inputWidth,
+                                   const unsigned int inputHeight,
+                                   const unsigned int desiredWidth,
+                                   const unsigned int desiredHeight,
+                                   BoxDimensionTest dimensionTest,
+                                   unsigned& outWidth,
+                                   unsigned& outHeight )
 {
   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
   DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
 }
 
-void DownscaleInPlacePow2RGB565(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight )
+void DownscaleInPlacePow2RGB565( unsigned char * pixels,
+                                 unsigned int inputWidth,
+                                 unsigned int inputHeight,
+                                 unsigned int desiredWidth,
+                                 unsigned int desiredHeight,
+                                 BoxDimensionTest dimensionTest,
+                                 unsigned int& outWidth,
+                                 unsigned int& outHeight )
 {
   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
 }
@@ -657,26 +871,214 @@ void DownscaleInPlacePow2RGB565(
  *
  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
  */
-void DownscaleInPlacePow2ComponentPair(
-    unsigned char * const pixels,
-    const unsigned int inputWidth, const unsigned int inputHeight,
-    const unsigned int desiredWidth, const unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned& outWidth, unsigned& outHeight )
+void DownscaleInPlacePow2ComponentPair( unsigned char * const pixels,
+                                        const unsigned int inputWidth,
+                                        const unsigned int inputHeight,
+                                        const unsigned int desiredWidth,
+                                        const unsigned int desiredHeight,
+                                        BoxDimensionTest dimensionTest,
+                                        unsigned& outWidth,
+                                        unsigned& outHeight )
 {
   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
 }
 
-void DownscaleInPlacePow2SingleBytePerPixel(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight )
+void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
+                                             unsigned int inputWidth,
+                                             unsigned int inputHeight,
+                                             unsigned int desiredWidth,
+                                             unsigned int desiredHeight,
+                                             BoxDimensionTest dimensionTest,
+                                             unsigned int& outWidth,
+                                             unsigned int& outHeight )
 {
   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
 }
 
+namespace
+{
+
+// Point sample an image to a new resolution (like GL_NEAREST).
+template<typename PIXEL>
+void PointSampleAddressablePixels( const uint8_t * inPixels,
+                                   unsigned int inputWidth,
+                                   unsigned int inputHeight,
+                                   uint8_t * outPixels,
+                                   unsigned int desiredWidth,
+                                   unsigned int desiredHeight )
+{
+  DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
+      outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
+      "The input and output buffers must not overlap for an upscaling.");
+  DALI_ASSERT_DEBUG( ((uint64_t) inPixels)  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+  DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+
+  if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
+  {
+    return;
+  }
+  const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
+  PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
+  const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
+  const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
+
+  unsigned int inY = 0;
+  for( unsigned int outY = 0; outY < desiredHeight; ++outY )
+  {
+    // Round fixed point y coordinate to nearest integer:
+    const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
+    const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
+    PIXEL* const outScanline = &outAligned[desiredWidth * outY];
+
+    DALI_ASSERT_DEBUG( integerY < inputHeight );
+    DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
+    DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
+
+    unsigned int inX = 0;
+    for( unsigned int outX = 0; outX < desiredWidth; ++outX )
+    {
+      // Round the fixed-point x coordinate to an integer:
+      const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
+      const PIXEL* const inPixelAddress = &inScanline[integerX];
+      const PIXEL pixel = *inPixelAddress;
+      outScanline[outX] = pixel;
+      inX += deltaX;
+    }
+    inY += deltaY;
+  }
+}
+
+}
+
+/*
+ * RGBA8888
+ */
+void PointSample4BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight )
+{
+  PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+}
+
+/*
+ * RGB565, LA88
+ */
+void PointSample2BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight )
+{
+  PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+}
+
+/*
+ * L8, A8
+ */
+void PointSample1BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight )
+{
+  PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+}
+
+/*
+ * RGB888
+ * RGB888 is a special case as its pixels are not aligned addressable units.
+ */
+void PointSample3BPP( const uint8_t * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      uint8_t * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight )
+{
+  if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
+  {
+    return;
+  }
+  const unsigned int BYTES_PER_PIXEL = 3;
+
+  // Generate fixed-point 16.16 deltas in input image coordinates:
+  const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
+  const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
+
+  // Step through output image in whole integer pixel steps while tracking the
+  // corresponding locations in the input image using 16.16 fixed-point
+  // coordinates:
+  unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
+  for( unsigned int outY = 0; outY < desiredHeight; ++outY )
+  {
+    const uint8_t* const inScanline = &inPixels[inputWidth * (inY >> 16u) * BYTES_PER_PIXEL];
+    uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
+    unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
+
+    for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
+    {
+      // Truncate the fixed-point input coordinate to the address of the input pixel to sample:
+      const uint8_t* const inPixelAddress = &inScanline[(inX >> 16u) * BYTES_PER_PIXEL];
+
+      // Issue loads for all pixel color components up-front:
+      const unsigned int c0 = inPixelAddress[0];
+      const unsigned int c1 = inPixelAddress[1];
+      const unsigned int c2 = inPixelAddress[2];
+      ///@ToDo: Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads.
+
+      // Output the pixel components:
+      outScanline[outX]     = c0;
+      outScanline[outX + 1] = c1;
+      outScanline[outX + 2] = c2;
+
+      // Increment the fixed-point input coordinate:
+      inX += deltaX;
+    }
+
+    inY += deltaY;
+  }
+}
+
+// Dispatch to a format-appropriate point sampling function:
+void PointSample( const unsigned char * inPixels,
+                  unsigned int inputWidth,
+                  unsigned int inputHeight,
+                  Pixel::Format pixelFormat,
+                  unsigned char * outPixels,
+                  unsigned int desiredWidth,
+                  unsigned int desiredHeight )
+{
+  // Check the pixel format is one that is supported:
+  if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+  {
+    if( pixelFormat == Pixel::RGBA8888 )
+    {
+      PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+    }
+    else if( pixelFormat == Pixel::RGB888 )
+    {
+      PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+    }
+    else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
+    {
+      PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+    }
+    else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
+    {
+      PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+    }
+  }
+  else
+  {
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+  }
+}
+
 } /* namespace Platform */
 } /* namespace Internal */
 } /* namespace Dali */
index de5bee7..2d26725 100644 (file)
@@ -44,19 +44,132 @@ enum BoxDimensionTest
 };
 
 /**
+ * @brief Simple class for passing around pairs of small ints.
+ *
+ * These are immutable. If you want to change a value, make a whole new object.
+ * @note One of these can be passed in a single 32 bit integer register on
+ * common architectures.
+ */
+class Vector2Uint16
+{
+public:
+  /**
+   * @brief Default constructor for the (0, 0) vector.
+   */
+  Vector2Uint16() : mData(0) {}
+
+  /**
+   * @brief Constructor taking separate x and y (width and height) parameters.
+   * @param[in] width The width or X dimension of the vector. Make sure it is less than 65536,
+   * @param[in] height The height or Y dimension of the vector. Make sure it is less than 65536,
+   */
+  Vector2Uint16( uint32_t width, uint32_t height )
+  {
+    DALI_ASSERT_DEBUG( width < ( 1u << 16 ) && "Width parameter not representable." );
+    DALI_ASSERT_DEBUG( height < ( 1u << 16 ) && "Height parameter not representable." );
+
+    /* Do equivalent of the code below with one aligned memory access:
+     * mComponents[0] = width;
+     * mComponents[1] = height;
+     * Unit tests make sure this is equivalent.
+     **/
+    mData = (height << 16u) + width;
+  }
+
+  /**
+   * @brief Copy constructor.
+   */
+  Vector2Uint16( const Vector2Uint16& rhs )
+  {
+    mData = rhs.mData;
+  }
+
+  /**
+   * @returns the x dimension stored in this 2-tuple.
+   */
+  uint16_t GetWidth() const
+  {
+    return mComponents[0];
+  }
+
+  /**
+   * @returns the y dimension stored in this 2-tuple.
+   */
+  uint16_t GetHeight() const
+  {
+    return mComponents[1];
+  }
+
+  /**
+   * @returns the x dimension stored in this 2-tuple.
+   */
+  uint16_t GetX()  const
+  {
+    return mComponents[0];
+  }
+
+  /**
+   * @returns the y dimension stored in this 2-tuple.
+   */
+  uint16_t GetY() const
+  {
+    return mComponents[1];
+  }
+
+private:
+  union
+  {
+    // Addressable view of X and Y:
+    uint16_t mComponents[2];
+    // Packed view of X and Y to force alignment and allow a faster copy:
+    uint32_t mData;
+  };
+};
+
+/**
+ * @brief Human readable text output.
+ */
+std::ostream& operator<<( std::ostream& o, const Vector2Uint16& vector );
+
+/**
+ * @brief The integer dimensions of an image or a region of an image packed into
+ *        16 bits per component.
+ * @note  This can only be used for images of up to 65535 x 65535 pixels.
+  */
+typedef Vector2Uint16 ImageDimensions;
+
+/**
+ * @defgroup BitmapOperations Bitmap-to-Bitmap Image operations.
+ * @{
+ */
+
+/**
  * @brief Apply requested attributes to bitmap.
+ *
+ * This is the top-level function which runs the on-load image post-processing
+ * pipeline. Bitmaps enter here as loaded from the file system by the file
+ * loaders and leave downscaled and filtered as requested by the application,
+ * ready for use.
+ *
  * @param[in] bitmap The input bitmap.
  * @param[in] requestedAttributes Attributes which should be applied to bitmap.
- * @return A bitmap which results from applying the requested attributes to the bitmap passed-in, or the original bitmap passed in if the attributes have no effect.
+ * @return A bitmap which results from applying the requested attributes to the
+ *         bitmap passed-in, or the original bitmap passed in if the attributes
+ *         have no effect.
  */
 Integration::BitmapPtr ApplyAttributesToBitmap( Integration::BitmapPtr bitmap, const ImageAttributes& requestedAttributes );
 
 /**
  * @brief Apply downscaling to a bitmap according to requested attributes.
- * @note Only rough power of 2 box filtering is currently performed.
- * @note The input bitmap may be modified and left in an invalid state so must be discarded.
+ * @note The input bitmap pixel buffer may be modified and used as scratch working space for efficiency, so it must be discarded.
  **/
-Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const ImageAttributes& requestedAttributes );
+Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, ImageDimensions desired, ImageAttributes::ScalingMode scalingMode, ImageAttributes::FilterMode filterMode );
+/**@}*/
+
+/**
+ * @defgroup ImageBufferScalingAlgorithms Pixel buffer-level scaling algorithms.
+ * @{
+ */
 
 /**
  * @brief Destructive in-place downscaling by a power of 2 factor.
@@ -65,6 +178,7 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const Image
  * of the next downscaling step would not be smaller than the desired
  * dimensions.
  * @param[in,out] pixels The buffer both to read from and write the result to.
+ * @param[in]     pixelFormat The format of the image pointed at by pixels.
  * @param[in]     inputWidth The width of the input image.
  * @param[in]     inputHeight The height of the input image.
  * @param[in]     desiredWidth The width the client is requesting.
@@ -72,58 +186,164 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const Image
  * @param[out]    outWidth  The resulting width after downscaling.
  * @param[out]    outHeight The resulting height after downscaling.
  */
-void DownscaleInPlacePow2RGB888(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight );
+void DownscaleInPlacePow2( unsigned char * const pixels,
+                           Pixel::Format pixelFormat,
+                           unsigned int inputWidth,
+                           unsigned int inputHeight,
+                           unsigned int desiredWidth,
+                           unsigned int desiredHeight,
+                           ImageAttributes::ScalingMode scalingMode,
+                           ImageAttributes::FilterMode filterMode,
+                           unsigned& outWidth,
+                           unsigned& outHeight );
+
+/**
+ * @brief Destructive in-place downscaling by a power of 2 factor.
+ *
+ * A box filter with a 2x2 kernel is repeatedly applied as long as the result
+ * of the next downscaling step would not be smaller than the desired
+ * dimensions.
+ * @param[in,out] pixels The buffer both to read from and write the result to.
+ * @param[in]     inputWidth The width of the input image.
+ * @param[in]     inputHeight The height of the input image.
+ * @param[in]     desiredWidth The width the client is requesting.
+ * @param[in]     desiredHeight The height the client is requesting.
+ * @param[out]    outWidth  The resulting width after downscaling.
+ * @param[out]    outHeight The resulting height after downscaling.
+ */
+void DownscaleInPlacePow2RGB888( unsigned char * pixels,
+                                 unsigned int inputWidth,
+                                 unsigned int inputHeight,
+                                 unsigned int desiredWidth,
+                                 unsigned int desiredHeight,
+                                 BoxDimensionTest dimensionTest,
+                                 unsigned int& outWidth,
+                                 unsigned int& outHeight );
 
 /**
  * @copydoc DownscaleInPlacePow2RGB888
  */
-void DownscaleInPlacePow2RGBA8888(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight );
+void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
+                                   unsigned int inputWidth,
+                                   unsigned int inputHeight,
+                                   unsigned int desiredWidth,
+                                   unsigned int desiredHeight,
+                                   BoxDimensionTest dimensionTest,
+                                   unsigned int& outWidth,
+                                   unsigned int& outHeight );
 
 /**
  * @copydoc DownscaleInPlacePow2RGB888
  *
  * For the 2-byte packed 16 bit format RGB565.
  */
-void DownscaleInPlacePow2RGB565(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight );
+void DownscaleInPlacePow2RGB565( unsigned char * pixels,
+                                 unsigned int inputWidth,
+                                 unsigned int inputHeight,
+                                 unsigned int desiredWidth,
+                                 unsigned int desiredHeight,
+                                 BoxDimensionTest dimensionTest,
+                                 unsigned int& outWidth,
+                                 unsigned int& outHeight );
 
 /**
  * @copydoc DownscaleInPlacePow2RGB888
  *
  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
  */
-void DownscaleInPlacePow2ComponentPair(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight );
+void DownscaleInPlacePow2ComponentPair( unsigned char * pixels,
+                                        unsigned int inputWidth,
+                                        unsigned int inputHeight,
+                                        unsigned int desiredWidth,
+                                        unsigned int desiredHeight,
+                                        BoxDimensionTest dimensionTest,
+                                        unsigned int& outWidth,
+                                        unsigned int& outHeight );
 
 /**
  * @copydoc DownscaleInPlacePow2RGB888
  *
  * For single-byte formats such as lum8 or alpha8.
  */
-void DownscaleInPlacePow2SingleBytePerPixel(
-    unsigned char * pixels,
-    unsigned int inputWidth, unsigned int inputHeight,
-    unsigned int desiredWidth, unsigned int desiredHeight,
-    BoxDimensionTest dimensionTest,
-    unsigned int& outWidth, unsigned int& outHeight );
+void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
+                                             unsigned int inputWidth,
+                                             unsigned int inputHeight,
+                                             unsigned int desiredWidth,
+                                             unsigned int desiredHeight,
+                                             BoxDimensionTest dimensionTest,
+                                             unsigned int& outWidth,
+                                             unsigned int& outHeight );
+
+/**
+ * @brief Rescales an input image into the exact output dimensions passed-in.
+ *
+ * Uses point sampling, equivalent to GL_NEAREST texture filter mode, for the
+ * fastest results, at the expense of aliasing (noisy images) when downscaling.
+ * @note inPixels is allowed to alias outPixels if this is a downscaling,
+ * but not for upscaling.
+ */
+void PointSample( const unsigned char * inPixels,
+                  unsigned int inputWidth,
+                  unsigned int inputHeight,
+                  Pixel::Format pixelFormat,
+                  unsigned char * outPixels,
+                  unsigned int desiredWidth,
+                  unsigned int desiredHeight );
+
+/**
+ * @copydoc PointSample
+ *
+ * Specialised for 4-byte formats like RGBA8888 and BGRA8888.
+ */
+void PointSample4BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight );
+
+/**
+ * @copydoc PointSample
+ *
+ * Specialised for 3-byte formats like RGB888 and BGR888.
+ */
+void PointSample3BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight );
+
+/**
+ * @copydoc PointSample
+ *
+ * Specialised for 2-byte formats like LA88.
+ */
+void PointSample2BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight );
+
+/**
+ * @copydoc PointSample
+ *
+ * Specialised for 1-byte formats like L8 and A8.
+ */
+void PointSample1BPP( const unsigned char * inPixels,
+                      unsigned int inputWidth,
+                      unsigned int inputHeight,
+                      unsigned char * outPixels,
+                      unsigned int desiredWidth,
+                      unsigned int desiredHeight );
+
+/**@}*/
+
+/**
+ * @defgroup ScalingAlgorithmFragments Composable subunits of the scaling algorithms.
+ * @{
+ */
 
 /**
  * @brief Average adjacent pairs of pixels, overwriting the input array.
@@ -135,9 +355,7 @@ void HalveScanlineInPlaceRGB888( unsigned char * pixels, unsigned int width );
 /**
  * @copydoc HalveScanlineInPlaceRGB888
  */
-void HalveScanlineInPlaceRGBA8888(
-    unsigned char * pixels,
-    unsigned int width );
+void HalveScanlineInPlaceRGBA8888( unsigned char * pixels, unsigned int width );
 
 /**
  * @copydoc HalveScanlineInPlaceRGB888
@@ -147,16 +365,12 @@ void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width );
 /**
  * @copydoc HalveScanlineInPlaceRGB888
  */
-void HalveScanlineInPlace2Bytes(
-    unsigned char * pixels,
-    unsigned int width );
+void HalveScanlineInPlace2Bytes( unsigned char * pixels, unsigned int width );
 
 /**
  * @copydoc HalveScanlineInPlaceRGB888
  */
-void HalveScanlineInPlace1Byte(
-    unsigned char * pixels,
-    unsigned int width );
+void HalveScanlineInPlace1Byte( unsigned char * pixels, unsigned int width );
 
 /**
  * @brief Average pixels at corresponding offsets in two scanlines.
@@ -167,54 +381,52 @@ void HalveScanlineInPlace1Byte(
  * @param[out] outputScanline Destination for the averaged pixels.
  * @param[in] width The widths of all the scanlines passed-in.
  */
-void AverageScanlines1(
-    const unsigned char * scanline1,
-    const unsigned char * scanline2,
-    unsigned char* outputScanline,
-    /** Image width in pixels (1 byte == 1 pixel: e.g. lum8 or alpha8).*/
-    unsigned int width );
+void AverageScanlines1( const unsigned char * scanline1,
+                        const unsigned char * scanline2,
+                        unsigned char* outputScanline,
+                        /** Image width in pixels (1 byte == 1 pixel: e.g. lum8 or alpha8).*/
+                        unsigned int width );
 
 /**
  * @copydoc AverageScanlines1
  */
-void AverageScanlines2(
-    const unsigned char * scanline1,
-    const unsigned char * scanline2,
-    unsigned char* outputScanline,
-    /** Image width in pixels (2 bytes == 1 pixel: e.g. lum8alpha8).*/
-    unsigned int width );
+void AverageScanlines2( const unsigned char * scanline1,
+                        const unsigned char * scanline2,
+                        unsigned char* outputScanline,
+                        /** Image width in pixels (2 bytes == 1 pixel: e.g. lum8alpha8).*/
+                        unsigned int width );
 
 /**
  * @copydoc AverageScanlines1
  */
-void AverageScanlines3(
-    const unsigned char * scanline1,
-    const unsigned char * scanline2,
-    unsigned char* outputScanline,
-    /** Image width in pixels (3 bytes == 1 pixel: e.g. RGB888).*/
-    unsigned int width );
+void AverageScanlines3( const unsigned char * scanline1,
+                        const unsigned char * scanline2,
+                        unsigned char* outputScanline,
+                        /** Image width in pixels (3 bytes == 1 pixel: e.g. RGB888).*/
+                        unsigned int width );
 
 /**
  * @copydoc AverageScanlines1
  */
-void AverageScanlinesRGBA8888(
-    const unsigned char * scanline1,
-    const unsigned char * scanline2,
-    unsigned char * outputScanline,
-    unsigned int width );
+void AverageScanlinesRGBA8888( const unsigned char * scanline1,
+                               const unsigned char * scanline2,
+                               unsigned char * outputScanline,
+                               unsigned int width );
 
 /**
  * @copydoc AverageScanlines1
  */
-void AverageScanlinesRGB565(
-    const unsigned char * scanline1,
-    const unsigned char * scanline2,
-    unsigned char* outputScanline,
-    unsigned int width );
+void AverageScanlinesRGB565( const unsigned char * scanline1,
+                             const unsigned char * scanline2,
+                             unsigned char* outputScanline,
+                             unsigned int width );
+/**@}*/
 
 /**
- * @brief Inline functions exposed in header to allow unit testing.
+ * @defgroup TestableInlines Inline functions exposed in header to allow unit testing.
+ * @{
  */
+
 /**
  * @brief Average two integer arguments.
  * @return The average of two uint arguments.
@@ -228,7 +440,7 @@ inline unsigned int AverageComponent( unsigned int a, unsigned int b )
 }
 
 /**
- * @brief Average a pair of RGB565 pixels.
+ * @brief Average a pair of RGBA8888 pixels.
  * @return The average of two RGBA8888 pixels.
  * @param[in] a First pixel to average.
  * @param[in] b Second pixel to average
@@ -260,6 +472,8 @@ inline uint32_t AveragePixelRGB565( uint32_t a, uint32_t b )
   return avg;
 }
 
+/**@}*/
+
 } /* namespace Platform */
 } /* namespace Internal */
 } /* namespace Dali */