--- /dev/null
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "utc-image-loading-common.h"
+
+#include "platform-abstractions/portable/image-operations.h"
+
+#define ANSI_BLACK "\x1B[0m"
+#define ANSI_RED "\x1B[31m"
+#define ANSI_GREEN "\x1B[32m"
+#define ANSI_YELLOW "\x1B[33m"
+#define ANSI_BLUE "\x1B[34m"
+#define ANSI_MAGENTA "\x1B[35m"
+#define ANSI_CYAN "\x1B[36m"
+#define ANSI_WHITE "\x1B[37m"
+#define ANSI_RESET "\033[0m"
+
+const unsigned char BORDER_FILL_VALUE = 0xff;
+const char* ASCII_FILL_VALUE = ANSI_YELLOW "#";
+const char* ASCII_PAD_VALUE = ANSI_BLUE "#";
+typedef unsigned char PixelBuffer;
+
+
+void FillBitmap( BitmapPtr bitmap )
+{
+ // Fill the given bitmap fully.
+ const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
+ const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+ PixelBuffer * const targetPixels = bitmap->GetBuffer();
+ const int bytesToFill = bitmap.Get()->GetImageWidth() * bitmap.Get()->GetImageHeight() * bytesPerPixel;
+
+ memset( targetPixels, BORDER_FILL_VALUE, bytesToFill );
+}
+
+typedef Rect< int > ActiveArea;
+
+// This struct defines all information for one test.
+struct ImageFittingTestParameters
+{
+ unsigned int sourceWidth;
+ unsigned int sourceHeight;
+ unsigned int desiredWidth;
+ unsigned int desiredHeight;
+ FittingMode::Type fittingMode;
+
+ unsigned int expectedWidth;
+ unsigned int expectedHeight;
+ ActiveArea expectedActiveImageArea;
+
+ ImageFittingTestParameters( unsigned int newSourceWidth, unsigned int newSourceHeight, unsigned int newDesiredWidth, unsigned int newDesiredHeight, FittingMode::Type newFittingMode,
+ unsigned int newExpectedWidth, unsigned int newExpectedHeight, ActiveArea newExpectedActiveImageArea )
+ : sourceWidth( newSourceWidth ),
+ sourceHeight( newSourceHeight ),
+ desiredWidth( newDesiredWidth ),
+ desiredHeight( newDesiredHeight ),
+ fittingMode( newFittingMode ),
+ expectedWidth( newExpectedWidth ),
+ expectedHeight( newExpectedHeight ),
+ expectedActiveImageArea( newExpectedActiveImageArea )
+ {
+ }
+};
+
+typedef std::vector< ImageFittingTestParameters > TestContainer;
+
+
+void PerformFittingTests( TestContainer& tests )
+{
+ // Iterate through all pre-defined tests.
+ for( unsigned int testNumber = 0; testNumber < tests.size(); ++testNumber )
+ {
+ // Gather info for this test.
+ ImageFittingTestParameters &test = tests[ testNumber ];
+
+ unsigned int sourceWidth = test.sourceWidth;
+ unsigned int sourceHeight = test.sourceHeight;
+ unsigned int desiredWidth = test.desiredWidth;
+ unsigned int desiredHeight = test.desiredHeight;
+ FittingMode::Type fittingMode = test.fittingMode;
+
+ // Create a source bitmap.
+ ImageDimensions desiredDimensions( desiredWidth, desiredHeight );
+ SamplingMode::Type samplingMode = SamplingMode::BOX_THEN_LINEAR;
+ BitmapPtr sourceBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
+ Integration::Bitmap::PackedPixelsProfile *packedView = sourceBitmap->GetPackedPixelsProfile();
+ const Pixel::Format pixelFormat = sourceBitmap->GetPixelFormat();
+ packedView->ReserveBuffer( pixelFormat, sourceWidth, sourceHeight, sourceWidth, sourceHeight );
+
+ // Completely fill the source bitmap (with white).
+ FillBitmap( sourceBitmap );
+
+ // Perform fitting operations (this is the method we are testing).
+ BitmapPtr newBitmap = ApplyAttributesToBitmap( sourceBitmap, desiredDimensions, fittingMode, samplingMode );
+
+ DALI_TEST_CHECK( newBitmap );
+
+ // As we do not need performance within this test, we branch to exit here (for readability, maintainability).
+ if( !newBitmap )
+ {
+ return;
+ }
+
+ Bitmap *bitmap = newBitmap.Get();
+
+ unsigned int resultWidth = bitmap->GetImageWidth();
+ unsigned int resultHeight = bitmap->GetImageHeight();
+
+ // Check the dimensions of the modified image match against the expected values defined in the test.
+ DALI_TEST_EQUALS( resultWidth, test.expectedWidth, TEST_LOCATION );
+ DALI_TEST_EQUALS( resultHeight, test.expectedHeight, TEST_LOCATION );
+
+ PixelBuffer* resultBuffer = bitmap->GetBuffer();
+ const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+
+ // We generate an ASCII representation of the source, desired and result images to log, purely as a debugging aid.
+ // (0 = border, 1 = active image area - from the source image).
+ std::string xSourceImageString( sourceWidth, '#' );
+ std::string xDesiredSizeString( desiredWidth - 2, '-' );
+ std::string xDesiredSizePadString( desiredWidth - 2, ' ' );
+ tet_printf( "%sRunning test: %d%s\n", ANSI_RED, testNumber + 1, ANSI_RESET );
+ tet_printf( "Source image: %s%s%s\n", ANSI_YELLOW, xSourceImageString.c_str(), ANSI_RESET );
+ for( unsigned int i = 0; i < sourceHeight - 1; ++i )
+ {
+ tet_printf( " %s%s%s\n", ANSI_YELLOW, xSourceImageString.c_str(), ANSI_RESET );
+ }
+ tet_printf( "Desired size: %s+%s+%s\n", ANSI_YELLOW, xDesiredSizeString.c_str(), ANSI_RESET );
+ for( unsigned int i = 0; i < desiredHeight - 2; ++i )
+ {
+ tet_printf( " %s|%s|%s\n", ANSI_YELLOW, xDesiredSizePadString.c_str(), ANSI_RESET );
+ }
+ tet_printf( " %s+%s+%s\n", ANSI_YELLOW, xDesiredSizeString.c_str(), ANSI_RESET );
+
+ // We want to calculate the active image area (the area filled with image data as opposed to borders).
+ // This is so we can determine if the fitting modes worked correctly.
+ ActiveArea resultActiveArea( -1, -1, -1, -1 );
+
+ // Iterate over the result image data to find the active area.
+ for( unsigned int y = 0; y < resultHeight; ++y )
+ {
+ int activeStartX = -1;
+ int activeEndX = -1;
+ std::string xResultImageString;
+
+ for( unsigned int x = 0; x < resultWidth; ++x )
+ {
+ bool pixelPopulated = resultBuffer[ x * bytesPerPixel ] != 0x00;
+
+ // If the pixel is filled AND we haven't found a filled pixel yet,
+ // this is the horizontal start of the active pixel area (for this line).
+ if( pixelPopulated && ( activeStartX == -1 ) )
+ {
+ activeStartX = x;
+ }
+ else if( !pixelPopulated && ( activeStartX != -1 ) && ( activeEndX == -1 ) )
+ {
+ // If the pixel is NOT filled AND we HAVE rpeviously found a filled pixel,
+ // then this is the horizontal end of the active pixel area (for this line).
+ activeEndX = x + 1;
+ }
+
+ // Populate a string with the filled state of the result pixels, to facilitate debugging.
+ xResultImageString += pixelPopulated ? ASCII_FILL_VALUE : ASCII_PAD_VALUE;
+ }
+
+ // First calculate the X-end span value, if we ran out of image before reaching the end of active image area.
+ if( ( activeStartX != -1 ) && ( activeEndX == -1 ) )
+ {
+ activeEndX = resultWidth - activeStartX;
+ }
+
+ // If the X-start pixel on this line is earlier than other lines, the overall active area starts earlier.
+ // Note: This is ignored if there was no pixels found.
+ if( ( activeStartX != -1 ) && ( ( activeStartX < resultActiveArea.x ) || ( resultActiveArea.x == -1 ) ) )
+ {
+ resultActiveArea.x = activeStartX;
+ }
+
+ // If the X-end pixel on this line is later than other lines, the overall active area starts later.
+ // Note: This is ignored if there was no pixels found.
+ if( ( activeEndX != -1 ) && ( ( activeEndX > resultActiveArea.width ) || ( resultActiveArea.width == -1 ) ) )
+ {
+ resultActiveArea.width = activeEndX;
+ }
+
+ // If there was an X-start pixel on this line AND we don't yet have a Y-start, this line IS the Y-start.
+ if( ( activeStartX != -1 ) && ( resultActiveArea.y == -1 ) )
+ {
+ resultActiveArea.y = y;
+ }
+
+ // If there was no X-start pixel on this line AND we already have a Y-start value,
+ // then the last Y becomes the new Y-end value.
+ if( ( activeStartX == -1 ) && ( resultActiveArea.y != -1 ) && ( resultActiveArea.height == -1 ) )
+ {
+ resultActiveArea.height = y - 1;
+ }
+
+ if( y == 0 )
+ {
+ tet_printf( "Result image: %s\n", xResultImageString.c_str() );
+ }
+ else
+ {
+ tet_printf( " %s\n", xResultImageString.c_str() );
+ }
+
+ resultBuffer += resultWidth * bytesPerPixel;
+ }
+
+ // Calculate the Y-end value, if we ran out of image before reaching the end of active image area.
+ if( ( resultActiveArea.y != -1 ) && ( resultActiveArea.height == -1 ) )
+ {
+ resultActiveArea.height = resultHeight - resultActiveArea.y;
+ }
+
+ tet_printf( "%s", ANSI_RESET );
+ tet_printf( "Test: %d Result image dimensions: %d,%d ActiveArea: %d,%d,%d,%d\n",
+ testNumber + 1, resultWidth, resultHeight, resultActiveArea.x, resultActiveArea.y, resultActiveArea.width, resultActiveArea.height );
+
+ // Test the result images active area matches the expected active area defined in the test.
+ DALI_TEST_EQUALS( resultActiveArea, test.expectedActiveImageArea, TEST_LOCATION );
+ }
+}
+
+// Test cases:
+
+// Positive test case for fitting mode: FIT_WIDTH.
+int UtcDaliFittingModesFitWidth(void)
+{
+ tet_printf("Running fitting mode test for: FIT_WIDTH\n");
+
+ TestContainer tests;
+
+ // Here we can define the input and expected output of each test on a single line.
+ // Source Width, Source Height, Desired Width, Desired Height, Fitting Mode, Expected Width, Expected Height, ActiveArea: X-start, Y-start, width, height
+
+ // Test Image source size = desired size. Output should be the same.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 4, 4, FittingMode::FIT_WIDTH, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect same. Should scale size down.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 2, 2, FittingMode::FIT_WIDTH, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect same. Should not scale size up.
+ tests.push_back( ImageFittingTestParameters( 2, 2, 4, 4, FittingMode::FIT_WIDTH, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect different. Should crop height, so no borders. No scale up as result has same aspect after crop.
+ tests.push_back( ImageFittingTestParameters( 2, 4, 8, 8, FittingMode::FIT_WIDTH, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size > desired size, but aspect different (w < h). Should crop height, so no borders. No scale as result is same size as desired size.
+ tests.push_back( ImageFittingTestParameters( 4, 8, 4, 4, FittingMode::FIT_WIDTH, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect different (w > h). Should add borders, AND scale down to desired size.
+ tests.push_back( ImageFittingTestParameters( 8, 4, 4, 4, FittingMode::FIT_WIDTH, 4, 4, ActiveArea( 0, 1, 4, 2 ) ) );
+
+ PerformFittingTests( tests );
+
+ END_TEST;
+}
+
+// Positive test case for fitting mode: FIT_HEIGHT.
+int UtcDaliFittingModesFitHeight(void)
+{
+ tet_printf("Running fitting mode test for: FIT_HEIGHT\n");
+
+ TestContainer tests;
+
+ // Here we can define the input and expected output of each test on a single line.
+ // Source Width, Source Height, Desired Width, Desired Height, Fitting Mode, Expected Width, Expected Height, ActiveArea: X-start, Y-start, width, height
+
+ // Test Image source size = desired size. Output should be the same.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 4, 4, FittingMode::FIT_HEIGHT, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect same. Should scale size down.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 2, 2, FittingMode::FIT_HEIGHT, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect same. Should not scale size up.
+ tests.push_back( ImageFittingTestParameters( 2, 2, 4, 4, FittingMode::FIT_HEIGHT, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect different. Should add borders, but not scale overall size up.
+ tests.push_back( ImageFittingTestParameters( 2, 4, 8, 8, FittingMode::FIT_HEIGHT, 4, 4, ActiveArea( 1, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect different (w < h). Should add borders, AND scale down to desired size.
+ tests.push_back( ImageFittingTestParameters( 4, 8, 4, 4, FittingMode::FIT_HEIGHT, 4, 4, ActiveArea( 1, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect different (w > h). Should crop width, so no borders. No scale as result is same size as desired size.
+ tests.push_back( ImageFittingTestParameters( 8, 4, 4, 4, FittingMode::FIT_HEIGHT, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+
+ PerformFittingTests( tests );
+
+ END_TEST;
+}
+
+// Positive test case for fitting mode: SHRINK_TO_FIT.
+int UtcDaliFittingModesShrinkToFit(void)
+{
+ tet_printf("Running fitting mode test for: SHRINK_TO_FIT\n");
+
+ TestContainer tests;
+
+ // Here we can define the input and expected output of each test on a single line.
+ // Source Width, Source Height, Desired Width, Desired Height, Fitting Mode, Expected Width, Expected Height, ActiveArea: X-start, Y-start, width, height
+
+ // Test Image source size = desired size. Output should be the same.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 4, 4, FittingMode::SHRINK_TO_FIT, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect same. Should scale size down.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 2, 2, FittingMode::SHRINK_TO_FIT, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect same. Should not scale size up.
+ tests.push_back( ImageFittingTestParameters( 2, 2, 4, 4, FittingMode::SHRINK_TO_FIT, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect different. Should add borders, but not scale overall size up, as although image is smaller than desired size, aspect is the same.
+ tests.push_back( ImageFittingTestParameters( 2, 4, 8, 8, FittingMode::SHRINK_TO_FIT, 4, 4, ActiveArea( 1, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect different (w < h). Should add borders, AND scale down to desired size.
+ tests.push_back( ImageFittingTestParameters( 4, 8, 4, 4, FittingMode::SHRINK_TO_FIT, 4, 4, ActiveArea( 1, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect different (w > h). Should add borders, AND scale down to desired size.
+ tests.push_back( ImageFittingTestParameters( 8, 4, 4, 4, FittingMode::SHRINK_TO_FIT, 4, 4, ActiveArea( 0, 1, 4, 2 ) ) );
+
+ PerformFittingTests( tests );
+
+ END_TEST;
+}
+
+// Positive test case for fitting mode: SCALE_TO_FILL.
+int UtcDaliFittingModesScaleToFill(void)
+{
+ tet_printf("Running fitting mode test for: SCALE_TO_FILL\n");
+
+ TestContainer tests;
+
+ // Here we can define the input and expected output of each test on a single line.
+ // Source Width, Source Height, Desired Width, Desired Height, Fitting Mode, Expected Width, Expected Height, ActiveArea: X-start, Y-start, width, height
+
+ // Test Image source size = desired size. Output should be the same.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 4, 4, FittingMode::SCALE_TO_FILL, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect same. Should scale size down.
+ tests.push_back( ImageFittingTestParameters( 4, 4, 2, 2, FittingMode::SCALE_TO_FILL, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect same. Should not scale size up.
+ tests.push_back( ImageFittingTestParameters( 2, 2, 4, 4, FittingMode::SCALE_TO_FILL, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size < desired size, but aspect different. Should crop height, so no borders. No scale up as result has same aspect after crop.
+ tests.push_back( ImageFittingTestParameters( 2, 4, 8, 8, FittingMode::SCALE_TO_FILL, 2, 2, ActiveArea( 0, 0, 2, 2 ) ) );
+ // Test Image source size > desired size, but aspect different (w < h). Should crop height, so no borders. No scale as result is same size as desired size.
+ tests.push_back( ImageFittingTestParameters( 4, 8, 4, 4, FittingMode::SCALE_TO_FILL, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+ // Test Image source size > desired size, but aspect different (w > h). Should crop width, so no borders. No scale as result is same size as desired size.
+ tests.push_back( ImageFittingTestParameters( 8, 4, 4, 4, FittingMode::SCALE_TO_FILL, 4, 4, ActiveArea( 0, 0, 4, 4 ) ) );
+
+ PerformFittingTests( tests );
+
+ END_TEST;
+}
+
/*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
namespace
{
+// The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
+// A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
+// We can optionally use a Vector4 color here, but at reduced fill speed.
+const uint8_t BORDER_FILL_VALUE( 0x00 );
+// A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
+const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
+
using Integration::Bitmap;
using Integration::BitmapPtr;
typedef unsigned char PixelBuffer;
}
/**
+ * @brief Calculate the number of lines on the X and Y axis that need to be
+ * either added or removed with repect to the specified fitting mode.
+ * (e.g., nearest or linear).
+ * @param[in] sourceSize The size of the source image
+ * @param[in] fittingMode The fitting mode to use
+ * @param[in/out] requestedSize The target size that the image will be fitted to.
+ * If the source image is smaller than the requested size, the source is not scaled up.
+ * So we reduce the target size while keeping aspect by lowering resolution.
+ * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
+ * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
+ */
+void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
+{
+ const unsigned int sourceWidth( sourceSize.GetWidth() );
+ const unsigned int sourceHeight( sourceSize.GetHeight() );
+ const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
+ int finalWidth = 0;
+ int finalHeight = 0;
+
+ switch( fittingMode )
+ {
+ case FittingMode::FIT_WIDTH:
+ {
+ finalWidth = sourceWidth;
+ finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
+
+ columnsToCrop = 0;
+ scanlinesToCrop = -( finalHeight - sourceHeight );
+ break;
+ }
+
+ case FittingMode::FIT_HEIGHT:
+ {
+ finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
+ finalHeight = sourceHeight;
+
+ columnsToCrop = -( finalWidth - sourceWidth );
+ scanlinesToCrop = 0;
+ break;
+ }
+
+ case FittingMode::SHRINK_TO_FIT:
+ {
+ const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
+ if( sourceAspect > targetAspect )
+ {
+ finalWidth = sourceWidth;
+ finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
+
+ columnsToCrop = 0;
+ scanlinesToCrop = -( finalHeight - sourceHeight );
+ }
+ else
+ {
+ finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
+ finalHeight = sourceHeight;
+
+ columnsToCrop = -( finalWidth - sourceWidth );
+ scanlinesToCrop = 0;
+ }
+ break;
+ }
+
+ case FittingMode::SCALE_TO_FILL:
+ {
+ const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
+ if( sourceAspect > targetAspect )
+ {
+ finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
+ finalHeight = sourceHeight;
+
+ columnsToCrop = -( finalWidth - sourceWidth );
+ scanlinesToCrop = 0;
+ }
+ else
+ {
+ finalWidth = sourceWidth;
+ finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
+
+ columnsToCrop = 0;
+ scanlinesToCrop = -( finalHeight - sourceHeight );
+ }
+ break;
+ }
+ }
+
+ requestedSize.SetWidth( finalWidth );
+ requestedSize.SetHeight( finalHeight );
+}
+
+/**
* @brief Construct a bitmap with format and dimensions requested.
*/
BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
}
/**
- * @brief Implement ScaleTofill scaling mode cropping.
+ * @brief Apply cropping and padding for specified fitting mode.
*
- * Implement the cropping required for SCALE_TO_FILL 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.
+ * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
+ * based on the fitting mode.
*
- * @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.
+ * This will add vertical or horizontal borders if necessary.
+ * Crop the source image data vertically or horizontally if necessary.
+ * The aspect of the source image is preserved.
+ * If the source image is smaller than the desired size, the algorithm will modify the the newly created
+ * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
+ * GPU scaling to be performed at render time giving the same result with less texture traversal.
+ *
+ * @param[in] bitmap The source bitmap to perform modifications on.
+ * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
+ * @param[in] fittingMode The fitting mode to use.
+ *
+ * @return A new bitmap with the padding and cropping required for fitting mode applied.
+ * If no modification is needed or possible, the passed in bitmap is returned.
+ */
+Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
+
+/**
+ * @brief Adds horizontal or vertical borders to the source image.
*
- * @return The bitmap passed in if no scaling is needed or possible, else a new,
- * smaller bitmap with the cropping required for the scaling mode applied.
+ * @param[in] targetPixels The destination image pointer to draw the borders on.
+ * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
+ * @param[in] targetDimensions The dimensions of the destination image.
+ * @param[in] padDimensions The columns and scanlines to pad with borders.
*/
-Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
+void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
{
bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode );
// 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() && fittingMode == FittingMode::SCALE_TO_FILL )
+ // resulting bitmap has the same aspect ratio as the desired dimensions.
+ // Add crop and add borders if necessary depending on fitting mode.
+ if( bitmap && bitmap->GetPackedPixelsProfile() )
{
- bitmap = CropForScaleToFill( bitmap, desiredDimensions );
+ bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
}
// Examine the image pixels remaining after cropping and scaling to see if all
return bitmap;
}
-BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
+BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
{
- const unsigned inputWidth = bitmap->GetImageWidth();
- const unsigned inputHeight = bitmap->GetImageHeight();
- const unsigned desiredWidth = desiredDimensions.GetWidth();
- const unsigned desiredHeight = desiredDimensions.GetHeight();
+ const unsigned int inputWidth = bitmap->GetImageWidth();
+ const unsigned int inputHeight = bitmap->GetImageHeight();
- if( desiredWidth < 1U || desiredHeight < 1U )
+ if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
{
- DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
+ DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
}
- else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
+ else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
{
- 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 = inputWidth / float(desiredWidth);
- const Vector2 scaledByWidth = desiredDims * widthsRatio;
- 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;
- const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
-
- // Work out how many pixels to trim from top and bottom, and left and right:
- // (We only ever do one dimension)
- const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - 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, 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 )
+ // Calculate any padding or cropping that needs to be done based on the fitting mode.
+ // Note: If the desired size is larger than the original image, the desired size will be
+ // reduced while maintaining the aspect, in order to save unnecessary memory usage.
+ int scanlinesToCrop = 0;
+ int columnsToCrop = 0;
+
+ CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
+
+ unsigned int desiredWidth( desiredDimensions.GetWidth() );
+ unsigned int desiredHeight( desiredDimensions.GetHeight() );
+
+ // Action the changes by making a new bitmap with the central part of the loaded one if required.
+ if( scanlinesToCrop != 0 || columnsToCrop != 0 )
{
- const unsigned newWidth = inputWidth - 2 * columnsToTrim;
- const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
+ // Split the adding and removing of scanlines and columns into separate variables,
+ // so we can use one piece of generic code to action the changes.
+ unsigned int scanlinesToPad = 0;
+ unsigned int columnsToPad = 0;
+ if( scanlinesToCrop < 0 )
+ {
+ scanlinesToPad = -scanlinesToCrop;
+ scanlinesToCrop = 0;
+ }
+ if( columnsToCrop < 0 )
+ {
+ columnsToPad = -columnsToCrop;
+ columnsToCrop = 0;
+ }
+
+ // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
+ if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
+ ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
+ {
+ DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
+ return bitmap;
+ }
+
+ // Create a new bitmap with the desired size.
BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
- Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
+ Integration::Bitmap::PackedPixelsProfile *packedView = croppedBitmap->GetPackedPixelsProfile();
DALI_ASSERT_DEBUG( packedView );
const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
- packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
-
- const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
-
- const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
- PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
- DALI_ASSERT_DEBUG( srcPixels && destPixels );
-
- // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
- if( trimTopAndBottom )
+ packedView->ReserveBuffer( pixelFormat, desiredWidth, desiredHeight, desiredWidth, desiredHeight );
+
+ // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
+ // The cropping is added to the source pointer, and the padding is added to the destination.
+ const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+ const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
+ PixelBuffer * const targetPixels = croppedBitmap->GetBuffer();
+ PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
+ DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
+
+ // Copy the image data to the new bitmap.
+ // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
+ unsigned int outputSpan( desiredWidth * bytesPerPixel );
+ if( columnsToCrop == 0 && columnsToPad == 0 )
{
- memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
+ memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
}
else
{
- for( unsigned y = 0; y < newHeight; ++y )
+ // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
+ // Precalculate any constants to optimize the inner loop.
+ const unsigned int inputSpan( inputWidth * bytesPerPixel );
+ const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
+ const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
+
+ for( unsigned int y = 0; y < scanlinesToCopy; ++y )
{
- memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
+ memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
}
}
- // Overwrite the loaded bitmap with the cropped version:
+ // Add vertical or horizontal borders to the final image (if required).
+ desiredDimensions.SetWidth( desiredWidth );
+ desiredDimensions.SetHeight( desiredHeight );
+ AddBorders( croppedBitmap->GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
+ // Overwrite the loaded bitmap with the cropped version
bitmap = croppedBitmap;
}
}
return bitmap;
}
+void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
+{
+ // Assign ints for faster access.
+ unsigned int desiredWidth( targetDimensions.GetWidth() );
+ unsigned int desiredHeight( targetDimensions.GetHeight() );
+ unsigned int columnsToPad( padDimensions.GetWidth() );
+ unsigned int scanlinesToPad( padDimensions.GetHeight() );
+ unsigned int outputSpan( desiredWidth * bytesPerPixel );
+
+ // Add letterboxing (symmetrical borders) if needed.
+ if( scanlinesToPad > 0 )
+ {
+ // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
+ memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
+
+ // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
+ // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
+ unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
+
+ // Bottom border.
+ memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
+ }
+ else if( columnsToPad > 0 )
+ {
+ // Add a left and right border.
+ // Left:
+ // Pre-calculate span size outside of loop.
+ unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
+ for( unsigned int y = 0; y < desiredHeight; ++y )
+ {
+ memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
+ }
+
+ // Right:
+ // Pre-calculate the initial x offset as it is always the same for a small optimization.
+ // We subtract columnsToPad/2 from columnsToPad so that we have the correct
+ // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
+ unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
+ PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
+ unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
+
+ for( unsigned int y = 0; y < desiredHeight; ++y )
+ {
+ memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
+ }
+ }
+}
+
Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
ImageDimensions desired,
FittingMode::Type fittingMode,