/*
- * 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.
*
*/
-// 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 <cmath>
+#include <limits>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/common/dali-vector.h>
+#include <dali/public-api/math/vector2.h>
+#include <resampler.h>
+
+// INTERNAL INCLUDES
namespace Dali
{
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 );
+
+// Constants used by the ImageResampler.
+const float DEFAULT_SOURCE_GAMMA = 1.75f; ///< Default source gamma value used in the Resampler() function. Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
+const float FILTER_SCALE = 1.f; ///< Default filter scale value used in the Resampler() function. Filter scale - values < 1.0 cause aliasing, but create sharper looking mips.
+const char* const FILTER_TYPE = "lanczos4"; ///< Default filter used in the Resampler() function. Possible Lanczos filters are: lanczos3, lanczos4, lanczos6, lanczos12
+
using Integration::Bitmap;
using Integration::BitmapPtr;
typedef unsigned char PixelBuffer;
+/**
+ * @brief 4 byte pixel structure.
+ */
+struct Pixel4Bytes
+{
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+ uint8_t a;
+} __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
+
+/**
+ * @brief RGB888 pixel structure.
+ */
+struct Pixel3Bytes
+{
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+} __attribute__((packed, aligned(1)));
+
+/**
+ * @brief RGB565 pixel typedefed from a short.
+ *
+ * Access fields by manual shifting and masking.
+ */
+typedef uint16_t PixelRGB565;
+
+/**
+ * @brief a Pixel composed of two independent byte components.
+ */
+struct Pixel2Bytes
+{
+ uint8_t l;
+ uint8_t a;
+} __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
+
+
#if defined(DEBUG_ENABLED)
/**
* Disable logging of image operations or make it verbose from the commandline
/**
* @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 )
{
if( desiredWidth == 0u || desiredHeight == 0u )
{
- DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
}
if( inputWidth == 0u || inputHeight == 0u )
{
- DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
}
}
* @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." );
}
-} // namespace - unnamed
+/**
+ * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
+ */
+BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
+{
+ BoxDimensionTest dimensionTest;
+ dimensionTest = BoxDimensionTestEither;
+
+ switch( fittingMode )
+ {
+ // 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 FittingMode::SHRINK_TO_FIT:
+ {
+ dimensionTest = BoxDimensionTestEither;
+ break;
+ }
+ // Scale to fill mode keeps both dimensions at least as large as desired:
+ case FittingMode::SCALE_TO_FILL:
+ {
+ dimensionTest = BoxDimensionTestBoth;
+ break;
+ }
+ // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
+ case FittingMode::FIT_WIDTH:
+ {
+ dimensionTest = BoxDimensionTestX;
+ break;
+ }
+ // X Dimension is ignored by definition in FIT_HEIGHT mode:
+ case FittingMode::FIT_HEIGHT:
+ {
+ dimensionTest = BoxDimensionTestY;
+ break;
+ }
+ }
+
+ return dimensionTest;
+}
/**
- * @brief Implement ImageAttributes::ScaleTofill scaling mode.
- *
- * Implement the ImageAttributes::ScaleToFill mode, returning a new bitmap with the aspect ratio specified by the scaling mode.
- * @note This fakes the scaling with a crop and relies on the GPU scaling at
- * render time. If the input bitmap was previously maximally downscaled using a
- * repeated box filter, this is a reasonable approach.
- * @return The bitmap passed in if no scaling is needed or possible, else a new,
- * smaller bitmap with the scaling mode applied.
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting ShinkToFit scaling mode.
*/
-Integration::BitmapPtr ProcessBitmapScaleToFill( Integration::BitmapPtr bitmap, const ImageAttributes& requestedAttributes );
+ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
+{
+ // 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 );
+}
-BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
+/**
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting SCALE_TO_FILL scaling mode.
+ * @note An image scaled into 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 )
{
- // If a different size than the raw one has been requested, resize the image
- // maximally using a repeated box filter without making it smaller than the
- // requested size in either dimension:
- if( bitmap )
+ 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 )
{
- bitmap = DownscaleBitmap( *bitmap, requestedAttributes );
+ return source;
}
- // 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 )
+ 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 FIT_WIDTH 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 )
{
- bitmap = ProcessBitmapScaleToFill( bitmap, requestedAttributes );
+ return source;
}
+ return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
+}
- // 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() ) )
+/**
+ * @brief Work out the dimensions for a uniform scaling of the input to map it
+ * into the target while effecting FIT_HEIGHT 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 )
{
- bitmap->GetPackedPixelsProfile()->TestForTransparency();
+ return source;
}
- return bitmap;
+
+ 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, FittingMode::Type fittingMode )
+{
+ ImageDimensions fitDimensions;
+ switch( fittingMode )
+ {
+ case FittingMode::SHRINK_TO_FIT:
+ {
+ fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
+ break;
+ }
+ case FittingMode::SCALE_TO_FILL:
+ {
+ fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
+ break;
+ }
+ case FittingMode::FIT_WIDTH:
+ {
+ fitDimensions = FitForFitWidth( requestedSize, sourceSize );
+ break;
+ }
+ case FittingMode::FIT_HEIGHT:
+ {
+ fitDimensions = FitForFitHeight( requestedSize, sourceSize );
+ break;
+ }
+ }
+
+ return fitDimensions;
+}
+
+/**
+ * @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 );
}
-BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
+/**
+ * @brief Construct a bitmap with format and dimensions requested.
+ */
+BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
{
- const unsigned loadedWidth = bitmap->GetImageWidth();
- const unsigned loadedHeight = bitmap->GetImageHeight();
- const unsigned desiredWidth = requestedAttributes.GetWidth();
- const unsigned desiredHeight = requestedAttributes.GetHeight();
+ DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
- if( desiredWidth < 1U || desiredHeight < 1U )
+ // Allocate a pixel buffer to hold the image passed in:
+ Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
+ newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
+ return newBitmap;
+}
+
+/**
+ * @brief Construct a bitmap object from a copy of the pixel array passed in.
+ */
+BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
+{
+ DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
+
+ // Allocate a pixel buffer to hold the image passed in:
+ Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
+
+ // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
+ memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
+ return newBitmap;
+}
+
+/**
+ * @brief Work out the desired width and height, accounting for zeros.
+ *
+ * @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.
+ */
+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 )
{
- DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
+ return ImageDimensions( bitmapWidth, bitmapHeight );
}
- else if( loadedWidth != desiredWidth || loadedHeight != desiredHeight )
+
+ // If both dimensions have values requested, use them both:
+ if( requestedWidth != 0 && requestedHeight != 0 )
{
- const Vector2 desiredDims( desiredWidth, desiredHeight );
+ return ImageDimensions( requestedWidth, requestedHeight );
+ }
- // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
- // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
- const float widthsRatio = loadedWidth / float(desiredWidth);
- const Vector2 scaledByWidth = desiredDims * widthsRatio;
- const float heightsRatio = loadedHeight / float(desiredHeight);
- const Vector2 scaledByHeight = desiredDims * heightsRatio;
- // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
- const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
- const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
+ // 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 );
+}
+
+} // namespace - unnamed
+
+ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
+{
+ return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
+}
- // 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 );
+/**
+ * @brief Apply cropping and padding for specified fitting mode.
+ *
+ * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
+ * based on the fitting mode.
+ *
+ * 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 );
- 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" );
+/**
+ * @brief Adds horizontal or vertical borders to the source image.
+ *
+ * @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.
+ */
+void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
- // Make a new bitmap with the central part of the loaded one if required:
- if( scanlinesToTrim > 0 || columnsToTrim > 0 )
+BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
+{
+ if( bitmap )
+ {
+ // Calculate the desired box, accounting for a possible zero component:
+ const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
+
+ // 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, 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.
+ // Add crop and add borders if necessary depending on fitting mode.
+ if( bitmap && bitmap->GetPackedPixelsProfile() )
{
- const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
- const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
- BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
- Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
- DALI_ASSERT_DEBUG( packedView );
- const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
- packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
+ bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
+ }
- const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+ // 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 CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
+{
+ const unsigned int inputWidth = bitmap->GetImageWidth();
+ const unsigned int inputHeight = bitmap->GetImageHeight();
+
+ if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
+ {
+ DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
+ }
+ else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
+ {
+ // 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 )
+ {
+ // 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;
+ }
- const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
- PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
- DALI_ASSERT_DEBUG( srcPixels && destPixels );
+ // 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;
+ }
- // 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 )
+ // 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();
+ DALI_ASSERT_DEBUG( packedView );
+ const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
+ 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 * loadedWidth * 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;
}
-namespace
+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 );
-/**
- * @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;
+ // 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 );
- 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;
+ // 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 );
+ }
- return dimensionTest;
-}
+ // 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 );
+ }
+ }
}
-// 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,
+ FittingMode::Type fittingMode,
+ SamplingMode::Type samplingMode )
{
+ // 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, fittingMode, samplingMode, 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 ), fittingMode );
+ const unsigned int filteredWidth = filteredDimensions.GetWidth();
+ const unsigned int filteredHeight = filteredDimensions.GetHeight();
- if( shrunkWidth != bitmapWidth && shrunkHeight != bitmapHeight )
+ // Run a filter to scale down the bitmap if it needs it:
+ bool filtered = false;
+ if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
+ {
+ if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
+ samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
+ {
+ outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
+ if( outputBitmap )
{
- // 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;
+ if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
+ {
+ LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
+ }
+ else
+ {
+ PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
+ }
+ filtered = true;
}
}
- else
- {
- DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
- }
+ }
+ // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
+ if( filtered == false && ( 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,
+ FittingMode::Type fittingMode,
+ SamplingMode::Type samplingMode,
+ 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( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
+ {
+ // 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( fittingMode );
+
+ 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_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
+ }
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+ }
+}
+
+void DownscaleInPlacePow2RGB888( unsigned char *pixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned int desiredWidth,
+ 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 * pixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned int desiredWidth,
+ 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 *pixels,
+ unsigned int inputWidth,
+ unsigned int inputHeight,
+ unsigned int desiredWidth,
+ 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
+{
+
+/**
+ * @brief Point sample an image to a new resolution (like GL_NEAREST).
+ *
+ * Template is used purely as a type-safe code generator in this one
+ * compilation unit. Generated code is inlined into type-specific wrapper
+ * functions below which are exported to rest of module.
+ */
+template<typename PIXEL>
+inline 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 unsigned int integerY = (inY + (1u << 15u)) >> 16u;
+ const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
+ uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
+ unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
+
+ for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
+ {
+ // Round the fixed-point input coordinate to the address of the input pixel to sample:
+ const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
+ const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
+
+ // Issue loads for all pixel color components up-front:
+ const unsigned int c0 = inPixelAddress[0];
+ const unsigned int c1 = inPixelAddress[1];
+ const unsigned int c2 = inPixelAddress[2];
+ ///@ToDo: Optimise - Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads, versus using an RGB packed, aligned(1) struct and letting compiler pick a strategy.
+
+ // Output the pixel components:
+ outScanline[outX] = c0;
+ 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::RGB888 )
+ {
+ PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
+ }
+ else if( pixelFormat == Pixel::RGBA8888 )
+ {
+ PointSample4BPP( 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_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+ }
+}
+
+// Linear sampling group below
+
+namespace
+{
+
+/** @brief Blend 4 pixels together using horizontal and vertical weights. */
+inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
+{
+ return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
+}
+
+/** @copydoc BilinearFilter1BPPByte */
+inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
+{
+ Pixel2Bytes pixel;
+ pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
+ pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
+ return pixel;
+}
+
+/** @copydoc BilinearFilter1BPPByte */
+inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
+{
+ Pixel3Bytes pixel;
+ pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
+ pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
+ pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
+ return pixel;
+}
+
+/** @copydoc BilinearFilter1BPPByte */
+inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
+{
+ const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
+ (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
+ BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
+ return pixel;
+}
+
+/** @copydoc BilinearFilter1BPPByte */
+inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
+{
+ Pixel4Bytes pixel;
+ pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
+ pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
+ pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
+ pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
+ return pixel;
+}
+
+/**
+ * @brief Generic version of bilinear sampling image resize function.
+ * @note Limited to one compilation unit and exposed through type-specific
+ * wrapper functions below.
+ */
+template<
+ typename PIXEL,
+ PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
+ bool DEBUG_ASSERT_ALIGNMENT
+>
+inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ const unsigned int inputWidth = inputDimensions.GetWidth();
+ const unsigned int inputHeight = inputDimensions.GetHeight();
+ const unsigned int desiredWidth = desiredDimensions.GetWidth();
+ const unsigned int desiredHeight = desiredDimensions.GetHeight();
+
+ DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
+ (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
+ "Input and output buffers cannot overlap.");
+ if( DEBUG_ASSERT_ALIGNMENT )
+ {
+ DALI_ASSERT_DEBUG( ((uint64_t) inPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+ DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+ }
+
+ if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
+ {
+ return;
+ }
+ const PIXEL* const inAligned = reinterpret_cast<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 )
+ {
+ PIXEL* const outScanline = &outAligned[desiredWidth * outY];
+
+ // Find the two scanlines to blend and the weight to blend with:
+ const unsigned int integerY1 = inY >> 16u;
+ const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
+ const unsigned int inputYWeight = inY & 65535u;
+
+ DALI_ASSERT_DEBUG( integerY1 < inputHeight );
+ DALI_ASSERT_DEBUG( integerY2 < inputHeight );
+
+ const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
+ const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
+
+ unsigned int inX = 0;
+ for( unsigned int outX = 0; outX < desiredWidth; ++outX )
+ {
+ // Work out the two pixel scanline offsets for this cluster of four samples:
+ const unsigned int integerX1 = inX >> 16u;
+ const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
+
+ // Execute the loads:
+ const PIXEL pixel1 = inScanline1[integerX1];
+ const PIXEL pixel2 = inScanline2[integerX1];
+ const PIXEL pixel3 = inScanline1[integerX2];
+ const PIXEL pixel4 = inScanline2[integerX2];
+ ///@ToDo Optimise - for 1 and 2 and 4 byte types to execute a single 2, 4, or 8 byte load per pair (caveat clamping) and let half of them be unaligned.
+
+ // Weighted bilinear filter:
+ const unsigned int inputXWeight = inX & 65535u;
+ outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
+
+ inX += deltaX;
+ }
+ inY += deltaY;
+ }
+}
+
+}
+
+// Format-specific linear scaling instantiations:
+
+void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
+}
+
+void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
+}
+
+void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
+}
+
+void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
+}
+
+void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
+}
+
+void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ // Got from the test.cpp of the ImageResampler lib.
+ const float ONE_DIV_255 = 1.0f / 255.0f;
+ const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
+ const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
+ const int ALPHA_CHANNEL = 3;
+ const int NUMBER_OF_CHANNELS = 4;
+
+ float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
+ for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
+ {
+ srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
+ }
+
+ unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
+
+ const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
+ const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
+
+ for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
+ {
+ int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
+ if( k < 0 )
+ {
+ k = 0;
+ }
+ else if( k > MAX_UNSIGNED_CHAR )
+ {
+ k = MAX_UNSIGNED_CHAR;
+ }
+ linearToSrgb[i] = static_cast<unsigned char>( k );
+ }
+
+ Resampler* resamplers[NUMBER_OF_CHANNELS] = { 0 };
+ Vector<float> samples[NUMBER_OF_CHANNELS];
+
+ const int srcWidth = inputDimensions.GetWidth();
+ const int srcHeight = inputDimensions.GetHeight();
+ const int dstWidth = desiredDimensions.GetWidth();
+ const int dstHeight = desiredDimensions.GetHeight();
+
+ // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
+ // used for the other components (a memory and slight cache efficiency optimization).
+ resamplers[0] = new Resampler( srcWidth,
+ srcHeight,
+ dstWidth,
+ dstHeight,
+ Resampler::BOUNDARY_CLAMP,
+ 0.0f, // sample_low,
+ 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
+ FILTER_TYPE, // The type of filter. Currently Lanczos.
+ NULL, // Pclist_x,
+ NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
+ FILTER_SCALE, // src_x_ofs,
+ FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
+ samples[0].Resize( srcWidth );
+ for( int i = 1; i < NUMBER_OF_CHANNELS; ++i )
+ {
+ resamplers[i] = new Resampler( srcWidth,
+ srcHeight,
+ dstWidth,
+ dstHeight,
+ Resampler::BOUNDARY_CLAMP,
+ 0.0f,
+ 1.0f,
+ FILTER_TYPE,
+ resamplers[0]->get_clist_x(),
+ resamplers[0]->get_clist_y(),
+ FILTER_SCALE,
+ FILTER_SCALE );
+ samples[i].Resize( srcWidth );
+ }
+
+ const int srcPitch = srcWidth * NUMBER_OF_CHANNELS;
+ const int dstPitch = dstWidth * NUMBER_OF_CHANNELS;
+ int dstY = 0;
+
+ for( int srcY = 0; srcY < srcHeight; ++srcY )
+ {
+ const unsigned char* pSrc = &inPixels[srcY * srcPitch];
+
+ for( int x = 0; x < srcWidth; ++x )
+ {
+ for( int c = 0; c < NUMBER_OF_CHANNELS; ++c )
+ {
+ if( c == ALPHA_CHANNEL )
+ {
+ samples[c][x] = *pSrc++ * ONE_DIV_255;
+ }
+ else
+ {
+ samples[c][x] = srgbToLinear[*pSrc++];
+ }
+ }
+ }
+
+ for( int c = 0; c < NUMBER_OF_CHANNELS; ++c )
+ {
+ if( !resamplers[c]->put_line( &samples[c][0] ) )
+ {
+ DALI_ASSERT_DEBUG( !"Out of memory" );
+ }
+ }
+
+ for(;;)
+ {
+ int compIndex;
+ for( compIndex = 0; compIndex < NUMBER_OF_CHANNELS; ++compIndex )
+ {
+ const float* pOutputSamples = resamplers[compIndex]->get_line();
+ if( !pOutputSamples )
+ {
+ break;
+ }
+
+ const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL );
+ DALI_ASSERT_DEBUG( dstY < dstHeight );
+ unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
+
+ for( int x = 0; x < dstWidth; ++x )
+ {
+ if( isAlphaChannel )
+ {
+ int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
+ if( c < 0 )
+ {
+ c = 0;
+ }
+ else if( c > MAX_UNSIGNED_CHAR )
+ {
+ c = MAX_UNSIGNED_CHAR;
+ }
+ *pDst = static_cast<unsigned char>( c );
+ }
+ else
+ {
+ int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
+ if( j < 0 )
+ {
+ j = 0;
+ }
+ else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
+ {
+ j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
+ }
+ *pDst = linearToSrgb[j];
+ }
+
+ pDst += NUMBER_OF_CHANNELS;
+ }
+ }
+ if( compIndex < NUMBER_OF_CHANNELS )
+ {
+ break;
+ }
+
+ ++dstY;
+ }
+ }
+
+ // Delete the resamplers.
+ for( int i = 0; i < NUMBER_OF_CHANNELS; ++i )
+ {
+ delete resamplers[i];
+ }
+}
+
+// Dispatch to a format-appropriate linear sampling function:
+void LinearSample( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inDimensions,
+ Pixel::Format pixelFormat,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions outDimensions )
+{
+ // Check the pixel format is one that is supported:
+ if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
+ {
+ if( pixelFormat == Pixel::RGB888 )
+ {
+ LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
+ }
+ else if( pixelFormat == Pixel::RGBA8888 )
+ {
+ LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
+ }
+ else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
+ {
+ LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
+ }
+ else if( pixelFormat == Pixel::LA88 )
+ {
+ LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
+ }
+ else if ( pixelFormat == Pixel::RGB565 )
+ {
+ LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
+ }
+ else
+ {
+ DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
+ }
+}
+
} /* namespace Platform */
} /* namespace Internal */
} /* namespace Dali */