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