Image scaling operations - FilterMode::Box implemented for all pixel formats 15/32515/9
authorAndrew Cox <andrew.cox@partner.samsung.com>
Tue, 2 Dec 2014 11:52:51 +0000 (11:52 +0000)
committerAndrew Cox <andrew.cox@partner.samsung.com>
Thu, 8 Jan 2015 09:53:57 +0000 (09:53 +0000)
RGB888, RGBA8888, RGB565, and 2-byte and 1-byte per pixel box
filters with unit tests. Plumbed-in to image loading pipeline to
implement the Box FilterMode.

Change-Id: I815d04299e77953d57ea2f122fb4ab9d086e29d5
Signed-off-by: Andrew Cox <andrew.cox@partner.samsung.com>
automated-tests/src/dali-adaptor-internal/CMakeLists.txt
automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp [new file with mode: 0644]
build/tizen/adaptor/Makefile.am
platform-abstractions/portable/image-operations.cpp [new file with mode: 0644]
platform-abstractions/portable/image-operations.h [new file with mode: 0644]
platform-abstractions/slp/file.list
platform-abstractions/slp/image-loaders/image-loader.cpp
platform-abstractions/slp/image-loaders/loader-png.cpp

index b677e9e..cf77527 100644 (file)
@@ -10,6 +10,7 @@ SET(TC_SOURCES
     utc-Dali-TiltSensor.cpp
     utc-Dali-CommandLineOptions.cpp
     utc-Dali-Lifecycle-Controller.cpp
+    utc-Dali-ImageOperations.cpp
 )
 
 LIST(APPEND TC_SOURCES
diff --git a/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp b/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp
new file mode 100644 (file)
index 0000000..7127312
--- /dev/null
@@ -0,0 +1,1082 @@
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-test-suite-utils.h>
+
+#include "platform-abstractions/portable/image-operations.h"
+
+using namespace Dali::Internal::Platform;
+
+namespace
+{
+
+/**
+ * @brief Generate a random integer between zero and the parameter passed in.
+ **/
+uint32_t RandomInRange( uint32_t max )
+{
+  const uint32_t randToMax = lrand48() % (max + 1);
+  return randToMax;
+}
+
+/**
+ * @brief Random number representable in an 8 bit color component.
+ */
+inline uint32_t RandomComponent8()
+{
+  return RandomInRange( 255u );
+}
+
+/**
+ * @brief Random number representable in a 5 bit color component.
+ */
+inline uint32_t RandomComponent5()
+{
+  return RandomInRange( 31u );
+}
+
+/**
+ * @brief Random number representable in a 6 bit color component.
+ */
+inline uint32_t RandomComponent6()
+{
+  return RandomInRange( 63u );
+}
+
+/**
+ * @brief RGBA8888 Pixels from separate color components.
+ */
+inline uint32_t PixelRGBA8888( uint32_t r, uint32_t g, uint32_t b, uint32_t a )
+{
+  return (r << 24) + (g << 16) + (b << 8) + a;
+}
+
+/**
+ * @brief RGB565 Pixels from color components in the low bits of passed-in words.
+ */
+inline uint16_t PixelRGB565( uint32_t r, uint32_t g, uint32_t b )
+{
+  return (r << 11) + (g << 5) + b;
+}
+
+/**
+ * @brief RGBA8888 Pixels with random color components.
+ */
+inline uint32_t RandomPixelRGBA8888( )
+{
+  const uint32_t randomPixel = PixelRGBA8888( RandomComponent8(), RandomComponent8(), RandomComponent8(), RandomComponent8() );
+  return randomPixel;
+}
+
+/**
+ * @brief Return a hash over a set of pixels.
+ *
+ * Used to check a buffer of pixels is unmodified by an operation given inputs
+ * that should mean that it is not changed.
+ */
+inline uint32_t HashPixels( const uint32_t* const pixels, unsigned int numPixels )
+{
+  uint32_t hash = 5381;
+
+  for( unsigned int i = 0; i < numPixels; ++i )
+  {
+    hash = hash * 33 + pixels[i];
+  }
+
+  return hash;
+}
+
+/**
+ * @brief Build some dummy scanlines to exercise scanline averaging code on.
+ */
+void SetupScanlineForHalvingTestsRGBA8888( size_t scanlineLength, Dali::Vector<uint32_t>& scanline, Dali::Vector<uint32_t>& reference )
+{
+  scanline.Resize( scanlineLength );
+  reference.Reserve( scanlineLength / 2 + 32 );
+
+  // Prepare some random pixels:
+  srand( 19 * 23 * 47 * 53 );
+  for( size_t i = 0; i < scanlineLength / 2; ++i )
+  {
+    // Generate random colors:
+    const uint32_t red1   = RandomComponent8();
+    const uint32_t red2   = RandomComponent8();
+    const uint32_t green1 = RandomComponent8();
+    const uint32_t green2 = RandomComponent8();
+    const uint32_t blue1  = RandomComponent8();
+    const uint32_t blue2  = RandomComponent8();
+    const uint32_t alpha1 = RandomComponent8();
+    const uint32_t alpha2 = RandomComponent8();
+
+    // The average of these pixels should equal the reference:
+    scanline[i * 2]     = PixelRGBA8888( red1, green1, blue1, alpha1 );
+    scanline[i * 2 + 1] = PixelRGBA8888( red2, green2, blue2, alpha2 );
+
+    // Average the two pixels manually as a reference:
+    reference.PushBack( PixelRGBA8888( (red1 + red2) >> 1u, (green1 + green2) >> 1u, (blue1 + blue2) >> 1u, (alpha1 + alpha2) >> 1u ) );
+  }
+
+  for( size_t i = scanlineLength / 2; i < reference.Capacity(); ++i )
+  {
+    reference[i] = 0xEEEEEEEE;
+  }
+}
+
+/**
+ * @brief Build some dummy scanlines to exercise scanline averaging code on.
+ */
+void SetupScanlineForHalvingTestsRGB565( size_t scanlineLength, Dali::Vector<uint16_t>& scanline, Dali::Vector<uint16_t>& reference )
+{
+  scanline.Resize( scanlineLength );
+  reference.Reserve( scanlineLength / 2 + 32 );
+
+  // Prepare some random pixels:
+  srand48( 19 * 23 * 47 * 53 );
+  for( size_t i = 0; i < scanlineLength / 2; ++i )
+  {
+    // Generate random colors:
+    const uint32_t red1   = RandomComponent5();
+    const uint32_t red2   = RandomComponent5();
+    const uint32_t green1 = RandomComponent6();
+    const uint32_t green2 = RandomComponent6();
+    const uint32_t blue1  = RandomComponent5();
+    const uint32_t blue2  = RandomComponent5();
+
+    // The average of these pixels should equal the reference:
+    scanline[i * 2]     = PixelRGB565( red1, green1, blue1 );
+    scanline[i * 2 + 1] = PixelRGB565( red2, green2, blue2 );
+
+    // Average the two pixels manually as a reference:
+    reference.PushBack( PixelRGB565( (red1 + red2) >> 1u, (green1 + green2) >> 1u, (blue1 + blue2) >> 1u ) );
+  }
+
+  for( size_t i = scanlineLength / 2; i < reference.Capacity(); ++i )
+  {
+    reference[i] = 0xEEEE;
+  }
+}
+
+/**
+ * @brief Build some dummy scanlines to exercise scanline averaging code on.
+ */
+void SetupScanlineForHalvingTests2Bytes( size_t scanlineLength, Dali::Vector<uint8_t>& scanline, Dali::Vector<uint8_t>& reference )
+{
+  scanline.Resize( scanlineLength * 2 );
+  reference.Reserve( scanlineLength + 32 );
+
+  // Prepare some random pixels:
+  srand48( 19 * 23 * 47 * 53 * 59 );
+  for( size_t i = 0; i < scanlineLength / 2; ++i )
+  {
+    // Generate random colors:
+    const uint32_t c11   = RandomComponent8();
+    const uint32_t c12   = RandomComponent8();
+    const uint32_t c21   = RandomComponent8();
+    const uint32_t c22   = RandomComponent8();
+
+    // The average of these pixels should equal the reference:
+    scanline[i * 4]     = c11;
+    scanline[i * 4 + 1] = c12;
+    scanline[i * 4 + 2] = c21;
+    scanline[i * 4 + 3] = c22;
+
+    // Average the two pixels manually as a reference:
+    reference.PushBack( (c11 + c21) >> 1u );
+    reference.PushBack( (c12 + c22) >> 1u );
+  }
+
+  for( size_t i = scanlineLength; i < reference.Capacity(); ++i )
+  {
+    reference[i] = 0xEE;
+  }
+}
+
+/**
+ * @brief Build some dummy 1 byte per pixel scanlines to exercise scanline averaging code on.
+ */
+void SetupScanlineForHalvingTests1Byte( size_t scanlineLength, Dali::Vector<uint8_t>& scanline, Dali::Vector<uint8_t>& reference )
+{
+  scanline.Resize( scanlineLength * 2 );
+  reference.Reserve( scanlineLength + 32 );
+
+  // Prepare some random pixels:
+  srand48( 19 * 23 * 47 * 53 * 63 );
+  for( size_t i = 0; i < scanlineLength / 2; ++i )
+  {
+    // Generate random colors:
+    const uint32_t c1 = RandomComponent8();
+    const uint32_t c2 = RandomComponent8();
+
+    // The average of these pixels should equal the reference:
+    scanline[i * 2]     = c1;
+    scanline[i * 2 + 1] = c2;
+
+    // Average the two pixels manually as a reference:
+    reference.PushBack( (c1 + c2) >> 1u );
+
+  }
+
+  for( size_t i = scanlineLength; i < reference.Capacity(); ++i )
+  {
+    reference[i] = 0xEE;
+  }
+}
+
+/**
+ * @brief Build some dummy scanlines to exercise vertical averaging code on.
+ *
+ * All tested formats bar RGB565 can share this setup.
+ */
+void SetupScanlinesRGBA8888( size_t scanlineLength, Dali::Vector<uint32_t>& scanline1, Dali::Vector<uint32_t>& scanline2, Dali::Vector<uint32_t>& reference, Dali::Vector<uint32_t>& output )
+{
+  scanline1.Reserve( scanlineLength );
+  scanline2.Reserve( scanlineLength );
+  reference.Reserve( scanlineLength + 32 );
+  output.Reserve( scanlineLength + 32 );
+
+  for( size_t i = scanlineLength; i < output.Capacity(); ++i )
+  {
+    output[i]    = 0xDEADBEEF;
+    reference[i] = 0xDEADBEEF;
+  }
+
+  // Prepare some random pixels:
+  srand48( 19 * 23 * 47 );
+  for( size_t i = 0; i < scanlineLength; ++i )
+  {
+    // Generate random colors:
+    const uint32_t red1   = RandomComponent8();
+    const uint32_t red2   = RandomComponent8();
+    const uint32_t green1 = RandomComponent8();
+    const uint32_t green2 = RandomComponent8();
+    const uint32_t blue1  = RandomComponent8();
+    const uint32_t blue2  = RandomComponent8();
+    const uint32_t alpha1 = RandomComponent8();
+    const uint32_t alpha2 = RandomComponent8();
+
+    // The average of these pixels should equal the reference:
+    scanline1.PushBack( PixelRGBA8888( red1, green1, blue1, alpha1 ) );
+    scanline2.PushBack( PixelRGBA8888( red2, green2, blue2, alpha2 ) );
+
+    // Average the two pixels manually as a reference:
+    reference.PushBack( PixelRGBA8888( (red1 + red2) >> 1u, (green1 + green2) >> 1u, (blue1 + blue2) >> 1u, (alpha1 + alpha2) >> 1u ) );
+  }
+}
+
+/**
+ * @brief Compares a scanline of interest to a reference, testing each pixel is the same.
+ */
+void MatchScanlinesRGBA8888( Dali::Vector<uint32_t>& reference, Dali::Vector<uint32_t>& output, size_t& numMatches, const char * const location )
+{
+  numMatches = 0;
+  for( size_t i = 0, length = reference.Capacity(); i < length; ++i )
+  {
+    DALI_TEST_EQUALS( output[i], reference[i], location );
+    numMatches += output[i] == reference[i];
+  }
+}
+
+} //< namespace unnamed
+
+/**
+ * @brief Test component averaging code.
+ */
+int UtcDaliImageOperationsAverageComponent(void)
+{
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 0u, 0u ), 0u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 1u, 1u ), 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 0xffffffffu >> 1u, 0xffffffffu >> 1u ), 0xffffffffu >> 1u, TEST_LOCATION );
+  const unsigned int avg3 = Dali::Internal::Platform::AverageComponent( 0xfffffffeu, 1u );
+  DALI_TEST_EQUALS( avg3, 0x7fffffffu, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 255u, 255u ), 255u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 512u, 0u ), 256u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 511u, 0u ), 255u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 510u, 0u ), 255u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 509u, 0u ), 254u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AverageComponent( 0u, 509u ), 254u, TEST_LOCATION );
+  END_TEST;
+}
+
+/**
+ * @brief Test Pixel averaging code.
+ */
+int UtcDaliImageOperationsAveragePixelRGBA8888(void)
+{
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGBA8888( 0u, 0u ), 0u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGBA8888( 0x01010101, 0x01010101 ), 0x01010101u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGBA8888( 0x01010101, 0x03030303 ), 0x02020202u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGBA8888( 0xffffffff, 0xffffffff ), 0xffffffffu, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGBA8888( 0xffffffff, 0u ), 0x7f7f7f7fu, TEST_LOCATION );
+  END_TEST;
+}
+
+/**
+ * @brief Test RGBA565 pixel averaging function.
+ */
+int UtcDaliImageOperationsAveragePixelRGB565(void)
+{
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0u, 0u ), 0u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0xf800u, 0xf800u ), 0xf800u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0xf800u, 0x800u ), 1u << 15, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0x7e0u, 0x7e0u ), 0x7e0u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0x7e0u, 0x20u ), 1u << 10, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0x1f, 0x1f ), 0x1fu, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0x1f, 0x1 ), 1u << 4, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0xf800u, 0x7e0u ), 0x7800u + 0x3e0u, TEST_LOCATION );
+  DALI_TEST_EQUALS( Dali::Internal::Platform::AveragePixelRGB565( 0xffff, 0xffff ), 0xffffu, TEST_LOCATION );
+  END_TEST;
+}
+
+/**
+ * @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 )
+{
+  ImageAttributes attributes;
+  attributes.SetScalingMode( ImageAttributes::ShrinkToFit );
+  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 );
+  DALI_TEST_EQUALS( downScaled->GetImageWidth(), expectedDimension, location );
+  DALI_TEST_EQUALS( downScaled->GetImageHeight(), expectedDimension, location );
+  DALI_TEST_EQUALS( downScaled->GetPixelFormat(), format, location );
+}
+
+/**
+ * @brief Test the top-level function for reducing the dimension of a bitmap,
+ * feeding it each of the five pixel formats that are output by image loaders.
+ * Simply assert that the resulting bitmaps have the expected dimensions and
+ * formats.
+ */
+int UtcDaliImageOperationsDownscaleBitmap(void)
+{
+  // Do Scalings that are expected to work for all pixels modes and assert the resulting bitmap dimensions:
+
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGBA8888, 1024, 8, 8, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGB888, 1024, 8, 8, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGB565, 1024, 8, 8, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::LA88, 1024, 8, 8, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::L8, 1024, 8, 8, TEST_LOCATION );
+
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGBA8888, 773, 1, 1, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGB888, 787, 1, 1, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGB565, 797, 1, 1, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::LA88, 809, 1, 1, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::L8, 811, 1, 1, TEST_LOCATION );
+
+  // Do Scalings that are expected to produce a slightly larger than requested image:
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGBA8888, 47, 7, 11, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGB888, 73, 17, 18, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::RGB565, 61, 8, 15, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::LA88, 19, 5, 9, TEST_LOCATION );
+  TestDownscaledBitmapHasRightDimensionsAndFormat( Pixel::L8, 353, 23, 44, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test downscaling of RGB888 images as raw pixel arrays.
+ */
+int UtcDaliImageOperationsDownscaleInPlacePow2RGB888(void)
+{
+  unsigned outWidth = -1, outHeight = -1;
+
+  // Do downscaling to 1 x 1 so we can easily assert the value of the single pixel produced:
+
+  // Scale down a black/white checkerboard to mid-grey:
+  unsigned char check_4x4 [16 * 3] = {
+      0xff, 0xff, 0xff,  0x00, 0x00, 0x00,  0xff, 0xff, 0xff,  0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00,  0xff, 0xff, 0xff,  0x00, 0x00, 0x00,  0xff, 0xff, 0xff,
+      0xff, 0xff, 0xff,  0x00, 0x00, 0x00,  0xff, 0xff, 0xff,  0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00,  0xff, 0xff, 0xff,  0x00, 0x00, 0x00,  0xff, 0xff, 0xff
+  };
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888(check_4x4, 4, 4, 1, 1, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( check_4x4[0], 0x7f, TEST_LOCATION );
+
+  // Scale down a 16 pixel black image with a single white pixel to a 1/16th grey single pixel:
+  unsigned char single_4x4 [16 * 3] = {
+    0xff, 0xff, 0xff,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00
+  };
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888(single_4x4, 4, 4, 1, 1, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( single_4x4[0], 0xf, TEST_LOCATION );
+
+  // Scale down a 16 pixel black image with a single white pixel to a 1/16th grey single pixel:
+  // (white pixel at bottom-right of image)
+  unsigned char single_4x4_2 [16 * 3] = {
+      0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0x00, 0x00, 0x00,  0xff, 0xff, 0xff
+    };
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888(single_4x4_2, 4, 4, 1, 1, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( single_4x4_2[0], 0xf, TEST_LOCATION );
+
+  // Build a larger ~600 x ~600 uniform magenta image for tests which only test output dimensions:
+
+  unsigned char magenta_600_x_600[608*608 * 3];
+  for( unsigned int i = 0; i < sizeof(magenta_600_x_600); i += 3 )
+  {
+    magenta_600_x_600[i] = 0xff;
+    magenta_600_x_600[i + 1] = 0;
+    magenta_600_x_600[i + 2] = 0xff;
+  }
+
+  // Scaling to 0 x 0 should stop at 1 x 1:
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 352, 352, 0, 0, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 1u, TEST_LOCATION );
+
+  // Scaling to 1 x 1 should hit 1 x 1:
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 608, 608, 1, 1, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 1u, TEST_LOCATION );
+
+  // Scaling to original dimensions should NOP:
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 384, 384, 384, 384, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 384u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 384u, TEST_LOCATION );
+
+  // More dimension tests:
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 352, 352, 44, 11, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 44u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 44u, TEST_LOCATION );
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 384, 384, 3, 48, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_EQUALS( outWidth, 48u, TEST_LOCATION );
+  DALI_TEST_EQUALS( outHeight, 48u, TEST_LOCATION );
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 384, 384, 3, 3, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_CHECK( outWidth == 3u && outHeight == 3u );
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 320, 320, 5, 5, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_CHECK( outWidth == 5u && outHeight == 5u );
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 448, 448, 7, 7, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_CHECK( outWidth == 7u && outHeight == 7u );
+
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB888( magenta_600_x_600, 352, 352, 11, 11, BoxDimensionTestBoth, outWidth, outHeight );
+  DALI_TEST_CHECK( outWidth == 11u && outHeight == 11u );
+
+  // Check that no pixel values were modified by the repeated averaging of identical pixels in tests above:
+  unsigned int numNonMagenta = 0u;
+  for( unsigned i = 0; i < sizeof(magenta_600_x_600); i += 3 )
+  {
+    numNonMagenta += magenta_600_x_600[i] == 0xff && magenta_600_x_600[i + 1] == 0x00 && magenta_600_x_600[i + 2] == 0xff ? 0 : 1;
+  }
+  DALI_TEST_EQUALS( numNonMagenta, 0u, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test that resizing RGBA8888 images as raw pixel arrays produces a result of the correct dimensions.
+ */
+void TestDownscaleOutputsExpectedDimensionsRGBA8888( uint32_t pixels[], unsigned inputWidth, unsigned inputHeight, unsigned int desiredWidth, unsigned int desiredHeight, unsigned int expectedWidth, unsigned int expectedHeight, const char * const location )
+{
+  unsigned int resultingWidth = -1, resultingHeight = -1;
+  Dali::Internal::Platform::DownscaleInPlacePow2RGBA8888(
+      reinterpret_cast<unsigned char *> (pixels),
+      inputWidth, inputHeight,
+      desiredWidth, desiredHeight, BoxDimensionTestBoth,
+      resultingWidth, resultingHeight );
+
+  DALI_TEST_EQUALS( resultingWidth, expectedWidth, location );
+  DALI_TEST_EQUALS( resultingHeight, expectedHeight, location );
+}
+
+/**
+ * @brief Test that resizing RGB565 images as raw pixel arrays produces a result of the correct dimensions.
+ */
+void TestDownscaleOutputsExpectedDimensionsRGB565( uint16_t pixels[], unsigned inputWidth, unsigned inputHeight, unsigned int desiredWidth, unsigned int desiredHeight, unsigned int expectedWidth, unsigned int expectedHeight, const char * const location )
+{
+  unsigned int resultingWidth = -1, resultingHeight = -1;
+  Dali::Internal::Platform::DownscaleInPlacePow2RGB565(
+      reinterpret_cast<unsigned char *> (pixels),
+      inputWidth, inputHeight,
+      desiredWidth, desiredHeight, BoxDimensionTestBoth,
+      resultingWidth, resultingHeight );
+
+  DALI_TEST_EQUALS( resultingWidth, expectedWidth, location );
+  DALI_TEST_EQUALS( resultingHeight, expectedHeight, location );
+}
+
+/**
+ * @brief Test that resizing 2-byte-per-pixel images as raw pixel arrays produces a result of the correct dimensions.
+ */
+void TestDownscaleOutputsExpectedDimensions2ComponentPair( uint8_t pixels[], unsigned inputWidth, unsigned inputHeight, unsigned int desiredWidth, unsigned int desiredHeight, unsigned int expectedWidth, unsigned int expectedHeight, const char * const location )
+{
+  unsigned int resultingWidth = -1, resultingHeight = -1;
+  Dali::Internal::Platform::DownscaleInPlacePow2ComponentPair(
+      pixels,
+      inputWidth, inputHeight,
+      desiredWidth, desiredHeight, BoxDimensionTestBoth,
+      resultingWidth, resultingHeight );
+
+  DALI_TEST_EQUALS( resultingWidth, expectedWidth, location );
+  DALI_TEST_EQUALS( resultingHeight, expectedHeight, location );
+}
+
+/**
+ * @brief Test that resizing single-byte-per-pixel images as raw pixel arrays produces a result of the correct dimensions.
+ */
+void TestDownscaleOutputsExpectedDimensionsSingleComponent( uint8_t pixels[], unsigned inputWidth, unsigned inputHeight, unsigned int desiredWidth, unsigned int desiredHeight, unsigned int expectedWidth, unsigned int expectedHeight, const char * const location )
+{
+  unsigned int resultingWidth = -1, resultingHeight = -1;
+  Dali::Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel(
+      pixels,
+      inputWidth, inputHeight,
+      desiredWidth, desiredHeight, BoxDimensionTestBoth,
+      resultingWidth, resultingHeight );
+
+  DALI_TEST_EQUALS( resultingWidth, expectedWidth, location );
+  DALI_TEST_EQUALS( resultingHeight, expectedHeight, location );
+}
+
+/**
+ * @brief Test downscaling of RGBA8888 images in raw image arrays.
+ */
+int UtcDaliImageOperationsDownscaleInPlacePow2RGBA8888(void)
+{
+  uint32_t image[608*608];
+  for( unsigned i = 0; i < sizeof(image) / sizeof(image[0]); ++i )
+  {
+    image[i] = 0xffffffff;
+  }
+  unsigned char* const pixels = reinterpret_cast<unsigned char *> (image);
+  unsigned int resultingWidth = -1, resultingHeight = -1;
+
+  // Test downscaling where the input size is an exact multiple of the desired size:
+  // (We expect a perfect result here)
+
+  DownscaleInPlacePow2RGBA8888( pixels, 600, 600, 75, 75, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 75u, TEST_LOCATION );
+  DALI_TEST_EQUALS( resultingHeight, 75u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 512, 512, 16, 16, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 16u, TEST_LOCATION );
+  DALI_TEST_EQUALS( resultingHeight, 16u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 512, 64, 16, 2, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 16u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 2u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 64, 1024, 4, 64, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 4u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 64u, TEST_LOCATION );
+
+  // Test downscaling where the input size is slightly off being an exact multiple of the desired size:
+  // (We expect a perfect match at the end because of rounding-down to an even width and height at each step)
+
+  DownscaleInPlacePow2RGBA8888( pixels, 601, 603, 75, 75, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 75u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 75u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 736 + 1, 352 + 3, 23, 11, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 23u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 11u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 384 + 3, 896 + 1, 3, 7, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 3u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 7u, TEST_LOCATION );
+
+  // Test downscales with source dimensions which are under a nice power of two by one:
+
+  // The target is hit exactly due to losing spare columns or rows at each iteration:
+  DownscaleInPlacePow2RGBA8888( pixels, 63, 31, 7, 3, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 7u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 3u, TEST_LOCATION );
+
+  // Asking to downscale a bit smaller should stop at the dimensions of the last test as one more halving would go down to 3 x 1, which is too small.
+  DownscaleInPlacePow2RGBA8888( pixels, 63, 31, 4, 2, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 7u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 3u, TEST_LOCATION );
+
+  // Should stop at almost twice the requested dimensions:
+  DownscaleInPlacePow2RGBA8888( pixels, 15, 127, 4, 32, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 7u, TEST_LOCATION  );
+  DALI_TEST_EQUALS( resultingHeight, 63u, TEST_LOCATION );
+
+  // Test downscales to 1 in one or both dimensions:
+  // Parameters:                                         input-x  input-y, desired-x, desired-y, expected-x, expected-y
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     512,     1,         1,         1,          1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     32,      16,        1,         16,         1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     32,      7,         1,         16,         1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     32,      7,         1,         16,         1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     32,      5,         1,         16,         1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     32,      3,         1,         16,         1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 32,      512,     1,         1,         1,          16,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 32,      512,     1,         16,        1,          16,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 32,      512,     1,         3,         1,          16,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 33,      33,      1,         1,         1,          1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 17*19,   17*19,   1,         1,         1,          1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 33,      33,      3,         1,         4,          4,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 33,      9,       3,         1,         4,          1,         TEST_LOCATION );
+
+
+
+  // Test downscales to zero in one or both dimensions:
+  // Scaling should stop when one or both dimensions reach 1.
+  // Parameters:                                         input-x  input-y, desired-x, desired-y, expected-x, expected-y
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     512,     0,         0,         1,          1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     256,     0,         0,         2,          1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     128,     0,         0,         4,          1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 512,     16,      0,         0,         32,         1,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 128,     512,     0,         0,         1,          4,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 32,      512,     0,         0,         1,          16,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 8,       512,     0,         0,         1,          64,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 2,       512,     0,         0,         1,          256,       TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test downscalings of RGBA8888 images in raw image arrays that should have no effect on the input.
+ */
+int UtcDaliImageOperationsDownscaleInPlacePow2RGBA8888Nops(void)
+{
+  uint32_t image[608*608];
+  const uint32_t numPixels = sizeof(image) / sizeof(image[0]);
+  for( unsigned i = 0; i < numPixels; ++i )
+  {
+    image[i] = RandomPixelRGBA8888();
+  }
+  const uint32_t imageHash = HashPixels( image, numPixels );
+  unsigned char* const pixels = reinterpret_cast<unsigned char *> (image);
+  unsigned int resultingWidth = -1, resultingHeight = -1;
+
+  // Test downscales to the same size:
+  // The point is just to be sure the downscale is a NOP in this case:
+
+  DownscaleInPlacePow2RGBA8888( pixels, 600, 600, 600, 600, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 600u, TEST_LOCATION );
+  DALI_TEST_EQUALS( resultingHeight, 600u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 512, 128, 512, 128, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 512u, TEST_LOCATION );
+  DALI_TEST_EQUALS( resultingHeight, 128u, TEST_LOCATION );
+
+  DownscaleInPlacePow2RGBA8888( pixels, 17, 1001, 17, 1001, BoxDimensionTestBoth, resultingWidth, resultingHeight );
+  DALI_TEST_EQUALS( resultingWidth, 17u, TEST_LOCATION );
+  DALI_TEST_EQUALS( resultingHeight, 1001u, TEST_LOCATION );
+
+  // Test downscales that request a larger size (we never upscale so these are NOPs too):
+  // Parameters:                                         input-x  input-y, desired-x, desired-y, expected-x, expected-y
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 300,     300,     600,       600,       300,        300,       TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 3,       127,     99,        599,       3,          127,       TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGBA8888( image, 600,     600,     999,       999,       600,        600,       TEST_LOCATION ); //< checks no out of bounds mem access in this case
+
+
+  // Make sure that none of these NOP downscalings has affected the pixels of the image:
+  DALI_TEST_EQUALS( HashPixels( image, numPixels ), imageHash, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Do additional downscaling testing using RGB565 images in raw image
+ * arrays to shake out differences relating to the pixel format.
+ */
+int UtcDaliImageOperationsDownscaleInPlacePow2RGB565(void)
+{
+  // Test that calling with null and zero parameters doesn't blow up:
+  unsigned int outWidth, outHeight;
+  DownscaleInPlacePow2RGB565( 0, 0, 0, 0, 0, BoxDimensionTestBoth, outWidth, outHeight );
+
+  uint16_t image[608*608];
+  for( unsigned i = 0; i < sizeof(image) / sizeof(image[0]); ++i )
+  {
+    image[i] = 0xffff;
+  }
+
+  // Do a straightforward test using an exact divisor target size:
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 600, 600, 75, 75, 75, 75, TEST_LOCATION );
+  // Test that a slightly smaller than possible to achieve target results in the
+  // next-higher exact divisor output image dimensions:
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 600, 600, 71, 69, 75, 75, TEST_LOCATION );
+  // Test that resizing from a starting size that is slightly larger than an exact
+  // multiple of the desired dimensions still results in the desired ones being
+  // reached:
+  // Parameters:                                       input-x  input-y, desired-x, desired-y, expected-x, expected-y
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 600 + 1, 600 + 1, 75,        75,        75,         75,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 256 + 1, 512 + 1, 2,         4,         2,          4,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 512 + 1, 128 + 1, 16,        4,         16,         4,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 512 + 1, 64  + 1, 16,        2,         16,         2,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 512 + 3, 512 + 3, 16,        16,        16,         16,        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 512 + 3, 256 + 3, 16,        8,         16,         8,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 256 + 3, 512 + 3, 4,         8,         4,          8,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 256 + 7, 512 + 7, 4,         8,         4,          8,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 256 + 7, 512 + 7, 2,         4,         2,          4,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 512 + 7, 128 + 7, 16,        4,         16,         4,         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsRGB565( image, 512 + 7, 64  + 7, 16,        2,         16,         2,         TEST_LOCATION );
+
+
+  END_TEST;
+}
+
+/**
+ * @brief Do additional downscaling testing using 2-byte-per-pixel images in
+ * raw image arrays to shake out differences relating to the pixel format.
+ */
+int UtcDaliImageOperationsDownscaleInPlacePow2ComponentPair(void)
+{
+  // Simple test that a null pointer does not get dereferenced in the function:
+  unsigned int outWidth, outHeight;
+  DownscaleInPlacePow2ComponentPair( 0, 0, 0, 0, 0, BoxDimensionTestBoth, outWidth, outHeight );
+
+  // Simple tests of dimensions output:
+
+  uint8_t image[608*608*2];
+  for( unsigned i = 0; i < sizeof(image) / sizeof(image[0]); ++i )
+  {
+    image[i] = 0xff;
+  }
+
+  TestDownscaleOutputsExpectedDimensions2ComponentPair( image,
+                                                        600, 600, //< Input dimensions
+                                                        37, 37,   //< Requested dimensions
+                                                        37, 37,   //< Expected output dimensions
+                                                        TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensions2ComponentPair( image,
+                                                        600, 600, //< Input dimensions
+                                                        34, 35,   //< Requested dimensions to scale-down to
+                                                        37, 37,   //< Expected output dimensions achieved
+                                                        TEST_LOCATION );
+  ///@note: No need to be as comprehensive as with RGB888 and RGBA8888 as the logic is shared.
+
+  END_TEST;
+}
+
+/**
+ * @brief Do additional downscaling testing using 1-byte-per-pixel images in
+ * raw image arrays to shake out differences relating to the pixel format.
+ */
+int UtcDaliImageOperationsDownscaleInPlacePow2SingleBytePerPixel(void)
+{
+  // Simple test that a null pointer does not get dereferenced in the function:
+  unsigned int outWidth, outHeight;
+  DownscaleInPlacePow2SingleBytePerPixel( 0, 0, 0, 0, 0, BoxDimensionTestBoth, outWidth, outHeight );
+
+  // Tests of output dimensions from downscaling:
+  uint8_t image[608*608];
+  for( unsigned i = 0; i < sizeof(image) / sizeof(image[0]); ++i )
+  {
+    image[i] = 0xff;
+  }
+
+  TestDownscaleOutputsExpectedDimensionsSingleComponent( image,
+                                                         600, 300,  //< Input dimensions
+                                                         150, 75,   //< Requested dimensions to scale-down to
+                                                         150, 75,   //< Expected output dimensions achieved
+                                                         TEST_LOCATION );
+  TestDownscaleOutputsExpectedDimensionsSingleComponent( image, 577, 411, 142, 99, 144, 102, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging pairs of pixels on a scanline.
+ */
+int UtcDaliImageOperationsHalveScanlineInPlaceRGB888(void)
+{
+  // Red and cyan, averaging to grey:
+  unsigned char shortEven[] =    { 0xff, 0, 0,   0, 0xff, 0xff,   0xff, 0, 0,   0, 0xff, 0xff };
+  unsigned char shortOdd[] =     { 0xff, 0, 0,  0, 0xff, 0xff,  0xff, 0, 0,  0, 0xff, 0xff,  0xC, 0xC, 0xC };
+
+  Dali::Internal::Platform::HalveScanlineInPlaceRGB888( shortEven, 4u );
+  Dali::Internal::Platform::HalveScanlineInPlaceRGB888( shortOdd, 4u );
+  for( unsigned i = 0; i < sizeof(shortEven) >> 1u ; ++i )
+  {
+    DALI_TEST_EQUALS( unsigned(shortEven[i]), 0x7fu, TEST_LOCATION );
+    DALI_TEST_EQUALS( unsigned(shortOdd[i]), 0x7fu, TEST_LOCATION );
+  }
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging pairs of pixels on a scanline.
+ */
+int UtcDaliImageOperationsHalveScanlineInPlaceRGBA8888(void)
+{
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint32_t> scanline;
+  Dali::Vector<uint32_t> reference;
+  SetupScanlineForHalvingTestsRGBA8888( scanlineLength, scanline, reference );
+
+  HalveScanlineInPlaceRGBA8888( (uint8_t *) &scanline[0], scanlineLength );
+
+  // Check that the halving matches the independently calculated reference:
+  size_t numMatches = 0;
+  for( int i = 0, length = reference.Size(); i < length; ++i )
+  {
+    DALI_TEST_EQUALS( scanline[i], reference[i], TEST_LOCATION );
+    numMatches += scanline[i] == reference[i];
+  }
+  DALI_TEST_EQUALS( numMatches, scanlineLength / 2, TEST_LOCATION );
+
+  // Test for no beyond-bounds writes:
+  for( size_t i = scanlineLength / 2; i < reference.Capacity(); ++i )
+  {
+    DALI_TEST_EQUALS( reference[i],  0xEEEEEEEE, TEST_LOCATION );
+  }
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging pairs of pixels on a scanline.
+ */
+int UtcDaliImageOperationsHalveScanlineInPlaceRGB565(void)
+{
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint16_t> scanline;
+  Dali::Vector<uint16_t> reference;
+  SetupScanlineForHalvingTestsRGB565( scanlineLength, scanline, reference );
+
+  HalveScanlineInPlaceRGB565( (unsigned char *) (&scanline[0]), scanlineLength );
+
+  // Check output against reference:
+  size_t numMatches = 0;
+  for( int i = 0, length = reference.Size(); i < length; ++i )
+  {
+    DALI_TEST_EQUALS( scanline[i], reference[i], TEST_LOCATION );
+    numMatches += scanline[i] == reference[i];
+  }
+  DALI_TEST_EQUALS( numMatches, scanlineLength / 2, TEST_LOCATION );
+
+  // Test for no beyond-bounds writes:
+  for( size_t i = scanlineLength / 2; i < reference.Capacity(); ++i )
+  {
+    DALI_TEST_EQUALS( reference[i],  0xEEEE, TEST_LOCATION );
+  }
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging pairs of pixels on a scanline.
+ */
+int UtcDaliImageOperationsHalveScanlineInPlace2Bytes(void)
+{
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint8_t> scanline;
+  Dali::Vector<uint8_t> reference;
+  SetupScanlineForHalvingTests2Bytes( scanlineLength, scanline, reference );
+
+  HalveScanlineInPlace2Bytes( &scanline[0], scanlineLength );
+
+  // Test the output against the reference (no differences):
+  size_t numMatches = 0;
+  for( int i = 0, length = reference.Size(); i < length; ++i )
+  {
+    DALI_TEST_EQUALS( 1u * scanline[i], 1u * reference[i], TEST_LOCATION );
+    numMatches += scanline[i] == reference[i];
+  }
+  // The number of matching bytes should be double the number of pixels, which happens to be the original scanline length in pixels:
+  DALI_TEST_EQUALS( numMatches, scanlineLength, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging pairs of pixels on a scanline.
+ */
+int UtcDaliImageOperationsHalveScanlineInPlace1Byte(void)
+{
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint8_t> scanline;
+  Dali::Vector<uint8_t> reference;
+  SetupScanlineForHalvingTests1Byte( scanlineLength, scanline, reference );
+
+  HalveScanlineInPlace1Byte( &scanline[0], scanlineLength );
+
+  // Test the reference matches the output:
+  size_t numMatches = 0;
+  for( int i = 0, length = reference.Size(); i < length; ++i )
+  {
+    DALI_TEST_EQUALS( 1u * scanline[i], 1u * reference[i], TEST_LOCATION );
+    numMatches += scanline[i] == reference[i];
+  }
+  DALI_TEST_EQUALS( numMatches, scanlineLength / 2, TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging vertically-adjacent pairs of single-byte-per-pixel pixels on a scanline.
+ */
+int UtcDaliImageOperationsAverageScanlines1(void)
+{
+  // Red and cyan, averaging to grey:
+  unsigned char shortEven1[] =    { 0xff, 0, 0,    0, 0xff, 0xff,  0xff, 0, 0,      0, 0xff, 0xff };
+  unsigned char shortEven2[] =    { 0, 0xff, 0xff, 0xff, 0, 0,     0, 0xff,  0xff,  0xff, 0, 0 };
+  unsigned char outputBuffer[sizeof(shortEven1)];
+
+  AverageScanlines1( shortEven1, shortEven2, outputBuffer, sizeof(shortEven1) );
+  for( unsigned i = 0; i < sizeof(shortEven1) ; ++i )
+  {
+    DALI_TEST_EQUALS( unsigned(outputBuffer[i]), 0x7fu, TEST_LOCATION );
+  }
+
+  // Longer test reusing RGBA setup/test logic:
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint32_t> scanline1;
+  Dali::Vector<uint32_t> scanline2;
+  Dali::Vector<uint32_t> reference;
+  Dali::Vector<uint32_t> output;
+  SetupScanlinesRGBA8888( scanlineLength, scanline1, scanline2, reference, output );
+
+  AverageScanlines1( (const unsigned char*) &scanline1[0], (const unsigned char*) &scanline2[0], (unsigned char*) &output[0], scanlineLength * 4 );
+
+  // Check the output matches the independently generated reference:
+  size_t numMatches = 0;
+  MatchScanlinesRGBA8888( reference, output, numMatches, TEST_LOCATION );
+  DALI_TEST_EQUALS( numMatches, reference.Capacity(), TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging vertically-adjacent pairs of 2-byte-per-pixel pixels on a scanline.
+ */
+int UtcDaliImageOperationsAverageScanlines2(void)
+{
+  // Red and cyan, averaging to grey:
+  unsigned char shortEven1[] =    { 0xff, 0, 0,    0, 0xff, 0xff,  0xff, 0, 0,      0, 0xff, 0xff };
+  unsigned char shortEven2[] =    { 0, 0xff, 0xff, 0xff, 0, 0,     0, 0xff,  0xff,  0xff, 0, 0 };
+  unsigned char outputBuffer[sizeof(shortEven1)];
+
+  AverageScanlines2( shortEven1, shortEven2, outputBuffer, sizeof(shortEven1) / 2 );
+
+  for( unsigned i = 0; i < sizeof(shortEven1); ++i )
+  {
+    DALI_TEST_EQUALS( unsigned(outputBuffer[i]), 0x7fu, TEST_LOCATION );
+  }
+
+  // Longer test reusing RGBA setup/test logic:
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint32_t> scanline1;
+  Dali::Vector<uint32_t> scanline2;
+  Dali::Vector<uint32_t> reference;
+  Dali::Vector<uint32_t> output;
+  SetupScanlinesRGBA8888( scanlineLength, scanline1, scanline2, reference, output );
+
+  AverageScanlines2( (const unsigned char*) &scanline1[0], (const unsigned char*) &scanline2[0], (unsigned char*) &output[0], scanlineLength * 2 );
+
+  // Check the output matches the independently generated reference:
+  size_t numMatches = 0;
+  MatchScanlinesRGBA8888( reference, output, numMatches, TEST_LOCATION );
+  DALI_TEST_EQUALS( numMatches, reference.Capacity(), TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging vertically-adjacent pairs of RGB888 pixels on a scanline.
+ */
+int UtcDaliImageOperationsAverageScanlines3(void)
+{
+  // Red and cyan, averaging to grey:
+  unsigned char shortEven1[] =    { 0xff, 0, 0,    0, 0xff, 0xff,  0xff, 0, 0,      0, 0xff, 0xff };
+  unsigned char shortEven2[] =    { 0, 0xff, 0xff, 0xff, 0, 0,     0, 0xff,  0xff,  0xff, 0, 0 };
+  unsigned char outputBuffer[sizeof(shortEven1)];
+
+  AverageScanlines3( shortEven1, shortEven2, outputBuffer, sizeof(shortEven1) / 3 );
+  for( unsigned i = 0; i < sizeof(shortEven1) ; ++i )
+  {
+    DALI_TEST_EQUALS( unsigned(outputBuffer[i]), 0x7fu, TEST_LOCATION );
+  }
+
+  // Longer test reusing RGBA setup/test logic:
+  const size_t scanlineLength = 3 * 4 * 90u;
+  Dali::Vector<uint32_t> scanline1;
+  Dali::Vector<uint32_t> scanline2;
+  Dali::Vector<uint32_t> reference;
+  Dali::Vector<uint32_t> output;
+  SetupScanlinesRGBA8888( scanlineLength, scanline1, scanline2, reference, output );
+
+  AverageScanlines3( (const unsigned char*) &scanline1[0], (const unsigned char*) &scanline2[0], (unsigned char*) &output[0], scanlineLength * 4 / 3 );
+
+  // Check the output matches the independently generated reference:
+  size_t numMatches = 0;
+  MatchScanlinesRGBA8888( reference, output, numMatches, TEST_LOCATION );
+  DALI_TEST_EQUALS( numMatches, reference.Capacity(), TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging vertically-adjacent pairs of RGBA8888 pixels on a scanline.
+ */
+int UtcDaliImageOperationsAverageScanlinesRGBA8888(void)
+{
+  const size_t scanlineLength = 4096u;
+  Dali::Vector<uint32_t> scanline1;
+  Dali::Vector<uint32_t> scanline2;
+  Dali::Vector<uint32_t> reference;
+  Dali::Vector<uint32_t> output;
+  SetupScanlinesRGBA8888( scanlineLength, scanline1, scanline2, reference, output );
+
+  AverageScanlinesRGBA8888( (const unsigned char*) &scanline1[0], (const unsigned char*) &scanline2[0], (unsigned char*) &output[0], scanlineLength );
+
+  // Check the output matches the independently generated reference:
+  size_t numMatches = 0;
+  MatchScanlinesRGBA8888( reference, output, numMatches, TEST_LOCATION );
+  DALI_TEST_EQUALS( numMatches, reference.Capacity(), TEST_LOCATION );
+
+  END_TEST;
+}
+
+/**
+ * @brief Test the function for averaging vertically-adjacent pairs of RGB565 pixels on a scanline.
+ */
+int UtcDaliImageOperationsAverageScanlinesRGB565(void)
+{
+  // Red and cyan, averaging to grey:
+  const uint16_t shortEven1[] =    { 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xBEEF, 0xBEEF };
+  const uint16_t shortEven2[] =    { 0x7ff,  0x7ff,  0x7ff,  0x7ff,  0x7ff,  0x7ff, 0xBEEF, 0xBEEF };
+  const size_t arrayLength = sizeof(shortEven1) / sizeof(shortEven1[0]) - 2;
+  uint16_t outputBuffer[arrayLength + 2];
+  outputBuffer[arrayLength] = 0xDEAD;
+  outputBuffer[arrayLength+1] = 0xDEAD;
+
+  Dali::Internal::Platform::AverageScanlinesRGB565( (const unsigned char*) shortEven1, (const unsigned char*) shortEven2, (unsigned char*) outputBuffer,  arrayLength );
+  for( unsigned i = 0; i <  arrayLength ; ++i )
+  {
+    DALI_TEST_EQUALS( unsigned(outputBuffer[i]), 0xffff - (1u << 15) - (1u << 10) - (1u << 4), TEST_LOCATION );
+  }
+
+  // Check for buffer overrun:
+  DALI_TEST_EQUALS( outputBuffer[arrayLength], 0xDEAD, TEST_LOCATION );
+  DALI_TEST_EQUALS( outputBuffer[arrayLength+1], 0xDEAD, TEST_LOCATION );
+
+  END_TEST;
+}
index 4b017b3..d029920 100644 (file)
@@ -25,6 +25,7 @@ include ../../../adaptors/base/file.list
 
 # Platform Abstraction
 slp_platform_abstraction_src_dir = ../../../platform-abstractions/slp
+portable_platform_abstraction_src_dir = ../../../platform-abstractions/portable
 include ../../../platform-abstractions/slp/file.list
 
 # Internal Common
diff --git a/platform-abstractions/portable/image-operations.cpp b/platform-abstractions/portable/image-operations.cpp
new file mode 100644 (file)
index 0000000..e6b9415
--- /dev/null
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// 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>
+
+namespace Dali
+{
+namespace Internal
+{
+namespace Platform
+{
+
+namespace
+{
+using Integration::Bitmap;
+using Integration::BitmapPtr;
+typedef unsigned char PixelBuffer;
+
+#if defined(DEBUG_ENABLED)
+/**
+ * Disable logging of image operations or make it verbose from the commandline
+ * as follows (e.g., for dali demo app):
+ * <code>
+ * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
+ * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
+ * </code>
+ */
+Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
+#endif
+
+/** @return The greatest even number less than or equal to the argument. */
+inline unsigned int EvenDown( const unsigned int a )
+{
+  const unsigned int evened = a & ~1u;
+  return evened;
+}
+
+/**
+ * @brief Log bad parameters.
+ */
+void ValidateScalingParameters(
+  const unsigned int inputWidth,    const unsigned int inputHeight,
+  const unsigned int desiredWidth, const unsigned int desiredHeight )
+{
+  if( desiredWidth > inputWidth || desiredHeight > inputHeight )
+  {
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
+  }
+
+  if( desiredWidth == 0u || desiredHeight == 0u )
+  {
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
+  }
+
+  if( inputWidth == 0u || inputHeight == 0u )
+  {
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
+  }
+}
+
+/**
+ * @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 )
+{
+  DALI_ASSERT_DEBUG( pixels && "Null pointer." );
+  DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
+  DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
+}
+
+/**
+ * @brief Assertions on params to functions averaging pairs of scanlines.
+ */
+inline void DebugAssertDualScanlineParameters(
+    const unsigned char * const scanline1,
+    const unsigned char * const scanline2,
+    unsigned char* const outputScanline,
+    const size_t widthInComponents )
+{
+  DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
+  DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
+  DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
+  DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
+  DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
+}
+
+} // namespace - unnamed
+
+/**
+ * @brief Implement ImageAttributes::ScaleTofill scaling mode.
+ *
+ * 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
+ * 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.
+ */
+Integration::BitmapPtr ProcessBitmapScaleToFill( Integration::BitmapPtr bitmap, const ImageAttributes& requestedAttributes );
+
+
+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:
+  if( bitmap )
+  {
+    bitmap = DownscaleBitmap( *bitmap, requestedAttributes );
+  }
+
+  // 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 );
+  }
+
+  // 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 )
+{
+  const unsigned loadedWidth = bitmap->GetImageWidth();
+  const unsigned loadedHeight = bitmap->GetImageHeight();
+  const unsigned desiredWidth = requestedAttributes.GetWidth();
+  const unsigned desiredHeight = requestedAttributes.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 )
+  {
+    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 Vector2 scaledByWidth = desiredDims * widthsRatio;
+    const float heightsRatio = loadedHeight / 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;
+    const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
+
+    // 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 );
+
+    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" );
+
+    // 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;
+      BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
+      Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
+      DALI_ASSERT_DEBUG( packedView );
+      const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
+      packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
+
+      const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+
+      const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
+      PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
+      DALI_ASSERT_DEBUG( srcPixels && destPixels );
+
+      // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
+      if( trimTopAndBottom )
+      {
+        memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
+      }
+      else
+      {
+        for( unsigned y = 0; y < newHeight; ++y )
+        {
+          memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * loadedWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
+        }
+      }
+
+      // Overwrite the loaded bitmap with the cropped version:
+      bitmap = croppedBitmap;
+    }
+  }
+
+  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;
+
+  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 )
+{
+  const unsigned int bitmapWidth  = bitmap.GetImageWidth();
+  const unsigned int bitmapHeight = bitmap.GetImageHeight();
+  const Size requestedSize = requestedAttributes.GetSize();
+
+  // 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) )
+  {
+    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 );
+
+        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 );
+        }
+
+        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) );
+      }
+    }
+  }
+  return Integration::BitmapPtr(&bitmap);
+}
+
+namespace
+{
+/**
+ * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
+ * @param test Which combination of the two dimensions matter for terminating the filtering.
+ * @param scaledWidth The width of the current downscaled image.
+ * @param scaledHeight The height of the current downscaled image.
+ * @param desiredWidth The target width for the downscaling.
+ * @param desiredHeight The target height for the downscaling.
+ */
+bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
+{
+  bool keepScaling = false;
+  const unsigned int nextWidth = scaledWidth >> 1u;
+  const unsigned int nextHeight = scaledHeight >> 1u;
+
+  if( nextWidth >= 1u && nextHeight >= 1u )
+  {
+    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;
+    }
+  }
+
+  return keepScaling;
+}
+
+/**
+ * @brief A shared implementation of the overall iterative 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
+ * on a single scanline, and a second for averaging pixels at corresponding
+ * positions on different scanlines.
+ **/
+template<
+  int BYTES_PER_PIXEL,
+  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 )
+{
+  if( pixels == 0 )
+  {
+    return;
+  }
+  ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
+
+  // Scale the image until it would be smaller than desired, stopping if the
+  // 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;
+    scaledHeight >>= 1u;
+
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
+
+    const unsigned int lastScanlinePair = scaledHeight - 1;
+
+    // Scale pairs of scanlines until any spare one at the end is dropped:
+    for( unsigned int y = 0; y <= lastScanlinePair; ++y )
+    {
+      // Scale two scanlines horizontally:
+      HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
+      HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
+
+      // 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
+      // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
+      AverageScanlines(
+          &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
+          &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
+          &pixels[y * scaledWidth * BYTES_PER_PIXEL],
+          scaledWidth );
+    }
+  }
+
+  ///@note: we could finish off with one of two mutually exclusive passes, one squashing horizontally as far as possible, and the other vertically, if we knew a following cpu point or bilinear filter would restore the desired aspect ratio.
+  outWidth = scaledWidth;
+  outHeight = scaledHeight;
+}
+
+}
+
+void HalveScanlineInPlaceRGB888(
+    unsigned char * const pixels,
+    const unsigned int width )
+{
+  DebugAssertScanlineParameters( pixels, width );
+
+  const unsigned int lastPair = EvenDown( width - 2 );
+
+  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
+  {
+    // Load all the byte pixel components we need:
+    const unsigned int c11 = pixels[pixel * 3];
+    const unsigned int c12 = pixels[pixel * 3 + 1];
+    const unsigned int c13 = pixels[pixel * 3 + 2];
+    const unsigned int c21 = pixels[pixel * 3 + 3];
+    const unsigned int c22 = pixels[pixel * 3 + 4];
+    const unsigned int c23 = pixels[pixel * 3 + 5];
+
+    // Save the averaged byte pixel components:
+    pixels[outPixel * 3]     = AverageComponent( c11, c21 );
+    pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
+    pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
+  }
+}
+
+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." );
+
+  uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
+
+  const unsigned int lastPair = EvenDown( width - 2 );
+
+  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
+  {
+    const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
+    alignedPixels[outPixel] = averaged;
+  }
+}
+
+void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
+{
+  DebugAssertScanlineParameters( pixels, width );
+  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
+
+  uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
+
+  const unsigned int lastPair = EvenDown( width - 2 );
+
+  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
+  {
+    const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
+    alignedPixels[outPixel] = averaged;
+  }
+}
+
+void HalveScanlineInPlace2Bytes(
+    unsigned char * const pixels,
+    const unsigned int width )
+{
+  DebugAssertScanlineParameters( pixels, width );
+
+  const unsigned int lastPair = EvenDown( width - 2 );
+
+  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
+  {
+    // Load all the byte pixel components we need:
+    const unsigned int c11 = pixels[pixel * 2];
+    const unsigned int c12 = pixels[pixel * 2 + 1];
+    const unsigned int c21 = pixels[pixel * 2 + 2];
+    const unsigned int c22 = pixels[pixel * 2 + 3];
+
+    // Save the averaged byte pixel components:
+    pixels[outPixel * 2]     = AverageComponent( c11, c21 );
+    pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
+  }
+}
+
+void HalveScanlineInPlace1Byte(
+    unsigned char * const pixels,
+    const unsigned int width )
+{
+  DebugAssertScanlineParameters( pixels, width );
+
+  const unsigned int lastPair = EvenDown( width - 2 );
+
+  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
+  {
+    // Load all the byte pixel components we need:
+    const unsigned int c1 = pixels[pixel];
+    const unsigned int c2 = pixels[pixel + 1];
+
+    // Save the averaged byte pixel component:
+    pixels[outPixel] = AverageComponent( c1, c2 );
+  }
+}
+
+/**
+ * @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 )
+{
+  DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
+
+  for( unsigned int component = 0; component < width; ++component )
+  {
+    outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
+  }
+}
+
+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 );
+
+  for( unsigned int component = 0; component < width * 2; ++component )
+  {
+    outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
+  }
+}
+
+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 );
+
+  for( unsigned int component = 0; component < width * 3; ++component )
+  {
+    outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
+  }
+}
+
+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." );
+  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
+  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
+
+  const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
+  const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
+  uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
+
+  for( unsigned int pixel = 0; pixel < width; ++pixel )
+  {
+    alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
+  }
+}
+
+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." );
+  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
+  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
+
+  const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
+  const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
+  uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
+
+  for( unsigned int pixel = 0; pixel < width; ++pixel )
+  {
+    alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
+  }
+}
+
+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 )
+{
+  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 )
+{
+  DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+}
+
+/**
+ * @copydoc DownscaleInPlacePow2RGB888
+ *
+ * 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 )
+{
+  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 )
+{
+  DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+}
+
+} /* namespace Platform */
+} /* namespace Internal */
+} /* namespace Dali */
diff --git a/platform-abstractions/portable/image-operations.h b/platform-abstractions/portable/image-operations.h
new file mode 100644 (file)
index 0000000..8a97ac5
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef DALI_INTERNAL_PLATFORM_IMAGE_OPERATIONS_H_
+#define DALI_INTERNAL_PLATFORM_IMAGE_OPERATIONS_H_
+
+// INTERNAL INCLUDES
+#include <dali/integration-api/bitmap.h>
+#include <dali/public-api/images/image-attributes.h>
+
+// EXTERNAL INCLUDES
+#include <stdint.h>
+
+namespace Dali
+{
+namespace Internal
+{
+namespace Platform
+{
+
+/**
+ * @brief Identify which combination of x and y dimensions matter in terminating iterative box filtering.
+ */
+enum BoxDimensionTest
+{
+  BoxDimensionTestEither,
+  BoxDimensionTestBoth,
+  BoxDimensionTestX,
+  BoxDimensionTestY
+};
+
+/**
+ * @brief Apply requested attributes to bitmap.
+ * @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.
+ */
+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.
+ **/
+Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const ImageAttributes& requestedAttributes );
+
+/**
+ * @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 );
+
+/**
+ * @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 );
+
+/**
+ * @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 );
+
+/**
+ * @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 );
+
+/**
+ * @brief Average adjacent pairs of pixels, overwriting the input array.
+ * @param[in,out] pixels The array of pixels to work on.
+ * @param[i]      width  The number of pixels in the array passed-in.
+ */
+void HalveScanlineInPlaceRGB888( unsigned char * pixels, unsigned int width );
+
+/**
+ * @copydoc HalveScanlineInPlaceRGB888
+ */
+void HalveScanlineInPlaceRGBA8888(
+    unsigned char * pixels,
+    unsigned int width );
+
+/**
+ * @copydoc HalveScanlineInPlaceRGB888
+ */
+void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width );
+
+/**
+ * @copydoc HalveScanlineInPlaceRGB888
+ */
+void HalveScanlineInPlace2Bytes(
+    unsigned char * pixels,
+    unsigned int width );
+
+/**
+ * @copydoc HalveScanlineInPlaceRGB888
+ */
+void HalveScanlineInPlace1Byte(
+    unsigned char * pixels,
+    unsigned int width );
+
+/**
+ * @brief Average pixels at corresponding offsets in two scanlines.
+ *
+ * outputScanline is allowed to alias scanline1.
+ * @param[in] scanline1 First scanline of pixels to average.
+ * @param[in] scanline2 Second scanline of pixels to average.
+ * @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 );
+
+/**
+ * @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 );
+
+/**
+ * @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 );
+
+/**
+ * @copydoc AverageScanlines1
+ */
+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 );
+
+/**
+ * @brief Inline functions exposed in header to allow unit testing.
+ */
+namespace
+{
+  /**
+   * @brief Average two integer arguments.
+   * @return The average of two uint arguments.
+   * @param[in] a First component to average.
+   * @param[in] b Second component to average.
+   **/
+  inline unsigned int AverageComponent( unsigned int a, unsigned int b )
+  {
+    unsigned int avg = (a + b) >> 1u;
+    return avg;
+  }
+
+  /**
+   * @brief Average a pair of RGB565 pixels.
+   * @return The average of two RGBA8888 pixels.
+   * @param[in] a First pixel to average.
+   * @param[in] b Second pixel to average
+   **/
+  inline uint32_t AveragePixelRGBA8888( uint32_t a, uint32_t b )
+  {
+    const unsigned int avg =
+      ((AverageComponent( (a & 0xff000000) >> 1u, (b & 0xff000000) >> 1u ) << 1u) & 0xff000000 ) +
+      (AverageComponent( a & 0x00ff0000, b & 0x00ff0000 ) & 0x00ff0000 ) +
+      (AverageComponent( a & 0x0000ff00, b & 0x0000ff00 ) & 0x0000ff00 ) +
+      (AverageComponent( a & 0x000000ff, b & 0x000000ff ) );
+    return avg;
+    ///@ToDo: Optimise by trying return (((a ^ b) & 0xfefefefeUL) >> 1) + (a & b);
+    ///@ToDo: Optimise for ARM using the single ARMV6 instruction: UHADD8  R4, R0, R5. This is not neon. It runs in the normal integer pipeline so there is no downside like a stall moving between integer and copro.
+  }
+
+  /**
+   * @brief Average a pair of RGB565 pixels.
+   * @param a[in] Low 16 bits hold a color value as RGB565 to average with parameter b.
+   * @param b[in] Low 16 bits hold a color value as RGB565 to average with parameter a.
+   * @return The average color of the two RGB565 pixels passed in, in the low 16 bits of the returned value.
+   **/
+  inline uint32_t AveragePixelRGB565( uint32_t a, uint32_t b )
+  {
+    const unsigned int avg =
+      (AverageComponent( a & 0xf800, b & 0xf800 ) & 0xf800 ) +
+      (AverageComponent( a & 0x7e0,  b & 0x7e0 )  & 0x7e0 ) +
+      (AverageComponent( a & 0x1f,   b & 0x1f ) );
+    return avg;
+  }
+
+} // namespace - unnamed
+} /* namespace Platform */
+} /* namespace Internal */
+} /* namespace Dali */
+
+#endif /* DALI_INTERNAL_PLATFORM_IMAGE_OPERATIONS_H_ */
index 8f34b65..5ea1f61 100755 (executable)
@@ -37,7 +37,8 @@ slp_platform_abstraction_src_files = \
   $(slp_platform_abstraction_src_dir)/image-loaders/loader-ico.cpp \
   $(slp_platform_abstraction_src_dir)/image-loaders/loader-ktx.cpp \
   $(slp_platform_abstraction_src_dir)/image-loaders/loader-wbmp.cpp \
-  $(slp_platform_abstraction_src_dir)/image-loaders/image-loader.cpp
+  $(slp_platform_abstraction_src_dir)/image-loaders/image-loader.cpp \
+  $(portable_platform_abstraction_src_dir)/image-operations.cpp
 
 slp_turbo_jpeg_loader = \
   $(slp_platform_abstraction_src_dir)/image-loaders/loader-jpeg-turbo.cpp
index 1877e9e..70a7118 100644 (file)
@@ -28,8 +28,7 @@
 #include "loader-ico.h"
 #include "loader-ktx.h"
 #include "loader-wbmp.h"
-
-#include <cstring>
+#include "image-operations.h"
 
 using namespace Dali::Integration;
 
@@ -272,94 +271,26 @@ bool ConvertStreamToBitmap(const ResourceType& resourceType, std::string path, F
 
       DALI_LOG_SET_OBJECT_STRING(bitmap, path);
       const BitmapResourceType& resType = static_cast<const BitmapResourceType&>(resourceType);
-      ImageAttributes attributes  = resType.imageAttributes;
+      const ImageAttributes& requestedAttributes = resType.imageAttributes; //< Original attributes.
+      ImageAttributes attributes  = resType.imageAttributes; //< r/w copy of the attributes.
 
       // Check for cancellation now we have hit the filesystem, done some allocation, and burned some cycles:
       // This won't do anything from synchronous API, it's only useful when called from another thread.
-      client.InterruptionPoint(); // Note: This can throw an exception.
+      client.InterruptionPoint(); // Note: By design, this can throw an exception
 
+      // Run the image type decoder:
+      // Note, this can overwrite the attributes parameter.
       result = function( fp, *bitmap, attributes, client );
 
       if (!result)
       {
-        DALI_LOG_WARNING("Unable to convert %s\n", path.c_str());
+        DALI_LOG_WARNING( "Unable to convert %s\n", path.c_str() );
         bitmap = 0;
       }
 
       // Apply the requested image attributes in best-effort fashion:
-      const ImageAttributes& requestedAttributes = resType.imageAttributes;
-      // 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 )
-      {
-        const unsigned loadedWidth = bitmap->GetImageWidth();
-        const unsigned loadedHeight = bitmap->GetImageHeight();
-        const unsigned desiredWidth = requestedAttributes.GetWidth();
-        const unsigned desiredHeight = requestedAttributes.GetHeight();
-
-        if( desiredWidth < 1U || desiredHeight < 1U )
-        {
-          DALI_LOG_WARNING( "Image scaling aborted for image %s as desired dimensions too small (%u, %u)\n.", path.c_str(), desiredWidth, desiredHeight );
-        }
-        else if( loadedWidth != desiredWidth || loadedHeight != 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 Vector2 scaledByWidth = desiredDims * widthsRatio;
-          const float heightsRatio = loadedHeight / 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;
-          const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
-
-          // 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 );
-
-          DALI_LOG_INFO( gLogFilter, Debug::General, "ImageAttributes::ScaleToFill - 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" );
-
-          // Make a new bitmap with the central part of the loaded one if required:
-          if( scanlinesToTrim > 0 || columnsToTrim > 0 ) ///@ToDo: Make this test a bit fuzzy (allow say a 5% difference).
-          {
-            // This won't do anything from synchronous API, it's only useful when called from another thread.
-            client.InterruptionPoint(); // Note: This can throw an exception.
-
-            const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
-            const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
-            BitmapPtr croppedBitmap = Bitmap::New( Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
-            Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
-            DALI_ASSERT_DEBUG( packedView );
-            const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
-            packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
-
-            const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
-
-            const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
-            PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
-            DALI_ASSERT_DEBUG( srcPixels && destPixels );
-
-            // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
-            if( trimTopAndBottom )
-            {
-              memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
-            }
-            else
-            {
-              for( unsigned y = 0; y < newHeight; ++y )
-              {
-                memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * loadedWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
-              }
-            }
-
-            // Overwrite the loaded bitmap with the cropped version:
-            bitmap = croppedBitmap;
-          }
-        }
-      }
+      client.InterruptionPoint(); // Note: By design, this can throw an exception
+      bitmap = Internal::Platform::ApplyAttributesToBitmap( bitmap, requestedAttributes );
     }
     else
     {
@@ -371,7 +302,6 @@ bool ConvertStreamToBitmap(const ResourceType& resourceType, std::string path, F
   return result;
 }
 
-
 ResourcePointer LoadResourceSynchronously( const Integration::ResourceType& resourceType, const std::string& resourcePath )
 {
   ResourcePointer resource;
index bcc0584..55ff4cf 100644 (file)
@@ -123,21 +123,6 @@ bool LoadPngHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &wi
 
   bool success = LoadPngHeader(fp, width, height, png, info);
 
-  if( success )
-  {
-    bool crop = (attributes.GetScalingMode() == Dali::ImageAttributes::ScaleToFill);
-    if(crop)
-    {
-      Size req((float) attributes.GetWidth(), (float) attributes.GetHeight());
-      const Size orig((float)width, (float)height);
-
-      // calculate actual width, height
-      req = FitScaleToFill(req, orig);
-
-      width = (int) req.width;
-      height = (int) req.height;
-    }
-  }
   return success;
 }
 
@@ -308,18 +293,8 @@ bool LoadBitmapFromPng( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, c
     bufferWidth = stride / bpp;
   }
 
-  /// @todo support more scaling types
-  bool crop = (attributes.GetScalingMode() == Dali::ImageAttributes::ScaleToFill);
-
-  if(crop)
-  {
-    pixels = new unsigned char[stride*bufferHeight];
-  }
-  else
-  {
-    // decode the whole image into bitmap buffer
-    pixels = bitmap.GetPackedPixelsProfile()->ReserveBuffer(pixelFormat, width, height, bufferWidth, bufferHeight);
-  }
+  // decode the whole image into bitmap buffer
+  pixels = bitmap.GetPackedPixelsProfile()->ReserveBuffer(pixelFormat, width, height, bufferWidth, bufferHeight);
 
   DALI_ASSERT_DEBUG(pixels);
   rows = (png_bytep*) malloc(sizeof(png_bytep) * height);
@@ -331,52 +306,9 @@ bool LoadBitmapFromPng( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, c
   // decode image
   png_read_image(png, rows);
 
-  // copy part of the buffer to bitmap
-  if(crop)
-  {
-    Size req((float) attributes.GetWidth(), (float) attributes.GetHeight());
-    const Size orig((float)width, (float)height);
-
-    // calculate actual width, height
-    req = FitScaleToFill(req, orig);
-
-    // modify attributes with result
-    attributes.SetSize((int) req.width, (int) req.height);
-    attributes.SetPixelFormat(pixelFormat);
-
-    bufferWidth  = GetTextureDimension(attributes.GetWidth());
-    bufferHeight = GetTextureDimension(attributes.GetHeight());
-
-    // cropped buffer's stride
-    int lstride = bufferWidth*bpp;
-
-    // calculate offsets
-    int x_offset = ((width  - attributes.GetWidth())  / 2) * bpp;
-    int y_offset = ((height - attributes.GetHeight()) / 2) * stride;
-
-    // allocate bitmap buffer using requested size
-    unsigned char *bitmapBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer(pixelFormat, attributes.GetWidth(), attributes.GetHeight(), bufferWidth, bufferHeight);
-
-    // copy memory area from y and x to bitmap buffer line-by-line
-    unsigned char *bufptr = bitmapBuffer;
-    unsigned char *lptr = pixels+y_offset+x_offset;
-    for(unsigned int i = 0; i < attributes.GetHeight(); ++i)
-    {
-      memcpy(bufptr, lptr, attributes.GetWidth()*bpp);
-      bufptr += lstride;
-      lptr += stride;
-    }
-
-    delete[] pixels;
-  }
-  else
-  {
-    // set the attributes
-     attributes.SetSize(width, height);
-     attributes.SetPixelFormat(pixelFormat);
-  }
-
-  bitmap.GetPackedPixelsProfile()->TestForTransparency();
+  // set the attributes
+  attributes.SetSize( width, height );
+  attributes.SetPixelFormat( pixelFormat );
 
   free(rows);