X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=platform-abstractions%2Fportable%2Fimage-operations.cpp;h=f759d39a187059c27d09438cbcf692c98a43489a;hb=316892a6d5196a469bfda0bd56bca85dfce8271b;hp=5164d79610f93bd90860632a7fe4af3314d371c5;hpb=5cc3e6aa3eb52979d6dd90dab5d31dc73ec5ca81;p=platform%2Fcore%2Fuifw%2Fdali-adaptor.git diff --git a/platform-abstractions/portable/image-operations.cpp b/platform-abstractions/portable/image-operations.cpp index 5164d79..f759d39 100644 --- a/platform-abstractions/portable/image-operations.cpp +++ b/platform-abstractions/portable/image-operations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -15,15 +15,20 @@ * */ -// INTERNAL INCLUDES #include "image-operations.h" -#include -#include -#include -#include // EXTERNAL INCLUDES #include +#include +#include +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES namespace Dali { @@ -34,10 +39,61 @@ namespace Platform 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 Resampler::Filter FILTER_TYPE = Resampler::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 @@ -60,9 +116,10 @@ inline unsigned int EvenDown( const unsigned int a ) /** * @brief Log bad parameters. */ -void ValidateScalingParameters( - const unsigned int inputWidth, const unsigned int inputHeight, - const unsigned int desiredWidth, const unsigned int desiredHeight ) +void ValidateScalingParameters( const unsigned int inputWidth, + const unsigned int inputHeight, + const unsigned int desiredWidth, + const unsigned int desiredHeight ) { if( desiredWidth > inputWidth || desiredHeight > inputHeight ) { @@ -71,12 +128,12 @@ void ValidateScalingParameters( 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" ); } } @@ -84,9 +141,7 @@ void ValidateScalingParameters( * @brief Do debug assertions common to all scanline halving functions. * @note Inline and in anon namespace so should boil away in release builds. */ -inline void DebugAssertScanlineParameters( - unsigned char * const pixels, - const unsigned int width ) +inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width ) { DALI_ASSERT_DEBUG( pixels && "Null pointer." ); DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." ); @@ -95,124 +150,491 @@ inline void DebugAssertScanlineParameters( /** * @brief Assertions on params to functions averaging pairs of scanlines. + * @note Inline as intended to boil away in release. */ -inline void DebugAssertDualScanlineParameters( - const unsigned char * const scanline1, - const unsigned char * const scanline2, - unsigned char* const outputScanline, - const size_t widthInComponents ) +inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1, + const uint8_t * const scanline2, + uint8_t* const outputScanline, + const size_t widthInComponents ) { DALI_ASSERT_DEBUG( scanline1 && "Null pointer." ); DALI_ASSERT_DEBUG( scanline2 && "Null pointer." ); DALI_ASSERT_DEBUG( outputScanline && "Null pointer." ); DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." ); - DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." ); + DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (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. + */ +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 ); +} + +/** + * @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. */ -Integration::BitmapPtr ProcessBitmapScaleToFill( Integration::BitmapPtr bitmap, const ImageAttributes& requestedAttributes ); +ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source ) +{ + DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" ); + // Scale the input by the least extreme of the two dimensions: + const float widthScale = target.GetX() / float(source.GetX()); + const float heightScale = target.GetY() / float(source.GetY()); + const float scale = widthScale > heightScale ? widthScale : heightScale; + + // Do no scaling at all if the result would increase area: + if( scale >= 1.0f ) + { + return source; + } + return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f ); +} -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 FIT_WIDTH scaling mode. + */ +ImageDimensions FitForFitWidth( 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 && "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 = DownscaleBitmap( *bitmap, requestedAttributes ); + return source; } + return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f ); +} + +/** + * @brief Work out the dimensions for a uniform scaling of the input to map it + * into the target while effecting 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()); - // 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 ) + // Do no scaling at all if the result would increase area: + if( scale >= 1.0f ) { - bitmap = ProcessBitmapScaleToFill( bitmap, requestedAttributes ); + return source; } - // 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() ) ) + 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 ) { - bitmap->GetPackedPixelsProfile()->TestForTransparency(); + 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 bitmap; + + 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 ); +} + +/** + * @brief Construct a bitmap with format and dimensions requested. + */ +BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height ) +{ + DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." ); + + // Allocate a pixel buffer to hold the image passed in: + Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::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; } -BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& requestedAttributes ) +/** + * @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 ) { - const unsigned loadedWidth = bitmap->GetImageWidth(); - const unsigned loadedHeight = bitmap->GetImageHeight(); - const unsigned desiredWidth = requestedAttributes.GetWidth(); - const unsigned desiredHeight = requestedAttributes.GetHeight(); + unsigned int maxSize = Dali::GetMaxTextureSize(); + + // If no dimensions have been requested, default to the source ones: + if( requestedWidth == 0 && requestedHeight == 0 ) + { + return ImageDimensions( std::min( bitmapWidth, maxSize ), std::min( bitmapHeight, maxSize ) ); + } - if( desiredWidth < 1U || desiredHeight < 1U ) + // If both dimensions have values requested, use them both: + if( requestedWidth != 0 && requestedHeight != 0 ) { - DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight ); + return ImageDimensions( std::min( requestedWidth, maxSize ), std::min( requestedHeight, maxSize ) ); } - else if( loadedWidth != desiredWidth || loadedHeight != desiredHeight ) + + // Only one of the dimensions has been requested. Calculate the other from + // the requested one and the source image aspect ratio: + if( requestedWidth != 0 ) { - const Vector2 desiredDims( desiredWidth, desiredHeight ); + requestedWidth = std::min( requestedWidth, maxSize ); + return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f ); + } - // 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; + requestedHeight = std::min( requestedHeight, maxSize ); + return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight ); +} - // 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 ); +} // namespace - unnamed - 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" ); +ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions ) +{ + return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ; +} - // Make a new bitmap with the central part of the loaded one if required: - if( scanlinesToTrim > 0 || columnsToTrim > 0 ) +/** + * @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 ); + +/** + * @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 ); + +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 ); + } + + // 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; - const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat ); + CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop ); - const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel; - PixelBuffer * const destPixels = croppedBitmap->GetBuffer(); - DALI_ASSERT_DEBUG( srcPixels && destPixels ); + unsigned int desiredWidth( desiredDimensions.GetWidth() ); + unsigned int desiredHeight( desiredDimensions.GetHeight() ); - // 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 ) + // 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 ) { - memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel ); + 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(); + 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( 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; } } @@ -220,112 +642,114 @@ BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& req 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 @@ -349,16 +773,25 @@ bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned switch( test ) { case BoxDimensionTestEither: + { keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight; break; + } case BoxDimensionTestBoth: + { keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight; break; + } case BoxDimensionTestX: + { keepScaling = nextWidth >= desiredWidth; break; + } case BoxDimensionTestY: + { keepScaling = nextHeight >= desiredHeight; + break; + } } } @@ -366,7 +799,8 @@ bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned } /** - * @brief A shared implementation of the overall iterative downscaling algorithm. + * @brief A shared implementation of the overall iterative box filter + * downscaling algorithm. * * Specialise this for particular pixel formats by supplying the number of bytes * per pixel and two functions: one for averaging pairs of neighbouring pixels @@ -378,12 +812,14 @@ template< void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ), void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width ) > -void DownscaleInPlacePow2Generic( - unsigned char * const pixels, - const unsigned int inputWidth, const unsigned int inputHeight, - const unsigned int desiredWidth, const unsigned int desiredHeight, - BoxDimensionTest dimensionTest, - unsigned& outWidth, unsigned& outHeight ) +void DownscaleInPlacePow2Generic( unsigned char * const pixels, + const unsigned int inputWidth, + const unsigned int inputHeight, + const unsigned int desiredWidth, + const unsigned int desiredHeight, + BoxDimensionTest dimensionTest, + unsigned& outWidth, + unsigned& outHeight ) { if( pixels == 0 ) { @@ -395,8 +831,6 @@ void DownscaleInPlacePow2Generic( // resulting height or width would be less than 1: unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight; while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) ) - //scaledWidth >> 1u >= desiredWidth && scaledHeight >> 1u >= desiredHeight && - // scaledWidth >> 1u >= 1u && scaledHeight >> 1u >= 1u ) { const unsigned int lastWidth = scaledWidth; scaledWidth >>= 1u; @@ -416,7 +850,7 @@ void DownscaleInPlacePow2Generic( // Scale vertical pairs of pixels while the last two scanlines are still warm in // the CPU cache(s): // Note, better access patterns for cache-coherence are possible for very large - // images but even a 4k RGB888 image will use just 24kB of cache (4k pixels + // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration. AverageScanlines( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], @@ -433,9 +867,7 @@ void DownscaleInPlacePow2Generic( } -void HalveScanlineInPlaceRGB888( - unsigned char * const pixels, - const unsigned int width ) +void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width ) { DebugAssertScanlineParameters( pixels, width ); @@ -458,9 +890,7 @@ void HalveScanlineInPlaceRGB888( } } -void HalveScanlineInPlaceRGBA8888( - unsigned char * const pixels, - const unsigned int width ) +void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width ) { DebugAssertScanlineParameters( pixels, width ); DALI_ASSERT_DEBUG( ((reinterpret_cast(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); @@ -492,9 +922,7 @@ void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width ) } } -void HalveScanlineInPlace2Bytes( - unsigned char * const pixels, - const unsigned int width ) +void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width ) { DebugAssertScanlineParameters( pixels, width ); @@ -514,9 +942,7 @@ void HalveScanlineInPlace2Bytes( } } -void HalveScanlineInPlace1Byte( - unsigned char * const pixels, - const unsigned int width ) +void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width ) { DebugAssertScanlineParameters( pixels, width ); @@ -537,11 +963,10 @@ void HalveScanlineInPlace1Byte( * @ToDo: Optimise for ARM using a 4 bytes at a time loop wrapped around the single ARMV6 instruction: UHADD8 R4, R0, R5. Note, this is not neon. It runs in the normal integer pipeline so there is no downside like a stall moving between integer and copro, or extra power for clocking-up the idle copro. * if (widthInComponents >= 7) { word32* aligned1 = scanline1 + 3 & 3; word32* aligned1_end = scanline1 + widthInPixels & 3; while(aligned1 < aligned1_end) { UHADD8 *aligned1++, *aligned2++, *alignedoutput++ } .. + 0 to 3 spare pixels at each end. */ -void AverageScanlines1( - const unsigned char * const scanline1, - const unsigned char * const __restrict__ scanline2, - unsigned char* const outputScanline, - const unsigned int width ) +void AverageScanlines1( const unsigned char * const scanline1, + const unsigned char * const __restrict__ scanline2, + unsigned char* const outputScanline, + const unsigned int width ) { DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width ); @@ -551,11 +976,10 @@ void AverageScanlines1( } } -void AverageScanlines2( - const unsigned char * const scanline1, - const unsigned char * const __restrict__ scanline2, - unsigned char* const outputScanline, - const unsigned int width ) +void AverageScanlines2( const unsigned char * const scanline1, + const unsigned char * const __restrict__ scanline2, + unsigned char* const outputScanline, + const unsigned int width ) { DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 ); @@ -565,11 +989,10 @@ void AverageScanlines2( } } -void AverageScanlines3( - const unsigned char * const scanline1, - const unsigned char * const __restrict__ scanline2, - unsigned char* const outputScanline, - const unsigned int width ) +void AverageScanlines3( const unsigned char * const scanline1, + const unsigned char * const __restrict__ scanline2, + unsigned char* const outputScanline, + const unsigned int width ) { DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 ); @@ -579,11 +1002,10 @@ void AverageScanlines3( } } -void AverageScanlinesRGBA8888( - const unsigned char * const scanline1, - const unsigned char * const __restrict__ scanline2, - unsigned char * const outputScanline, - const unsigned int width ) +void AverageScanlinesRGBA8888( const unsigned char * const scanline1, + const unsigned char * const __restrict__ scanline2, + unsigned char * const outputScanline, + const unsigned int width ) { DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 ); DALI_ASSERT_DEBUG( ((reinterpret_cast(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); @@ -600,11 +1022,10 @@ void AverageScanlinesRGBA8888( } } -void AverageScanlinesRGB565( - const unsigned char * const scanline1, - const unsigned char * const __restrict__ scanline2, - unsigned char * const outputScanline, - const unsigned int width ) +void AverageScanlinesRGB565( const unsigned char * const scanline1, + const unsigned char * const __restrict__ scanline2, + unsigned char * const outputScanline, + const unsigned int width ) { DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 ); DALI_ASSERT_DEBUG( ((reinterpret_cast(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." ); @@ -621,33 +1042,93 @@ void AverageScanlinesRGB565( } } -void DownscaleInPlacePow2RGB888( - unsigned char * const pixels, - const unsigned int inputWidth, const unsigned int inputHeight, - const unsigned int desiredWidth, const unsigned int desiredHeight, - BoxDimensionTest dimensionTest, - unsigned& outWidth, unsigned& outHeight ) +/// Dispatch to pixel format appropriate box filter downscaling functions. +void DownscaleInPlacePow2( unsigned char * const pixels, + Pixel::Format pixelFormat, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned int desiredWidth, + unsigned int desiredHeight, + 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(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." ); DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight ); } -void DownscaleInPlacePow2RGB565( - unsigned char * pixels, - unsigned int inputWidth, unsigned int inputHeight, - unsigned int desiredWidth, unsigned int desiredHeight, - BoxDimensionTest dimensionTest, - unsigned int& outWidth, unsigned int& outHeight ) +void DownscaleInPlacePow2RGB565( unsigned char * pixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned int desiredWidth, + unsigned int desiredHeight, + BoxDimensionTest dimensionTest, + unsigned int& outWidth, + unsigned int& outHeight ) { DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight ); } @@ -657,26 +1138,626 @@ void DownscaleInPlacePow2RGB565( * * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565. */ -void DownscaleInPlacePow2ComponentPair( - unsigned char * const pixels, - const unsigned int inputWidth, const unsigned int inputHeight, - const unsigned int desiredWidth, const unsigned int desiredHeight, - BoxDimensionTest dimensionTest, - unsigned& outWidth, unsigned& outHeight ) +void DownscaleInPlacePow2ComponentPair( unsigned char *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 +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( reinterpret_cast< 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( reinterpret_cast< uint64_t >( outPixels ) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." ); + + if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u ) + { + return; + } + const PIXEL* const inAligned = reinterpret_cast(inPixels); + PIXEL* const outAligned = reinterpret_cast(outPixels); + const unsigned int deltaX = (inputWidth << 16u) / desiredWidth; + const unsigned int deltaY = (inputHeight << 16u) / desiredHeight; + + unsigned int inY = 0; + for( unsigned int outY = 0; outY < desiredHeight; ++outY ) + { + // Round fixed point y coordinate to nearest integer: + const unsigned int integerY = (inY + (1u << 15u)) >> 16u; + const PIXEL* const inScanline = &inAligned[inputWidth * integerY]; + PIXEL* const outScanline = &outAligned[desiredWidth * outY]; + + DALI_ASSERT_DEBUG( integerY < inputHeight ); + DALI_ASSERT_DEBUG( reinterpret_cast(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) ); + DALI_ASSERT_DEBUG( reinterpret_cast(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) ); + + unsigned int inX = 0; + for( unsigned int outX = 0; outX < desiredWidth; ++outX ) + { + // Round the fixed-point x coordinate to an integer: + const unsigned int integerX = (inX + (1u << 15u)) >> 16u; + const PIXEL* const inPixelAddress = &inScanline[integerX]; + const PIXEL pixel = *inPixelAddress; + outScanline[outX] = pixel; + inX += deltaX; + } + inY += deltaY; + } +} + +} + +// RGBA8888 +void PointSample4BPP( const unsigned char * inPixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned char * outPixels, + unsigned int desiredWidth, + unsigned int desiredHeight ) +{ + PointSampleAddressablePixels( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); +} + +// RGB565, LA88 +void PointSample2BPP( const unsigned char * inPixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned char * outPixels, + unsigned int desiredWidth, + unsigned int desiredHeight ) +{ + PointSampleAddressablePixels( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); +} + +// L8, A8 +void PointSample1BPP( const unsigned char * inPixels, + unsigned int inputWidth, + unsigned int inputHeight, + unsigned char * outPixels, + unsigned int desiredWidth, + unsigned int desiredHeight ) +{ + PointSampleAddressablePixels( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight ); +} + +/* RGB888 + * RGB888 is a special case as its pixels are not aligned addressable units. + */ +void PointSample3BPP( const uint8_t * inPixels, + unsigned int inputWidth, + unsigned int inputHeight, + uint8_t * outPixels, + unsigned int desiredWidth, + unsigned int desiredHeight ) +{ + if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u ) + { + return; + } + const unsigned int BYTES_PER_PIXEL = 3; + + // Generate fixed-point 16.16 deltas in input image coordinates: + const unsigned int deltaX = (inputWidth << 16u) / desiredWidth; + const unsigned int deltaY = (inputHeight << 16u) / desiredHeight; + + // Step through output image in whole integer pixel steps while tracking the + // corresponding locations in the input image using 16.16 fixed-point + // coordinates: + unsigned int inY = 0; //< 16.16 fixed-point input image y-coord. + for( unsigned int outY = 0; outY < desiredHeight; ++outY ) + { + const 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( reinterpret_cast< 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( reinterpret_cast< uint64_t >( outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." ); + } + + if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u ) + { + return; + } + const PIXEL* const inAligned = reinterpret_cast(inPixels); + PIXEL* const outAligned = reinterpret_cast(outPixels); + const unsigned int deltaX = (inputWidth << 16u) / desiredWidth; + const unsigned int deltaY = (inputHeight << 16u) / desiredHeight; + + unsigned int inY = 0; + for( unsigned int outY = 0; outY < desiredHeight; ++outY ) + { + PIXEL* const outScanline = &outAligned[desiredWidth * outY]; + + // Find the two scanlines to blend and the weight to blend with: + const unsigned int integerY1 = inY >> 16u; + const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1; + const unsigned int inputYWeight = inY & 65535u; + + DALI_ASSERT_DEBUG( integerY1 < inputHeight ); + DALI_ASSERT_DEBUG( integerY2 < inputHeight ); + + const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1]; + const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2]; + + unsigned int inX = 0; + for( unsigned int outX = 0; outX < desiredWidth; ++outX ) + { + // Work out the two pixel scanline offsets for this cluster of four samples: + const unsigned int integerX1 = inX >> 16u; + const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1; + + // Execute the loads: + const PIXEL pixel1 = inScanline1[integerX1]; + const PIXEL pixel2 = inScanline2[integerX1]; + const PIXEL pixel3 = inScanline1[integerX2]; + const PIXEL pixel4 = inScanline2[integerX2]; + ///@ToDo Optimise - for 1 and 2 and 4 byte types to execute a single 2, 4, or 8 byte load per pair (caveat clamping) and let half of them be unaligned. + + // Weighted bilinear filter: + const unsigned int inputXWeight = inX & 65535u; + outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight ); + + inX += deltaX; + } + inY += deltaY; + } +} + +} + +// Format-specific linear scaling instantiations: + +void LinearSample1BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSample2BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSampleRGB565( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSample3BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LinearSample4BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); +} + +void LanczosSample( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions, + int numChannels, bool hasAlpha ) +{ + // 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::max(); + const int LINEAR_TO_SRGB_TABLE_SIZE = 4096; + const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0; + + static bool loadColorSpaces = true; + static float srgbToLinear[MAX_UNSIGNED_CHAR + 1]; + static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE]; + + if( loadColorSpaces ) // Only create the color space conversions on the first execution + { + loadColorSpaces = false; + + for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i ) + { + srgbToLinear[i] = pow( static_cast( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA ); + } + + const float invLinearToSrgbTableSize = 1.0f / static_cast( 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( 255.0f * pow( static_cast( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f ); + if( k < 0 ) + { + k = 0; + } + else if( k > MAX_UNSIGNED_CHAR ) + { + k = MAX_UNSIGNED_CHAR; + } + linearToSrgb[i] = static_cast( k ); + } + } + + Resampler* resamplers[numChannels]; + Vector samples[numChannels]; + + 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 < numChannels; ++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 * numChannels; + const int dstPitch = dstWidth * numChannels; + 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 < numChannels; ++c ) + { + if( c == ALPHA_CHANNEL && hasAlpha ) + { + samples[c][x] = *pSrc++ * ONE_DIV_255; + } + else + { + samples[c][x] = srgbToLinear[*pSrc++]; + } + } + } + + for( int c = 0; c < numChannels; ++c ) + { + if( !resamplers[c]->put_line( &samples[c][0] ) ) + { + DALI_ASSERT_DEBUG( !"Out of memory" ); + } + } + + for(;;) + { + int compIndex; + for( compIndex = 0; compIndex < numChannels; ++compIndex ) + { + const float* pOutputSamples = resamplers[compIndex]->get_line(); + if( !pOutputSamples ) + { + break; + } + + const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha ); + 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( 255.0f * pOutputSamples[x] + 0.5f ); + if( c < 0 ) + { + c = 0; + } + else if( c > MAX_UNSIGNED_CHAR ) + { + c = MAX_UNSIGNED_CHAR; + } + *pDst = static_cast( c ); + } + else + { + int j = static_cast( 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 += numChannels; + } + } + if( compIndex < numChannels ) + { + break; + } + + ++dstY; + } + } + + // Delete the resamplers. + for( int i = 0; i < numChannels; ++i ) + { + delete resamplers[i]; + } +} + +void LanczosSample4BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 4, true ); +} + +void LanczosSample1BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + // For L8 images + LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 1, false ); +} + +// 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 */