From 74197c0d3cc35039c3059e61c6d8c4c755c2e1f2 Mon Sep 17 00:00:00 2001 From: Andrew Cox Date: Mon, 9 Mar 2015 11:45:37 +0000 Subject: [PATCH] Image scaling operations - FilterModes Linear BoxThenLinear Change-Id: I81505c1f0fff2355fa3affbdfc6467e08e10b3aa Signed-off-by: Andrew Cox --- .../utc-Dali-ImageOperations.cpp | 50 +++ .../portable/image-operations.cpp | 378 ++++++++++++++++++--- platform-abstractions/portable/image-operations.h | 112 +++++- 3 files changed, 485 insertions(+), 55 deletions(-) diff --git a/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp b/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp index 9142fa7..ffb7dd7 100644 --- a/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp +++ b/automated-tests/src/dali-adaptor-internal/utc-Dali-ImageOperations.cpp @@ -1471,3 +1471,53 @@ int UtcDaliImageOperationsVector2Uint16(void) END_TEST; } + +/** + * @brief Test the four-tap linear blending for single-byte modes. + */ +int UtcDaliImageOperationsBilinearFilter1BPP(void) +{ + // Zeros blend to zero: + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 0, 0, 0, 0, 0 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 0, 0, 0, 32768, 0 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 0, 0, 0, 65535, 0 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 0, 0, 0, 0, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 0, 0, 0, 0, 65535 ), TEST_LOCATION ); + + // Ones and zeros average to 0.5: + DALI_TEST_EQUALS( 127u, BilinearFilter1Component( 255, 0, 0, 255, 32768, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 127u, BilinearFilter1Component( 0, 255, 0, 255, 32768, 32768 ), TEST_LOCATION ); + + // Quarters ones average to 0.25: + DALI_TEST_EQUALS( 64u, BilinearFilter1Component( 255, 0, 0, 0, 32768, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 64u, BilinearFilter1Component( 0, 255, 0, 0, 32768, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 64u, BilinearFilter1Component( 0, 0, 255, 0, 32768, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 64u, BilinearFilter1Component( 0, 0, 0, 255, 32768, 32768 ), TEST_LOCATION ); + + // Horizontal blends: + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 255, 0, 255, 0, 32768 ), TEST_LOCATION ); + for( unsigned y = 0; y < 65536u; y += 256 ) + { + // Vertical blends don't change result in this case as there is no vertical gradient in inputs: + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 255, 0, 255, 0, y ), TEST_LOCATION ); + } + DALI_TEST_EQUALS( 5u, BilinearFilter1Component( 0, 255, 0, 255, 1233, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 29u, BilinearFilter1Component( 0, 255, 0, 255, 7539, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 29u, BilinearFilter1Component( 0, 255, 0, 255, 7539, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 67u, BilinearFilter1Component( 0, 255, 0, 255, 17291, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 123u, BilinearFilter1Component( 0, 255, 0, 255, 31671, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 184u, BilinearFilter1Component( 0, 255, 0, 255, 47231, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 207u, BilinearFilter1Component( 0, 255, 0, 255, 53129, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 239u, BilinearFilter1Component( 0, 255, 0, 255, 61392, 32768 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 255u, BilinearFilter1Component( 0, 255, 0, 255, 65535, 32768 ), TEST_LOCATION ); + + // Vertical Blends: + DALI_TEST_EQUALS( 0u, BilinearFilter1Component( 0, 0, 255, 255, 32768, 0 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 60u, BilinearFilter1Component( 0, 0, 255, 255, 32768, 15379 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 130u, BilinearFilter1Component( 0, 0, 255, 255, 32768, 33451 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 186u, BilinearFilter1Component( 0, 0, 255, 255, 32768, 47836 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 244u, BilinearFilter1Component( 0, 0, 255, 255, 32768, 62731 ), TEST_LOCATION ); + DALI_TEST_EQUALS( 255u, BilinearFilter1Component( 0, 0, 255, 255, 32768, 65535 ), TEST_LOCATION ); + + END_TEST; +} diff --git a/platform-abstractions/portable/image-operations.cpp b/platform-abstractions/portable/image-operations.cpp index 7910c22..7ccfa55 100644 --- a/platform-abstractions/portable/image-operations.cpp +++ b/platform-abstractions/portable/image-operations.cpp @@ -33,10 +33,49 @@ namespace Platform namespace { + using Integration::Bitmap; using Integration::BitmapPtr; typedef unsigned char PixelBuffer; +/** + * @brief 4 byte pixel structure. + */ +struct Pixel4Bytes +{ + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load. + +/** + * @brief RGB888 pixel structure. + */ +struct Pixel3Bytes +{ + uint8_t r; + uint8_t g; + uint8_t b; +} __attribute__((packed, aligned(1))); + +/** + * @brief RGB565 pixel typedefed from a short. + * + * Access fields by manual shifting and masking. + */ +typedef uint16_t PixelRGB565; + +/** + * @brief a Pixel composed of two independent byte components. + */ +struct Pixel2Bytes +{ + uint8_t l; + uint8_t a; +} __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load. + + #if defined(DEBUG_ENABLED) /** * Disable logging of image operations or make it verbose from the commandline @@ -299,16 +338,27 @@ ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions } /** - * @brief Construct a bitmap object from a copy of the pixel array passed in. + * @brief Construct a bitmap with format and dimensions requested. */ -BitmapPtr MakeBitmap(const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height ) +BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height ) { - DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." ); DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." ); // Allocate a pixel buffer to hold the image passed in: Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD ); newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height ); + return newBitmap; +} + +/** + * @brief Construct a bitmap object from a copy of the pixel array passed in. + */ +BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height ) +{ + DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." ); + + // Allocate a pixel buffer to hold the image passed in: + Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height ); // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap: memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) ); @@ -470,17 +520,29 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const unsigned int filteredHeight = filteredDimensions.GetHeight(); // Run a filter to scale down the bitmap if it needs it: - if( (filteredWidth < shrunkWidth || filteredHeight < shrunkHeight) && - (filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::Linear || - filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear) ) + bool filtered = false; + if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight ) { - ///@note If a linear filter is requested, we do our best with a point filter for now. - PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, bitmap.GetBuffer(), filteredWidth, filteredHeight ); - - outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, filteredWidth, filteredHeight ); + if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear || + filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::BoxThenNearest ) + { + outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight ); + if( outputBitmap ) + { + if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear ) + { + LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions ); + } + else + { + PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight ); + } + filtered = true; + } + } } // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied: - else if( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) + if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) ) { outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight ); } @@ -821,6 +883,10 @@ void DownscaleInPlacePow2( unsigned char * const pixels, { Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight ); } + else + { + DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." ); + } } } else @@ -829,11 +895,11 @@ void DownscaleInPlacePow2( unsigned char * const pixels, } } -void DownscaleInPlacePow2RGB888( unsigned char * const pixels, - const unsigned int inputWidth, - const unsigned int inputHeight, - const unsigned int desiredWidth, - const unsigned int desiredHeight, +void DownscaleInPlacePow2RGB888( unsigned char *pixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned int desiredWidth, + unsigned int desiredHeight, BoxDimensionTest dimensionTest, unsigned& outWidth, unsigned& outHeight ) @@ -841,11 +907,11 @@ void DownscaleInPlacePow2RGB888( unsigned char * const pixels, 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, +void DownscaleInPlacePow2RGBA8888( unsigned char * pixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned int desiredWidth, + unsigned int desiredHeight, BoxDimensionTest dimensionTest, unsigned& outWidth, unsigned& outHeight ) @@ -871,11 +937,11 @@ void DownscaleInPlacePow2RGB565( unsigned char * pixels, * * 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, +void DownscaleInPlacePow2ComponentPair( unsigned char *pixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned int desiredWidth, + unsigned int desiredHeight, BoxDimensionTest dimensionTest, unsigned& outWidth, unsigned& outHeight ) @@ -898,9 +964,15 @@ void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels, namespace { -// Point sample an image to a new resolution (like GL_NEAREST). +/** + * @brief Point sample an image to a new resolution (like GL_NEAREST). + * + * Template is used purely as a type-safe code generator in this one + * compilation unit. Generated code is inlined into type-specific wrapper + * functions below which are exported to rest of module. + */ template -void PointSampleAddressablePixels( const uint8_t * inPixels, +inline void PointSampleAddressablePixels( const uint8_t * inPixels, unsigned int inputWidth, unsigned int inputHeight, uint8_t * outPixels, @@ -950,9 +1022,7 @@ void PointSampleAddressablePixels( const uint8_t * inPixels, } -/* - * RGBA8888 - */ +// RGBA8888 void PointSample4BPP( const unsigned char * inPixels, unsigned int inputWidth, unsigned int inputHeight, @@ -963,9 +1033,7 @@ void PointSample4BPP( const unsigned char * inPixels, PointSampleAddressablePixels( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); } -/* - * RGB565, LA88 - */ +// RGB565, LA88 void PointSample2BPP( const unsigned char * inPixels, unsigned int inputWidth, unsigned int inputHeight, @@ -976,9 +1044,7 @@ void PointSample2BPP( const unsigned char * inPixels, PointSampleAddressablePixels( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); } -/* - * L8, A8 - */ +// L8, A8 void PointSample1BPP( const unsigned char * inPixels, unsigned int inputWidth, unsigned int inputHeight, @@ -989,8 +1055,7 @@ void PointSample1BPP( const unsigned char * inPixels, PointSampleAddressablePixels( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); } -/* - * RGB888 +/* RGB888 * RGB888 is a special case as its pixels are not aligned addressable units. */ void PointSample3BPP( const uint8_t * inPixels, @@ -1016,20 +1081,22 @@ void PointSample3BPP( const uint8_t * inPixels, unsigned int inY = 0; //< 16.16 fixed-point input image y-coord. for( unsigned int outY = 0; outY < desiredHeight; ++outY ) { - const uint8_t* const inScanline = &inPixels[inputWidth * (inY >> 16u) * BYTES_PER_PIXEL]; + const unsigned int integerY = (inY + (1u << 15u)) >> 16u; + const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL]; uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL]; unsigned int inX = 0; //< 16.16 fixed-point input image x-coord. for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL ) { - // Truncate the fixed-point input coordinate to the address of the input pixel to sample: - const uint8_t* const inPixelAddress = &inScanline[(inX >> 16u) * BYTES_PER_PIXEL]; + // Round the fixed-point input coordinate to the address of the input pixel to sample: + const unsigned int integerX = (inX + (1u << 15u)) >> 16u; + const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL]; // Issue loads for all pixel color components up-front: const unsigned int c0 = inPixelAddress[0]; const unsigned int c1 = inPixelAddress[1]; const unsigned int c2 = inPixelAddress[2]; - ///@ToDo: Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads. + ///@ToDo: Optimise - Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads, versus using an RGB packed, aligned(1) struct and letting compiler pick a strategy. // Output the pixel components: outScanline[outX] = c0; @@ -1056,13 +1123,13 @@ void PointSample( const unsigned char * inPixels, // Check the pixel format is one that is supported: if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 ) { - if( pixelFormat == Pixel::RGBA8888 ) + if( pixelFormat == Pixel::RGB888 ) { - PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); + PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); } - else if( pixelFormat == Pixel::RGB888 ) + else if( pixelFormat == Pixel::RGBA8888 ) { - PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); + PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); } else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 ) { @@ -1072,6 +1139,10 @@ void PointSample( const unsigned char * inPixels, { PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); } + else + { + DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." ); + } } else { @@ -1079,6 +1150,219 @@ void PointSample( const unsigned char * inPixels, } } +// Linear sampling group below + +namespace +{ + +/** @brief Blend 4 pixels together using horizontal and vertical weights. */ +inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ) +{ + return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical ); +} + +/** @copydoc BilinearFilter1BPPByte */ +inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ) +{ + Pixel2Bytes pixel; + pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical ); + pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical ); + return pixel; +} + +/** @copydoc BilinearFilter1BPPByte */ +inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ) +{ + Pixel3Bytes pixel; + pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical ); + pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical ); + pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical ); + return pixel; +} + +/** @copydoc BilinearFilter1BPPByte */ +inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ) +{ + const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) + + (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) + + BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical ); + return pixel; +} + +/** @copydoc BilinearFilter1BPPByte */ +inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ) +{ + Pixel4Bytes pixel; + pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical ); + pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical ); + pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical ); + pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical ); + return pixel; +} + +/** + * @brief Generic version of bilinear sampling image resize function. + * @note Limited to one compilation unit and exposed through type-specific + * wrapper functions below. + */ +template< + typename PIXEL, + PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ), + bool DEBUG_ASSERT_ALIGNMENT +> +inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + const unsigned int inputWidth = inputDimensions.GetWidth(); + const unsigned int inputHeight = inputDimensions.GetHeight(); + const unsigned int desiredWidth = desiredDimensions.GetWidth(); + const unsigned int desiredHeight = desiredDimensions.GetHeight(); + + DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) || + (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) && + "Input and output buffers cannot overlap."); + if( DEBUG_ASSERT_ALIGNMENT ) + { + DALI_ASSERT_DEBUG( ((uint64_t) inPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." ); + DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." ); + } + + if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u ) + { + return; + } + const PIXEL* const inAligned = reinterpret_cast(inPixels); + PIXEL* const outAligned = reinterpret_cast(outPixels); + const unsigned int deltaX = (inputWidth << 16u) / desiredWidth; + const unsigned int deltaY = (inputHeight << 16u) / desiredHeight; + + unsigned int inY = 0; + for( unsigned int outY = 0; outY < desiredHeight; ++outY ) + { + PIXEL* const outScanline = &outAligned[desiredWidth * outY]; + + // Find the two scanlines to blend and the weight to blend with: + const unsigned int integerY1 = inY >> 16u; + const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1; + const unsigned int inputYWeight = inY & 65535u; + + DALI_ASSERT_DEBUG( integerY1 < inputHeight ); + DALI_ASSERT_DEBUG( integerY2 < inputHeight ); + + const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1]; + const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2]; + + unsigned int inX = 0; + for( unsigned int outX = 0; outX < desiredWidth; ++outX ) + { + // Work out the two pixel scanline offsets for this cluster of four samples: + const unsigned int integerX1 = inX >> 16u; + const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1; + + // Execute the loads: + const PIXEL pixel1 = inScanline1[integerX1]; + const PIXEL pixel2 = inScanline2[integerX1]; + const PIXEL pixel3 = inScanline1[integerX2]; + const PIXEL pixel4 = inScanline2[integerX2]; + ///@ToDo Optimise - for 1 and 2 and 4 byte types to execute a single 2, 4, or 8 byte load per pair (caveat clamping) and let half of them be unaligned. + + // Weighted bilinear filter: + const unsigned int inputXWeight = inX & 65535u; + outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight ); + + inX += deltaX; + } + inY += deltaY; + } +} + +} + +// Format-specific linear scaling instantiations: + +void LinearSample1BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSample2BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSampleRGB565( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSample3BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSample4BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +// Dispatch to a format-appropriate linear sampling function: +void LinearSample( const unsigned char * __restrict__ inPixels, + ImageDimensions inDimensions, + Pixel::Format pixelFormat, + unsigned char * __restrict__ outPixels, + ImageDimensions outDimensions ) +{ + // Check the pixel format is one that is supported: + if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 ) + { + if( pixelFormat == Pixel::RGB888 ) + { + LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions ); + } + else if( pixelFormat == Pixel::RGBA8888 ) + { + LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions ); + } + else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 ) + { + LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions ); + } + else if( pixelFormat == Pixel::LA88 ) + { + LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions ); + } + else if ( pixelFormat == Pixel::RGB565 ) + { + LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions ); + } + else + { + DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." ); + } + } + else + { + DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) ); + } +} + } /* namespace Platform */ } /* namespace Internal */ } /* namespace Dali */ diff --git a/platform-abstractions/portable/image-operations.h b/platform-abstractions/portable/image-operations.h index 2d26725..a8cd6a0 100644 --- a/platform-abstractions/portable/image-operations.h +++ b/platform-abstractions/portable/image-operations.h @@ -18,13 +18,13 @@ #ifndef DALI_INTERNAL_PLATFORM_IMAGE_OPERATIONS_H_ #define DALI_INTERNAL_PLATFORM_IMAGE_OPERATIONS_H_ +// EXTERNAL INCLUDES +#include + // INTERNAL INCLUDES #include #include -// EXTERNAL INCLUDES -#include - namespace Dali { namespace Internal @@ -127,11 +127,6 @@ private: }; /** - * @brief Human readable text output. - */ -std::ostream& operator<<( std::ostream& o, const Vector2Uint16& vector ); - -/** * @brief The integer dimensions of an image or a region of an image packed into * 16 bits per component. * @note This can only be used for images of up to 65535 x 65535 pixels. @@ -338,6 +333,71 @@ void PointSample1BPP( const unsigned char * inPixels, unsigned int desiredWidth, unsigned int desiredHeight ); +/** + * @brief Resample input image to output image using a bilinear filter. + * + * Each output pixel is formed of a weighted sum of a 2x2 block of four input + * pixels + * @pre inPixels must not alias outPixels. The input image should be a totally + * separate buffer from the input one. + */ +void LinearSample( const unsigned char * __restrict__ inPixels, + ImageDimensions inDimensions, + Pixel::Format pixelFormat, + unsigned char * __restrict__ outPixels, + ImageDimensions outDimensions ); + +/** + * @copydoc LinearSample + * + * Specialised for one byte per pixel formats. + */ +void LinearSample1BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ); + +/** + * @copydoc LinearSample + * + * Specialised for two byte per pixel formats. + */ +void LinearSample2BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ); + +/** + * @copydoc LinearSample + * + * Specialised for RGB565 16 bit pixel format. + */ +void LinearSampleRGB565( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ); + +/** + * @copydoc LinearSample + * + * Specialised for three byte per pixel formats like RGB888. + */ +void LinearSample3BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ); + +/** + * @copydoc LinearSample + * + * Specialised for four byte per pixel formats like RGBA888. + * @note, If used on RGBA8888, the A component will be blended independently. + */ +void LinearSample4BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ); + /**@}*/ /** @@ -472,6 +532,42 @@ inline uint32_t AveragePixelRGB565( uint32_t a, uint32_t b ) return avg; } +/** @return The weighted blend of two integers as a 16.16 fixed-point number, given a 0.16 fixed-point blending factor. */ +inline unsigned int WeightedBlendIntToFixed1616(unsigned int a, unsigned int b, unsigned int fractBlend ) +{ + DALI_ASSERT_DEBUG( fractBlend <= 65535u && "Factor should be in 0.16 fixed-point." ); + const unsigned int weightedAFixed = a * (65535u - fractBlend); + const unsigned int weightedBFixed = b * fractBlend; + const unsigned blended = (weightedAFixed + weightedBFixed); + return blended; +} + +/** @brief Blend two 16.16 inputs to give a 16.32 output. */ +inline uint64_t WeightedBlendFixed1616ToFixed1632(unsigned int a, unsigned int b, unsigned int fractBlend ) +{ + DALI_ASSERT_DEBUG( fractBlend <= 65535u && "Factor should be in 0.16 fixed-point." ); + // Blend while promoting intermediates to 16.32 fixed point: + const uint64_t weightedAFixed = uint64_t(a) * (65535u - fractBlend); + const uint64_t weightedBFixed = uint64_t(b) * fractBlend; + const uint64_t blended = (weightedAFixed + weightedBFixed); + return blended; +} + +/** + * @brief Blend 4 taps into one value using horizontal and vertical weights. + */ +inline unsigned int BilinearFilter1Component(unsigned int tl, unsigned int tr, unsigned int bl, unsigned int br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ) +{ + DALI_ASSERT_DEBUG( fractBlendHorizontal <= 65535u && "Factor should be in 0.16 fixed-point." ); + DALI_ASSERT_DEBUG( fractBlendVertical <= 65535u && "Factor should be in 0.16 fixed-point." ); + // + const unsigned int topBlend = WeightedBlendIntToFixed1616( tl, tr, fractBlendHorizontal ); + const unsigned int botBlend = WeightedBlendIntToFixed1616( bl, br, fractBlendHorizontal ); + const uint64_t blended2x2 = WeightedBlendFixed1616ToFixed1632( topBlend, botBlend, fractBlendVertical ); + const unsigned int rounded = (blended2x2 + (1u << 31u) ) >> 32u; + return rounded; +} + /**@}*/ } /* namespace Platform */ -- 2.7.4