From: Andrew Cox Date: Tue, 2 Dec 2014 11:52:51 +0000 (+0000) Subject: Image scaling operations - FilterMode::Box implemented for all pixel formats X-Git-Tag: submit/tizen_common/20150108.143100~4^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;ds=sidebyside;h=refs%2Fchanges%2F15%2F32515%2F9;hp=9d226abd9aa4db50cf2a5466ff97ec11c2dbdff4;p=platform%2Fcore%2Fuifw%2Fdali-adaptor.git Image scaling operations - FilterMode::Box implemented for all pixel formats 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 --- diff --git a/automated-tests/src/dali-adaptor-internal/CMakeLists.txt b/automated-tests/src/dali-adaptor-internal/CMakeLists.txt index b677e9e..cf77527 100644 --- a/automated-tests/src/dali-adaptor-internal/CMakeLists.txt +++ b/automated-tests/src/dali-adaptor-internal/CMakeLists.txt @@ -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 index 0000000..7127312 --- /dev/null +++ b/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp @@ -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 + +#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& scanline, Dali::Vector& 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& scanline, Dali::Vector& 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& scanline, Dali::Vector& 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& scanline, Dali::Vector& 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& scanline1, Dali::Vector& scanline2, Dali::Vector& reference, Dali::Vector& 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& reference, Dali::Vector& 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 (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 (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 (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 (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 scanline; + Dali::Vector 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 scanline; + Dali::Vector 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 scanline; + Dali::Vector 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 scanline; + Dali::Vector 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 scanline1; + Dali::Vector scanline2; + Dali::Vector reference; + Dali::Vector 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 scanline1; + Dali::Vector scanline2; + Dali::Vector reference; + Dali::Vector 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 scanline1; + Dali::Vector scanline2; + Dali::Vector reference; + Dali::Vector 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 scanline1; + Dali::Vector scanline2; + Dali::Vector reference; + Dali::Vector 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; +} diff --git a/build/tizen/adaptor/Makefile.am b/build/tizen/adaptor/Makefile.am index 4b017b3..d029920 100644 --- a/build/tizen/adaptor/Makefile.am +++ b/build/tizen/adaptor/Makefile.am @@ -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 index 0000000..e6b9415 --- /dev/null +++ b/platform-abstractions/portable/image-operations.cpp @@ -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 +#include +#include +#include + +// EXTERNAL INCLUDES +#include + +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): + * + * LOG_IMAGE_OPERATIONS=0 dali-demo #< off + * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose + * + */ +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(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); + + uint32_t* const alignedPixels = reinterpret_cast(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(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." ); + + uint16_t* const alignedPixels = reinterpret_cast(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(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); + DALI_ASSERT_DEBUG( ((reinterpret_cast(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); + DALI_ASSERT_DEBUG( ((reinterpret_cast(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); + + const uint32_t* const alignedScanline1 = reinterpret_cast(scanline1); + const uint32_t* const alignedScanline2 = reinterpret_cast(scanline2); + uint32_t* const alignedOutput = reinterpret_cast(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(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." ); + DALI_ASSERT_DEBUG( ((reinterpret_cast(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." ); + DALI_ASSERT_DEBUG( ((reinterpret_cast(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." ); + + const uint16_t* const alignedScanline1 = reinterpret_cast(scanline1); + const uint16_t* const alignedScanline2 = reinterpret_cast(scanline2); + uint16_t* const alignedOutput = reinterpret_cast(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(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 index 0000000..8a97ac5 --- /dev/null +++ b/platform-abstractions/portable/image-operations.h @@ -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 +#include + +// EXTERNAL INCLUDES +#include + +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_ */ diff --git a/platform-abstractions/slp/file.list b/platform-abstractions/slp/file.list index 8f34b65..5ea1f61 100755 --- a/platform-abstractions/slp/file.list +++ b/platform-abstractions/slp/file.list @@ -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 diff --git a/platform-abstractions/slp/image-loaders/image-loader.cpp b/platform-abstractions/slp/image-loaders/image-loader.cpp index 1877e9e..70a7118 100644 --- a/platform-abstractions/slp/image-loaders/image-loader.cpp +++ b/platform-abstractions/slp/image-loaders/image-loader.cpp @@ -28,8 +28,7 @@ #include "loader-ico.h" #include "loader-ktx.h" #include "loader-wbmp.h" - -#include +#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(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; diff --git a/platform-abstractions/slp/image-loaders/loader-png.cpp b/platform-abstractions/slp/image-loaders/loader-png.cpp index bcc0584..55ff4cf 100644 --- a/platform-abstractions/slp/image-loaders/loader-png.cpp +++ b/platform-abstractions/slp/image-loaders/loader-png.cpp @@ -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);