*/
#include <dali-test-suite-utils.h>
-
#include "platform-abstractions/portable/image-operations.h"
+#include <dali/public-api/common/ref-counted-dali-vector.h>
+
+#include <sys/mman.h>
using namespace Dali::Internal::Platform;
/**
* @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 );
END_TEST;
}
+
+namespace
+{
+
+void MakeSingleColorImageRGBA8888( unsigned int width, unsigned int height, uint32_t *inputImage )
+{
+ const uint32_t inPixel = PixelRGBA8888( 255, 192, 128, 64 );
+ for( unsigned int i = 0; i < width * height; ++i )
+ {
+ inputImage[i] = inPixel;
+ }
+}
+
+/**
+ * @brief Allocate an image buffer with protected pages to top and tail it and
+ * SEGV if an operation strays into them.
+ */
+void MakeGuardedOutputImageRGBA8888( unsigned int desiredWidth, unsigned int desiredHeight, uint32_t *& outputBuffer, uint32_t *& outputImage )
+{
+ const size_t outputBufferSize = getpagesize() + sizeof(uint32_t) * desiredWidth * desiredHeight + getpagesize();
+ outputBuffer = (uint32_t *) valloc( outputBufferSize );
+ mprotect( outputBuffer, getpagesize(), PROT_READ );
+ mprotect( ((char*) outputBuffer) + outputBufferSize - getpagesize(), getpagesize(), PROT_READ );
+ outputImage = outputBuffer + getpagesize() / sizeof(outputBuffer[0]);
+}
+
+/**
+ * @brief Allocate a buffer of pages that are read-only, that is big enough for the number of pixels passed-in.
+ */
+uint32_t* AllocateReadOnlyPagesRGBA( unsigned int numPixels )
+{
+ const unsigned int numWholePages = (numPixels * sizeof(uint32_t)) / getpagesize();
+ bool needExtraPage = (numPixels * sizeof(uint32_t)) % getpagesize() != 0;
+ const size_t outputBufferSize = (numWholePages + (needExtraPage ? 1 : 0)) * getpagesize();
+ uint32_t * outputBuffer = (uint32_t *) valloc( outputBufferSize );
+ mprotect( outputBuffer, outputBufferSize, PROT_READ );
+
+ return outputBuffer;
+}
+
+/**
+ * @brief Free a buffer of pages that are read-only.
+ */
+void FreeReadOnlyPagesRGBA( uint32_t * pages, unsigned int numPixels )
+{
+ const size_t bufferSize = numPixels * 4;
+ mprotect( pages, bufferSize, PROT_READ | PROT_WRITE );
+ free( pages );
+}
+
+/*
+ * @brief Make an image with a checkerboard pattern.
+ * @note This is an easy pattern to scan for correctness after a downscaling test.
+ */
+Dali::IntrusivePtr<Dali::RefCountedVector<uint32_t> > MakeCheckerboardImageRGBA8888( unsigned int width, unsigned int height, unsigned int checkerSize )
+{
+ const unsigned int imageWidth = width * checkerSize;
+ const unsigned int imageHeight = height * checkerSize;
+ Dali::IntrusivePtr<Dali::RefCountedVector<uint32_t> > image = new Dali::RefCountedVector<uint32_t>;
+ image->GetVector().Resize( imageWidth * imageHeight );
+
+ uint32_t rowColor = 0xffffffff;
+ for( unsigned int cy = 0; cy < height; ++cy )
+ {
+ rowColor = rowColor == 0xffffffff ? 0xff000000 : 0xffffffff;
+ uint32_t checkColor = rowColor;
+ for( unsigned int cx = 0; cx < width; ++cx )
+ {
+ checkColor = checkColor == 0xffffffff ? 0xff000000 : 0xffffffff;
+ uint32_t paintedColor = checkColor;
+ // Draw 3 special case checks as r,g,b:
+ if(cx == 0 && cy == 0)
+ {
+ paintedColor = 0xff0000ff;// Red
+ }
+ else if(cx == 7 && cy == 0)
+ {
+ paintedColor = 0xff00ff00;// Green
+ }
+ else if(cx == 7 && cy == 7)
+ {
+ paintedColor = 0xffff0000;// blue
+ }
+ uint32_t * check = &image->GetVector()[ (cy * checkerSize * imageWidth) + (cx * checkerSize)];
+ for( unsigned int py = 0; py < checkerSize; ++py )
+ {
+ uint32_t * checkLine = check + py * imageWidth;
+ for( unsigned int px = 0; px < checkerSize; ++px )
+ {
+ checkLine[px] = paintedColor;
+ }
+ }
+ }
+ }
+
+ return image;
+}
+
+}
+
+/**
+ * @brief Test that a scaling doesn't stray outside the bounds of the destination image.
+ *
+ * The test allocates a destination buffer that is an exact multiple of the page size
+ * with guard pages at either end.
+ */
+int UtcDaliImageOperationsPointSampleRGBA888InBounds(void)
+{
+ const unsigned int inputWidth = 163;
+ const unsigned int inputHeight = 691;
+ const unsigned int destinationBufferSize = 4096 * 4;
+ const unsigned int desiredWidth = 64;
+ const unsigned int desiredHeight = destinationBufferSize / desiredWidth; // (256)
+
+ uint32_t inputImage[ inputWidth * inputHeight ];
+
+ // Allocate an output image buffer with read-only guard pages at either end:
+ // The test will segfault if it strays into the guard pages.
+ uint32_t *outputBuffer, *outputImage;
+ MakeGuardedOutputImageRGBA8888( desiredWidth, desiredHeight, outputBuffer, outputImage );
+
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, inputWidth, inputHeight, (unsigned char*) outputImage, desiredWidth, desiredHeight );
+
+ FreeReadOnlyPagesRGBA( outputBuffer, desiredWidth * desiredHeight );
+
+ //! The only real test is whether the above code SEGVs, but do a fake test so we pass if that hasn't happened:
+ DALI_TEST_EQUALS( true, true, TEST_LOCATION );
+
+ END_TEST;
+}
+
+/**
+ * @brief Test the right pixels are generated when downsampling a checkerboard into a small image.
+ */
+int UtcDaliImageOperationsPointSampleCheckerboardRGBA888(void)
+{
+ Dali::IntrusivePtr<Dali::RefCountedVector<uint32_t> > image = MakeCheckerboardImageRGBA8888( 8, 8, 32 );
+ const unsigned int desiredWidth = 8;
+ const unsigned int desiredHeight = 8;
+
+ uint32_t outputImage[ desiredWidth * desiredHeight ];
+
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) &image->GetVector()[0], 256, 256, (unsigned char*) outputImage, desiredWidth, desiredHeight );
+
+ DALI_TEST_EQUALS( outputImage[0], 0xff0000ff, TEST_LOCATION ); // < Red corner pixel
+ DALI_TEST_EQUALS( outputImage[7], 0xff00ff00, TEST_LOCATION ); // < Green corner pixel
+ DALI_TEST_EQUALS( outputImage[8*8-1], 0xffff0000, TEST_LOCATION ); // < Blue corner pixel
+
+ DALI_TEST_EQUALS( outputImage[1], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[2], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[3], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[4], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[5], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[6], 0xffffffff, TEST_LOCATION ); // < white pixel
+
+ // Second scanline:
+ DALI_TEST_EQUALS( outputImage[8+0], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[8+1], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[8+2], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[8+3], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[8+4], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[8+5], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[8+6], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[8+7], 0xffffffff, TEST_LOCATION ); // < white pixel
+
+ // Third scanline:
+ DALI_TEST_EQUALS( outputImage[16+0], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[16+1], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[16+2], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[16+3], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[16+4], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[16+5], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[16+6], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[16+7], 0xff000000, TEST_LOCATION ); // < black pixel
+
+ // ... could do more scanlines (there are 8)
+
+ // Sample a few more pixels:
+
+ // Diagonals:
+ DALI_TEST_EQUALS( outputImage[24+3], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[32+4], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[40+5], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[48+6], 0xffffffff, TEST_LOCATION ); // < white pixel
+ DALI_TEST_EQUALS( outputImage[24+4], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[32+3], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[40+2], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[48+1], 0xff000000, TEST_LOCATION ); // < black pixel
+ DALI_TEST_EQUALS( outputImage[56+0], 0xff000000, TEST_LOCATION ); // < black pixel
+
+ END_TEST;
+}
+
+/**
+ * @brief Test that a scaling preserves input color in destination image.
+ */
+int UtcDaliImageOperationsPointSampleRGBA888PixelsCorrectColor(void)
+{
+ const unsigned int inputWidth = 137;
+ const unsigned int inputHeight = 571;
+ const unsigned int desiredWidth = 59;
+ const unsigned int desiredHeight = 257;
+
+ uint32_t inputImage[ inputWidth * inputHeight ];
+ MakeSingleColorImageRGBA8888( inputWidth, inputHeight, inputImage );
+
+ uint32_t *outputBuffer, *outputImage;
+ MakeGuardedOutputImageRGBA8888( desiredWidth, desiredHeight, outputBuffer, outputImage );
+
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, inputWidth, inputHeight, (unsigned char*) outputImage, desiredWidth, desiredHeight );
+
+ // Check that all the output pixels are the right color:
+ const uint32_t reference = inputImage[ inputWidth * inputHeight / 2];
+ unsigned int differentColorCount = 0;
+ for( unsigned int i = 0; i < desiredWidth * desiredHeight; ++i )
+ {
+ if( outputImage[i] != reference )
+ {
+ ++differentColorCount;
+ }
+ }
+
+ FreeReadOnlyPagesRGBA( outputBuffer, desiredWidth * desiredHeight );
+
+ DALI_TEST_EQUALS( 0U, differentColorCount, TEST_LOCATION );
+
+ END_TEST;
+}
+
+/**
+ * @brief Test that scaling down to a 1x1 image works.
+ */
+int UtcDaliImageOperationsPointSampleRGBA888ScaleToSinglePixel(void)
+{
+ const unsigned int desiredWidth = 1;
+ const unsigned int desiredHeight = 1;
+
+ uint32_t inputImage[ 1024 * 1024 ];
+ MakeSingleColorImageRGBA8888( 1024, 1024, inputImage );
+ uint32_t outputImage = 0;
+
+ // Try several different starting image sizes:
+
+ // 1x1 -> 1x1:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1, 1, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+ outputImage = 0;
+
+ // Single-pixel wide tall stripe:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1, 1024, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+ outputImage = 0;
+
+ // Single-pixel tall, wide strip:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1024, 1, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+ outputImage = 0;
+
+ // Square mid-size image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 103, 103, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+ outputImage = 0;
+
+ // Wide mid-size image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 313, 79, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+ outputImage = 0;
+
+ // Tall mid-size image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 53, 467, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, inputImage[0], TEST_LOCATION );
+ outputImage = 0;
+
+ // 0 x 0 input image (make sure output not written to):
+ outputImage = 0xDEADBEEF;
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 0, 0, (unsigned char*) &outputImage, desiredWidth, desiredHeight );
+ DALI_TEST_EQUALS( outputImage, 0xDEADBEEF, TEST_LOCATION );
+ outputImage = 0;
+
+ END_TEST;
+}
+
+/**
+ * @brief Test that downsampling to 0 - area images is a NOP and does not modify the destination.
+ * (edge-case)
+ */
+int UtcDaliImageOperationsPointSampleRGBA888ScaleToZeroDims(void)
+{
+ uint32_t inputImage[ 1024 * 1024 ];
+ MakeSingleColorImageRGBA8888( 1024, 1024, inputImage );
+ uint32_t* outputImage = AllocateReadOnlyPagesRGBA(1);
+
+ // Try several different starting image sizes:
+
+ // 1x1 -> 1x1:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1, 1, (unsigned char*) &outputImage, 0, 0 );
+
+ // Single-pixel wide tall stripe:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1, 1024, (unsigned char*) &outputImage, 0, 33 );
+
+ // Single-pixel tall, wide strip:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 1024, 1, (unsigned char*) &outputImage, 0, 67 );
+
+ // Square mid-size image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 103, 103, (unsigned char*) &outputImage, 21, 0 );
+
+ // Wide mid-size image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 313, 79, (unsigned char*) &outputImage, 99, 0 );
+
+ // Tall mid-size image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 53, 467, (unsigned char*) &outputImage, 9999, 0 );
+
+ // 0 x 0 input image:
+ Dali::Internal::Platform::PointSample4BPP( (const unsigned char *) inputImage, 0, 0, (unsigned char*) &outputImage, 200, 99 );
+
+ FreeReadOnlyPagesRGBA( outputImage, getpagesize() / 4 );
+
+ //! The only real test is whether the above code SEGVs, but do a fake test so we pass if that hasn't happened:
+ DALI_TEST_EQUALS( true, true, TEST_LOCATION );
+
+ END_TEST;
+}
+
+/**
+ * @brief Test that a scaling doesn't stray outside the bounds of the destination image.
+ *
+ * The test allocates a destination buffer that is an exact multiple of the page size
+ * with guard pages at either end.
+ */
+int UtcDaliImageOperationsPointSampleRGB88InBounds(void)
+{
+ const unsigned int inputWidth = 163;
+ const unsigned int inputHeight = 691;
+ const unsigned int desiredWidth = 32;
+ const unsigned int desiredHeight = 128;
+ const unsigned int outputBuffersizeInWords = desiredWidth * (desiredHeight / 4) * 3;
+
+ uint8_t inputImage[ inputWidth * inputHeight ][3];
+
+ // Allocate an output image buffer with read-only guard pages at either end:
+ // The test will segfault if it strays into the guard pages.
+ uint32_t *outputBuffer, *outputImage;
+
+ MakeGuardedOutputImageRGBA8888( desiredWidth * (desiredHeight / 4), 3, outputBuffer, outputImage );
+
+ Dali::Internal::Platform::PointSample3BPP( &inputImage[0][0], inputWidth, inputHeight, (uint8_t*) outputImage, desiredWidth, desiredHeight );
+
+ FreeReadOnlyPagesRGBA( outputBuffer, outputBuffersizeInWords );
+
+ //! The only real test is whether the above code SEGVs, but do a fake test so we pass if that hasn't happened:
+ DALI_TEST_EQUALS( true, true, TEST_LOCATION );
+
+ END_TEST;
+}
+
+/**
+ * @brief Test the small int (x,y) tuple.
+ */
+int UtcDaliImageOperationsVector2Uint16(void)
+{
+ Vector2Uint16 vec1( 2, 3 );
+
+ DALI_TEST_EQUALS( vec1.GetWidth(), 2, TEST_LOCATION );
+ DALI_TEST_EQUALS( vec1.GetX(), 2, TEST_LOCATION );
+
+ DALI_TEST_EQUALS( vec1.GetHeight(), 3, TEST_LOCATION );
+ DALI_TEST_EQUALS( vec1.GetY(), 3, TEST_LOCATION );
+
+ Vector2Uint16 vec1Copy = vec1;
+
+ DALI_TEST_EQUALS( vec1Copy.GetWidth(), 2, TEST_LOCATION );
+ DALI_TEST_EQUALS( vec1Copy.GetX(), 2, TEST_LOCATION );
+
+ DALI_TEST_EQUALS( vec1Copy.GetHeight(), 3, TEST_LOCATION );
+ DALI_TEST_EQUALS( vec1Copy.GetY(), 3, TEST_LOCATION );
+
+ Vector2Uint16 vec2( 65535u, 65535u );
+
+ DALI_TEST_EQUALS( vec2.GetX(), 65535u, TEST_LOCATION );
+ DALI_TEST_EQUALS( vec2.GetY(), 65535u, TEST_LOCATION );
+
+ END_TEST;
+}
*
*/
-// INTERNAL INCLUDES
#include "image-operations.h"
-#include <dali/integration-api/debug.h>
-#include <dali/public-api/common/ref-counted-dali-vector.h>
-#include <dali/public-api/images/image-attributes.h>
-#include <dali/integration-api/bitmap.h>
// EXTERNAL INCLUDES
#include <cstring>
+#include <stddef.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
namespace Dali
{
/**
* @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 )
{
* @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." );
/**
* @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." );
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;
// 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 );
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 );
{
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 );
}
}
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
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;
+ }
}
}
}
/**
- * @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
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 )
{
// 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;
// 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],
}
-void HalveScanlineInPlaceRGB888(
- unsigned char * const pixels,
- const unsigned int width )
+void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
{
DebugAssertScanlineParameters( pixels, width );
}
}
-void HalveScanlineInPlaceRGBA8888(
- unsigned char * const pixels,
- const unsigned int width )
+void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
{
DebugAssertScanlineParameters( pixels, width );
DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
}
}
-void HalveScanlineInPlace2Bytes(
- unsigned char * const pixels,
- const unsigned int width )
+void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
{
DebugAssertScanlineParameters( pixels, width );
}
}
-void HalveScanlineInPlace1Byte(
- unsigned char * const pixels,
- const unsigned int width )
+void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
{
DebugAssertScanlineParameters( pixels, width );
* @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 );
}
}
-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 );
}
}
-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 );
}
}
-void AverageScanlinesRGBA8888(
- const unsigned char * const scanline1,
- const unsigned char * const __restrict__ scanline2,
- unsigned char * const outputScanline,
- const unsigned int width )
+void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
+ const unsigned char * const __restrict__ scanline2,
+ unsigned char * const outputScanline,
+ const unsigned int width )
{
DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
}
}
-void AverageScanlinesRGB565(
- const unsigned char * const scanline1,
- const unsigned char * const __restrict__ scanline2,
- unsigned char * const outputScanline,
- const unsigned int width )
+void AverageScanlinesRGB565( const unsigned char * const scanline1,
+ const unsigned char * const __restrict__ scanline2,
+ unsigned char * const outputScanline,
+ const unsigned int width )
{
DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
}
}
-void DownscaleInPlacePow2RGB888(
- unsigned char * const pixels,
- const unsigned int inputWidth, const unsigned int inputHeight,
- const unsigned int desiredWidth, const unsigned int desiredHeight,
- BoxDimensionTest dimensionTest,
- unsigned& outWidth, unsigned& outHeight )
+/// Dispatch to pixel format appropriate box filter downscaling functions.
+void DownscaleInPlacePow2( unsigned char * const pixels,
+ Pixel::Format pixelFormat,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight,
+ ImageAttributes::ScalingMode scalingMode,
+ ImageAttributes::FilterMode filterMode,
+ unsigned& outWidth,
+ unsigned& outHeight )
+{
+ outWidth = inputWidth;
+ outHeight = inputHeight;
+ // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
+ if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
+ {
+ // Check the pixel format is one that is supported:
+ if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+ {
+ const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
+
+ if( pixelFormat == Pixel::RGBA8888 )
+ {
+ Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+ }
+ else if( pixelFormat == Pixel::RGB888 )
+ {
+ Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+ }
+ else if( pixelFormat == Pixel::RGB565 )
+ {
+ Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+ }
+ else if( pixelFormat == Pixel::LA88 )
+ {
+ Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+ }
+ else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+ {
+ Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
+ }
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+ }
+}
+
+void DownscaleInPlacePow2RGB888( unsigned char * const pixels,
+ const unsigned int inputWidth,
+ const unsigned int inputHeight,
+ const unsigned int desiredWidth,
+ const unsigned int desiredHeight,
+ BoxDimensionTest dimensionTest,
+ unsigned& outWidth,
+ unsigned& outHeight )
{
DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
}
-void DownscaleInPlacePow2RGBA8888(
- unsigned char * const pixels,
- const unsigned int inputWidth, const unsigned int inputHeight,
- const unsigned int desiredWidth, const unsigned int desiredHeight,
- BoxDimensionTest dimensionTest,
- unsigned& outWidth, unsigned& outHeight )
+void DownscaleInPlacePow2RGBA8888( unsigned char * const pixels,
+ const unsigned int inputWidth,
+ const unsigned int inputHeight,
+ const unsigned int desiredWidth,
+ const unsigned int desiredHeight,
+ BoxDimensionTest dimensionTest,
+ unsigned& outWidth,
+ unsigned& outHeight )
{
DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
}
-void DownscaleInPlacePow2RGB565(
- unsigned char * pixels,
- unsigned int inputWidth, unsigned int inputHeight,
- unsigned int desiredWidth, unsigned int desiredHeight,
- BoxDimensionTest dimensionTest,
- unsigned int& outWidth, unsigned int& outHeight )
+void DownscaleInPlacePow2RGB565( unsigned char * pixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight,
+ BoxDimensionTest dimensionTest,
+ unsigned int& outWidth,
+ unsigned int& outHeight )
{
DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
}
*
* For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
*/
-void DownscaleInPlacePow2ComponentPair(
- unsigned char * const pixels,
- const unsigned int inputWidth, const unsigned int inputHeight,
- const unsigned int desiredWidth, const unsigned int desiredHeight,
- BoxDimensionTest dimensionTest,
- unsigned& outWidth, unsigned& outHeight )
+void DownscaleInPlacePow2ComponentPair( unsigned char * const pixels,
+ const unsigned int inputWidth,
+ const unsigned int inputHeight,
+ const unsigned int desiredWidth,
+ const unsigned int desiredHeight,
+ BoxDimensionTest dimensionTest,
+ unsigned& outWidth,
+ unsigned& outHeight )
{
DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
}
-void DownscaleInPlacePow2SingleBytePerPixel(
- unsigned char * pixels,
- unsigned int inputWidth, unsigned int inputHeight,
- unsigned int desiredWidth, unsigned int desiredHeight,
- BoxDimensionTest dimensionTest,
- unsigned int& outWidth, unsigned int& outHeight )
+void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight,
+ BoxDimensionTest dimensionTest,
+ unsigned int& outWidth,
+ unsigned int& outHeight )
{
DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
}
+namespace
+{
+
+// Point sample an image to a new resolution (like GL_NEAREST).
+template<typename PIXEL>
+void PointSampleAddressablePixels( const uint8_t * inPixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ uint8_t * outPixels,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight )
+{
+ DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
+ outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
+ "The input and output buffers must not overlap for an upscaling.");
+ DALI_ASSERT_DEBUG( ((uint64_t) inPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+ DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+
+ if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
+ {
+ return;
+ }
+ const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
+ PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
+ const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
+ const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
+
+ unsigned int inY = 0;
+ for( unsigned int outY = 0; outY < desiredHeight; ++outY )
+ {
+ // Round fixed point y coordinate to nearest integer:
+ const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
+ const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
+ PIXEL* const outScanline = &outAligned[desiredWidth * outY];
+
+ DALI_ASSERT_DEBUG( integerY < inputHeight );
+ DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
+ DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
+
+ unsigned int inX = 0;
+ for( unsigned int outX = 0; outX < desiredWidth; ++outX )
+ {
+ // Round the fixed-point x coordinate to an integer:
+ const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
+ const PIXEL* const inPixelAddress = &inScanline[integerX];
+ const PIXEL pixel = *inPixelAddress;
+ outScanline[outX] = pixel;
+ inX += deltaX;
+ }
+ inY += deltaY;
+ }
+}
+
+}
+
+/*
+ * RGBA8888
+ */
+void PointSample4BPP( const unsigned char * inPixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned char * outPixels,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight )
+{
+ PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+}
+
+/*
+ * RGB565, LA88
+ */
+void PointSample2BPP( const unsigned char * inPixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned char * outPixels,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight )
+{
+ PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+}
+
+/*
+ * L8, A8
+ */
+void PointSample1BPP( const unsigned char * inPixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned char * outPixels,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight )
+{
+ PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+}
+
+/*
+ * RGB888
+ * RGB888 is a special case as its pixels are not aligned addressable units.
+ */
+void PointSample3BPP( const uint8_t * inPixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ uint8_t * outPixels,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight )
+{
+ if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
+ {
+ return;
+ }
+ const unsigned int BYTES_PER_PIXEL = 3;
+
+ // Generate fixed-point 16.16 deltas in input image coordinates:
+ const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
+ const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
+
+ // Step through output image in whole integer pixel steps while tracking the
+ // corresponding locations in the input image using 16.16 fixed-point
+ // coordinates:
+ unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
+ for( unsigned int outY = 0; outY < desiredHeight; ++outY )
+ {
+ const uint8_t* const inScanline = &inPixels[inputWidth * (inY >> 16u) * BYTES_PER_PIXEL];
+ uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
+ unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
+
+ for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
+ {
+ // Truncate the fixed-point input coordinate to the address of the input pixel to sample:
+ const uint8_t* const inPixelAddress = &inScanline[(inX >> 16u) * BYTES_PER_PIXEL];
+
+ // Issue loads for all pixel color components up-front:
+ const unsigned int c0 = inPixelAddress[0];
+ const unsigned int c1 = inPixelAddress[1];
+ const unsigned int c2 = inPixelAddress[2];
+ ///@ToDo: Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads.
+
+ // Output the pixel components:
+ outScanline[outX] = c0;
+ outScanline[outX + 1] = c1;
+ outScanline[outX + 2] = c2;
+
+ // Increment the fixed-point input coordinate:
+ inX += deltaX;
+ }
+
+ inY += deltaY;
+ }
+}
+
+// Dispatch to a format-appropriate point sampling function:
+void PointSample( const unsigned char * inPixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ Pixel::Format pixelFormat,
+ unsigned char * outPixels,
+ unsigned int desiredWidth,
+ unsigned int desiredHeight )
+{
+ // Check the pixel format is one that is supported:
+ if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+ {
+ if( pixelFormat == Pixel::RGBA8888 )
+ {
+ PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+ }
+ else if( pixelFormat == Pixel::RGB888 )
+ {
+ PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+ }
+ else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
+ {
+ PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+ }
+ else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+ {
+ PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+ }
+}
+
} /* namespace Platform */
} /* namespace Internal */
} /* namespace Dali */
};
/**
+ * @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.
* 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.
* @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.
/**
* @copydoc HalveScanlineInPlaceRGB888
*/
-void HalveScanlineInPlaceRGBA8888(
- unsigned char * pixels,
- unsigned int width );
+void HalveScanlineInPlaceRGBA8888( unsigned char * pixels, unsigned int width );
/**
* @copydoc HalveScanlineInPlaceRGB888
/**
* @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.
* @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.
}
/**
- * @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
return avg;
}
+/**@}*/
+
} /* namespace Platform */
} /* namespace Internal */
} /* namespace Dali */