/*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2018 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.
#include <cstring>
#include <stddef.h>
#include <cmath>
+#include <limits>
+#include <memory>
#include <dali/integration-api/debug.h>
+#include <dali/public-api/common/dali-vector.h>
#include <dali/public-api/math/vector2.h>
+#include <resampler.h>
+#include <image-loading.h>
// INTERNAL INCLUDES
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 float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
+const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians;
+const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians;
+const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians;
+
using Integration::Bitmap;
using Integration::BitmapPtr;
typedef unsigned char PixelBuffer;
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" );
}
}
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." );
}
/**
fitDimensions = FitForFitHeight( requestedSize, sourceSize );
break;
}
- };
+ }
return fitDimensions;
}
/**
- * @brief Construct a bitmap with format and dimensions requested.
+ * @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)
*/
-BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
+void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
{
- DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
+ 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;
- // Allocate a pixel buffer to hold the image passed in:
- Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
- newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
- return newBitmap;
+ 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 object from a copy of the pixel array passed in.
+ * @brief Construct a pixel buffer 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::Devel::PixelBuffer MakePixelBuffer( 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 );
+ auto newBitmap = Dali::Devel::PixelBuffer::New( width, height, pixelFormat );
// 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 ) );
+ memcpy( newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
return newBitmap;
}
*/
ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
{
+ unsigned int maxSize = Dali::GetMaxTextureSize();
+
// If no dimensions have been requested, default to the source ones:
if( requestedWidth == 0 && requestedHeight == 0 )
{
- return ImageDimensions( bitmapWidth, bitmapHeight );
+ if( bitmapWidth <= maxSize && bitmapHeight <= maxSize )
+ {
+ return ImageDimensions( bitmapWidth, bitmapHeight );
+ }
+ else
+ {
+ // Calculate the size from the max texture size and the source image aspect ratio
+ if( bitmapWidth > bitmapHeight )
+ {
+ return ImageDimensions( maxSize, bitmapHeight * maxSize / static_cast< float >( bitmapWidth ) + 0.5f );
+ }
+ else
+ {
+ return ImageDimensions( bitmapWidth * maxSize / static_cast< float >( bitmapHeight ) + 0.5f, maxSize );
+ }
+ }
}
// If both dimensions have values requested, use them both:
if( requestedWidth != 0 && requestedHeight != 0 )
{
- return ImageDimensions( requestedWidth, requestedHeight );
+ if( requestedWidth <= maxSize && requestedHeight <= maxSize )
+ {
+ return ImageDimensions( requestedWidth, requestedHeight );
+ }
+ else
+ {
+ // Calculate the size from the max texture size and the source image aspect ratio
+ if( requestedWidth > requestedHeight )
+ {
+ return ImageDimensions( maxSize, requestedHeight * maxSize / static_cast< float >( requestedWidth ) + 0.5f );
+ }
+ else
+ {
+ return ImageDimensions( requestedWidth * maxSize / static_cast< float >( requestedHeight ) + 0.5f, maxSize );
+ }
+ }
}
// Only one of the dimensions has been requested. Calculate the other from
// the requested one and the source image aspect ratio:
if( requestedWidth != 0 )
{
+ requestedWidth = std::min( requestedWidth, maxSize );
return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
}
+
+ requestedHeight = std::min( requestedHeight, maxSize );
return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
}
+/**
+ * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
+ *
+ * @note It allocates memory for the returned @p pixelsOut buffer.
+ * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
+ * @note It may fail if malloc() fails to allocate memory.
+ *
+ * @param[in] pixelsIn The input buffer.
+ * @param[in] widthIn The width of the input buffer.
+ * @param[in] heightIn The height of the input buffer.
+ * @param[in] pixelSize The size of the pixel.
+ * @param[out] pixelsOut The rotated output buffer.
+ * @param[out] widthOut The width of the output buffer.
+ * @param[out] heightOut The height of the output buffer.
+ *
+ * @return Whether the rotation succeded.
+ */
+bool Rotate90( const uint8_t* const pixelsIn,
+ unsigned int widthIn,
+ unsigned int heightIn,
+ unsigned int pixelSize,
+ uint8_t*& pixelsOut,
+ unsigned int& widthOut,
+ unsigned int& heightOut )
+{
+ // The new size of the image.
+ widthOut = heightIn;
+ heightOut = widthIn;
+
+ // Allocate memory for the rotated buffer.
+ pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
+ if( nullptr == pixelsOut )
+ {
+ widthOut = 0u;
+ heightOut = 0u;
+
+ // Return if the memory allocations fails.
+ return false;
+ }
+
+ // Rotate the buffer.
+ for( unsigned int y = 0u; y < heightIn; ++y )
+ {
+ const unsigned int srcLineIndex = y * widthIn;
+ const unsigned int dstX = y;
+ for( unsigned int x = 0u; x < widthIn; ++x )
+ {
+ const unsigned int dstY = heightOut - x - 1u;
+ const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
+ const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
+ *
+ * @note It allocates memory for the returned @p pixelsOut buffer.
+ * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
+ * @note It may fail if malloc() fails to allocate memory.
+ *
+ * @param[in] pixelsIn The input buffer.
+ * @param[in] widthIn The width of the input buffer.
+ * @param[in] heightIn The height of the input buffer.
+ * @param[in] pixelSize The size of the pixel.
+ * @param[out] pixelsOut The rotated output buffer.
+ *
+ * @return Whether the rotation succeded.
+ */
+bool Rotate180( const uint8_t* const pixelsIn,
+ unsigned int widthIn,
+ unsigned int heightIn,
+ unsigned int pixelSize,
+ uint8_t*& pixelsOut )
+{
+ // Allocate memory for the rotated buffer.
+ pixelsOut = static_cast<uint8_t*>( malloc ( widthIn * heightIn * pixelSize ) );
+ if( nullptr == pixelsOut )
+ {
+ // Return if the memory allocations fails.
+ return false;
+ }
+
+ // Rotate the buffer.
+ for( unsigned int y = 0u; y < heightIn; ++y )
+ {
+ const unsigned int srcLineIndex = y * widthIn;
+ const unsigned int dstY = heightIn - y - 1u;
+ for( unsigned int x = 0u; x < widthIn; ++x )
+ {
+ const unsigned int dstX = widthIn - x - 1u;
+ const unsigned int dstIndex = pixelSize * ( dstY * widthIn + dstX );
+ const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
+ *
+ * @note It allocates memory for the returned @p pixelsOut buffer.
+ * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
+ * @note It may fail if malloc() fails to allocate memory.
+ *
+ * @param[in] pixelsIn The input buffer.
+ * @param[in] widthIn The width of the input buffer.
+ * @param[in] heightIn The height of the input buffer.
+ * @param[in] pixelSize The size of the pixel.
+ * @param[out] pixelsOut The rotated output buffer.
+ * @param[out] widthOut The width of the output buffer.
+ * @param[out] heightOut The height of the output buffer.
+ *
+ * @return Whether the rotation succeded.
+ */
+bool Rotate270( const uint8_t* const pixelsIn,
+ unsigned int widthIn,
+ unsigned int heightIn,
+ unsigned int pixelSize,
+ uint8_t*& pixelsOut,
+ unsigned int& widthOut,
+ unsigned int& heightOut )
+{
+ // The new size of the image.
+ widthOut = heightIn;
+ heightOut = widthIn;
+
+ // Allocate memory for the rotated buffer.
+ pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
+ if( nullptr == pixelsOut )
+ {
+ widthOut = 0u;
+ heightOut = 0u;
+
+ // Return if the memory allocations fails.
+ return false;
+ }
+
+ // Rotate the buffer.
+ for( unsigned int y = 0u; y < heightIn; ++y )
+ {
+ const unsigned int srcLineIndex = y * widthIn;
+ const unsigned int dstX = widthOut - y - 1u;
+ for( unsigned int x = 0u; x < widthIn; ++x )
+ {
+ const unsigned int dstY = x;
+ const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
+ const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Skews a row horizontally (with filtered weights)
+ *
+ * @note Limited to 45 degree skewing only.
+ * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
+ *
+ * @param[in] srcBufferPtr Pointer to the input pixel buffer.
+ * @param[in] srcWidth The width of the input pixel buffer.
+ * @param[in] pixelSize The size of the pixel.
+ * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
+ * @param[in] dstWidth The width of the output pixel buffer.
+ * @param[in] row The row index.
+ * @param[in] offset The skew offset.
+ * @param[in] weight The relative weight of right pixel.
+ */
+void HorizontalSkew( const uint8_t* const srcBufferPtr,
+ int srcWidth,
+ unsigned int pixelSize,
+ uint8_t*& dstBufferPtr,
+ int dstWidth,
+ unsigned int row,
+ int offset,
+ float weight )
+{
+ if( offset > 0 )
+ {
+ // Fill gap left of skew with background.
+ memset( dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset );
+ }
+
+ unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
+
+ int i = 0;
+ for( i = 0u; i < srcWidth; ++i )
+ {
+ // Loop through row pixels
+ const unsigned int srcIndex = pixelSize * ( row * srcWidth + i );
+
+ unsigned char src[4u] = { 0u, 0u, 0u, 0u };
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ src[channel] = *( srcBufferPtr + srcIndex + channel );
+ }
+
+ // Calculate weights
+ unsigned char left[4u] = { 0u, 0u, 0u, 0u };
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
+
+ // Update left over on source
+ src[channel] -= ( left[channel] - oldLeft[channel] );
+ }
+
+ // Check boundaries
+ if( ( i + offset >= 0 ) && ( i + offset < dstWidth ) )
+ {
+ const unsigned int dstIndex = pixelSize * ( row * dstWidth + i + offset );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( dstBufferPtr + dstIndex + channel ) = src[channel];
+ }
+ }
+
+ // Save leftover for next pixel in scan
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ oldLeft[channel] = left[channel];
+ }
+ }
+
+ // Go to rightmost point of skew
+ i += offset;
+ if( i < dstWidth )
+ {
+ // If still in image bounds, put leftovers there
+ const unsigned int dstIndex = pixelSize * ( row * dstWidth + i );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
+ }
+
+ // Clear to the right of the skewed line with background
+ ++i;
+ memset( dstBufferPtr + pixelSize * ( row * dstWidth + i ), 0u, pixelSize * ( dstWidth - i ) );
+ }
+}
+
+/**
+ * @brief Skews a column vertically (with filtered weights)
+ *
+ * @note Limited to 45 degree skewing only.
+ * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
+ *
+ * @param[in] srcBufferPtr Pointer to the input pixel buffer.
+ * @param[in] srcWidth The width of the input pixel buffer.
+ * @param[in] srcHeight The height of the input pixel buffer.
+ * @param[in] pixelSize The size of the pixel.
+ * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
+ * @param[in] dstWidth The width of the output pixel buffer.
+ * @param[in] dstHeight The height of the output pixel buffer.
+ * @param[in] column The column index.
+ * @param[in] offset The skew offset.
+ * @param[in] weight The relative weight of uppeer pixel.
+ */
+void VerticalSkew( const uint8_t* const srcBufferPtr,
+ int srcWidth,
+ int srcHeight,
+ unsigned int pixelSize,
+ uint8_t*& dstBufferPtr,
+ int dstWidth,
+ int dstHeight,
+ unsigned int column,
+ int offset,
+ float weight )
+{
+ for( int i = 0; i < offset; ++i )
+ {
+ // Fill gap above skew with background
+ const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( dstBufferPtr + dstIndex + channel ) = 0u;
+ }
+ }
+
+ unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
+
+ int yPos = 0;
+ int i = 0;
+ for( i = 0; i < srcHeight; ++i )
+ {
+ // Loop through column pixels
+ const unsigned int srcIndex = pixelSize * ( i * srcWidth + column );
+
+ unsigned char src[4u] = { 0u, 0u, 0u, 0u };
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ src[channel] = *( srcBufferPtr + srcIndex + channel );
+ }
+
+ yPos = i + offset;
+
+ // Calculate weights
+ unsigned char left[4u] = { 0u, 0u, 0u, 0u };
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
+ // Update left over on source
+ src[channel] -= ( left[channel] - oldLeft[channel] );
+ }
+
+ // Check boundaries
+ if( ( yPos >= 0 ) && ( yPos < dstHeight ) )
+ {
+ const unsigned int dstIndex = pixelSize * ( yPos * dstWidth + column );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( dstBufferPtr + dstIndex + channel ) = src[channel];
+ }
+ }
+
+ // Save leftover for next pixel in scan
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ oldLeft[channel] = left[channel];
+ }
+ }
+
+ // Go to bottom point of skew
+ i = yPos;
+ if( i < dstHeight )
+ {
+ // If still in image bounds, put leftovers there
+ const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
+ }
+ }
+
+ while( ++i < dstHeight )
+ {
+ // Clear below skewed line with background
+ const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
+
+ for( unsigned int channel = 0u; channel < pixelSize; ++channel )
+ {
+ *( dstBufferPtr + dstIndex + channel ) = 0u;
+ }
+ }
+}
+
} // namespace - unnamed
ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
}
/**
- * @brief Implement ScaleTofill scaling mode cropping.
+ * @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.
*
- * Implement the cropping required for SCALE_TO_FILL mode,
- * returning a new bitmap with the aspect ratio specified by the scaling mode.
- * This scaling mode selects the central portion of a source image so any spare
- * pixels off one of either the top or bottom edge need to be removed.
+ * 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.
*
- * @note If the input bitmap was not previously downscaled to exactly encompass
- * the desired output size, the resulting bitmap will have the correct aspect
- * ratio but will have larger dimensions than requested. This can be used to
- * fake the scaling mode by relying on the GPU scaling at render time.
- * If the input bitmap was previously maximally downscaled using a
- * repeated box filter, this is a reasonable approach.
+ * @param[in] bitmap The source pixel buffer 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 The bitmap passed in if no scaling is needed or possible, else a new,
- * smaller bitmap with the cropping required for the scaling mode applied.
+ * @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 CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
+Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
-BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
+/**
+ * @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 );
+
+Dali::Devel::PixelBuffer ApplyAttributesToBitmap( Dali::Devel::PixelBuffer 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() );
+ const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap.GetWidth(), bitmap.GetHeight(), 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 );
+ bitmap = DownscaleBitmap( bitmap, desiredDimensions, fittingMode, samplingMode );
// Cut the bitmap according to the desired width and height so that the
- // resulting bitmap has the same aspect ratio as the desired dimensions:
- if( bitmap && bitmap->GetPackedPixelsProfile() && fittingMode == FittingMode::SCALE_TO_FILL )
- {
- bitmap = CropForScaleToFill( bitmap, desiredDimensions );
- }
-
- // Examine the image pixels remaining after cropping and scaling to see if all
- // are opaque, allowing faster rendering, or some have non-1.0 alpha:
- if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
+ // 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()->TestForTransparency();
+ bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
}
}
return bitmap;
}
-BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
+Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
{
- const unsigned inputWidth = bitmap->GetImageWidth();
- const unsigned inputHeight = bitmap->GetImageHeight();
- const unsigned desiredWidth = desiredDimensions.GetWidth();
- const unsigned desiredHeight = desiredDimensions.GetHeight();
+ const unsigned int inputWidth = bitmap.GetWidth();
+ const unsigned int inputHeight = bitmap.GetHeight();
- if( desiredWidth < 1U || desiredHeight < 1U )
+ if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
{
- DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
+ DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
}
- else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
+ else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
{
- const Vector2 desiredDims( desiredWidth, desiredHeight );
-
- // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
- // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
- const float widthsRatio = inputWidth / float(desiredWidth);
- const Vector2 scaledByWidth = desiredDims * widthsRatio;
- const float heightsRatio = inputHeight / float(desiredHeight);
- const Vector2 scaledByHeight = desiredDims * heightsRatio;
- // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
- const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
- const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
+ // 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;
- // Work out how many pixels to trim from top and bottom, and left and right:
- // (We only ever do one dimension)
- const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
- const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
+ CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
- DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, inputWidth, inputHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
+ unsigned int desiredWidth( desiredDimensions.GetWidth() );
+ unsigned int desiredHeight( desiredDimensions.GetHeight() );
- // Make a new bitmap with the central part of the loaded one if required:
- if( scanlinesToTrim > 0 || columnsToTrim > 0 )
+ // Action the changes by making a new bitmap with the central part of the loaded one if required.
+ if( scanlinesToCrop != 0 || columnsToCrop != 0 )
{
- const unsigned newWidth = inputWidth - 2 * columnsToTrim;
- const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
- 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 );
-
- const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
-
- const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
- PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
- DALI_ASSERT_DEBUG( srcPixels && destPixels );
-
- // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
- if( trimTopAndBottom )
+ // Split the adding and removing of scanlines and columns into separate variables,
+ // so we can use one piece of generic code to action the changes.
+ unsigned int scanlinesToPad = 0;
+ unsigned int columnsToPad = 0;
+ if( scanlinesToCrop < 0 )
+ {
+ scanlinesToPad = -scanlinesToCrop;
+ scanlinesToCrop = 0;
+ }
+ if( columnsToCrop < 0 )
+ {
+ columnsToPad = -columnsToCrop;
+ columnsToCrop = 0;
+ }
+
+ // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
+ if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
+ ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
+ {
+ DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
+ return bitmap;
+ }
+
+ // Create new PixelBuffer with the desired size.
+ const auto pixelFormat = bitmap.GetPixelFormat();
+
+ auto croppedBitmap = Devel::PixelBuffer::New( desiredWidth, desiredHeight, pixelFormat );
+
+ // 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 auto bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+ const PixelBuffer * const sourcePixels = bitmap.GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
+ PixelBuffer * const targetPixels = croppedBitmap.GetBuffer();
+ PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
+ DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
+
+ // Copy the image data to the new bitmap.
+ // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
+ unsigned int outputSpan( desiredWidth * bytesPerPixel );
+ if( columnsToCrop == 0 && columnsToPad == 0 )
{
- memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
+ memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
}
else
{
- for( unsigned y = 0; y < newHeight; ++y )
+ // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
+ // Precalculate any constants to optimize the inner loop.
+ const unsigned int inputSpan( inputWidth * bytesPerPixel );
+ const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
+ const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
+
+ for( unsigned int y = 0; y < scanlinesToCopy; ++y )
{
- memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
+ memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
}
}
- // Overwrite the loaded bitmap with the cropped version:
+ // Add vertical or horizontal borders to the final image (if required).
+ desiredDimensions.SetWidth( desiredWidth );
+ desiredDimensions.SetHeight( desiredHeight );
+ AddBorders( croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
+ // Overwrite the loaded bitmap with the cropped version
bitmap = croppedBitmap;
}
}
return bitmap;
}
-Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
+void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
+{
+ // Assign ints for faster access.
+ unsigned int desiredWidth( targetDimensions.GetWidth() );
+ unsigned int desiredHeight( targetDimensions.GetHeight() );
+ unsigned int columnsToPad( padDimensions.GetWidth() );
+ unsigned int scanlinesToPad( padDimensions.GetHeight() );
+ unsigned int outputSpan( desiredWidth * bytesPerPixel );
+
+ // Add letterboxing (symmetrical borders) if needed.
+ if( scanlinesToPad > 0 )
+ {
+ // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
+ memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
+
+ // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
+ // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
+ unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
+
+ // Bottom border.
+ memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
+ }
+ else if( columnsToPad > 0 )
+ {
+ // Add a left and right border.
+ // Left:
+ // Pre-calculate span size outside of loop.
+ unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
+ for( unsigned int y = 0; y < desiredHeight; ++y )
+ {
+ memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
+ }
+
+ // Right:
+ // Pre-calculate the initial x offset as it is always the same for a small optimization.
+ // We subtract columnsToPad/2 from columnsToPad so that we have the correct
+ // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
+ unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
+ PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
+ unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
+
+ for( unsigned int y = 0; y < desiredHeight; ++y )
+ {
+ memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
+ }
+ }
+}
+
+Dali::Devel::PixelBuffer DownscaleBitmap( Dali::Devel::PixelBuffer 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();
+ auto bitmapWidth = bitmap.GetWidth();
+ auto bitmapHeight = bitmap.GetHeight();
// Desired dimensions (the rectangle to fit the source image to):
- const unsigned int desiredWidth = desired.GetWidth();
- const unsigned int desiredHeight = desired.GetHeight();
+ auto desiredWidth = desired.GetWidth();
+ auto desiredHeight = desired.GetHeight();
- BitmapPtr outputBitmap( &bitmap );
+ Dali::Devel::PixelBuffer outputBitmap { bitmap };
// If a different size than the raw one has been requested, resize the image:
- if( bitmap.GetPackedPixelsProfile() &&
+ if(
(desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
{
- const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
+ auto pixelFormat = bitmap.GetPixelFormat();
// 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;
if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
{
- outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
+ outputBitmap = Dali::Devel::PixelBuffer::New( filteredWidth, filteredHeight, pixelFormat );
+
if( outputBitmap )
{
if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
{
- LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
+ LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions );
}
else
{
- PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
+ PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight );
}
filtered = true;
}
// 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 );
+ outputBitmap = MakePixelBuffer( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
}
}
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, ...)." );
+ 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 )
{
"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, ...)." );
+ 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 )
LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
}
+
+void Resample( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions,
+ Resampler::Filter filterType,
+ 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<uint8_t>::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<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
+ }
+
+ 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[numChannels];
+ Vector<float> 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.
+ filterType, // The type of filter.
+ 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,
+ filterType,
+ 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<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 += 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 )
+{
+ Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true );
+}
+
+void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
+ ImageDimensions inputDimensions,
+ unsigned char * __restrict__ outPixels,
+ ImageDimensions desiredDimensions )
+{
+ // For L8 images
+ Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false );
+}
+
// Dispatch to a format-appropriate linear sampling function:
void LinearSample( const unsigned char * __restrict__ inPixels,
ImageDimensions inDimensions,
}
}
+void RotateByShear( const uint8_t* const pixelsIn,
+ unsigned int widthIn,
+ unsigned int heightIn,
+ unsigned int pixelSize,
+ float radians,
+ uint8_t*& pixelsOut,
+ unsigned int& widthOut,
+ unsigned int& heightOut )
+{
+ // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
+
+ // Do first the fast rotations to transform the angle into a (-45..45] range.
+
+ float fastRotationPerformed = false;
+ if( ( radians > Math::PI_4 ) && ( radians <= RAD_135 ) )
+ {
+ // Angle in (45.0 .. 135.0]
+ // Rotate image by 90 degrees into temporary image,
+ // so it requires only an extra rotation angle
+ // of -45.0 .. +45.0 to complete rotation.
+ fastRotationPerformed = Rotate90( pixelsIn,
+ widthIn,
+ heightIn,
+ pixelSize,
+ pixelsOut,
+ widthOut,
+ heightOut );
+
+ if( !fastRotationPerformed )
+ {
+ // The fast rotation failed.
+ return;
+ }
+
+ radians -= Math::PI_2;
+ }
+ else if( ( radians > RAD_135 ) && ( radians <= RAD_225 ) )
+ {
+ // Angle in (135.0 .. 225.0]
+ // Rotate image by 180 degrees into temporary image,
+ // so it requires only an extra rotation angle
+ // of -45.0 .. +45.0 to complete rotation.
+
+ fastRotationPerformed = Rotate180( pixelsIn,
+ widthIn,
+ heightIn,
+ pixelSize,
+ pixelsOut );
+
+ if( !fastRotationPerformed )
+ {
+ // The fast rotation failed.
+ return;
+ }
+
+ radians -= Math::PI;
+ widthOut = widthIn;
+ heightOut = heightIn;
+ }
+ else if( ( radians > RAD_225 ) && ( radians <= RAD_315 ) )
+ {
+ // Angle in (225.0 .. 315.0]
+ // Rotate image by 270 degrees into temporary image,
+ // so it requires only an extra rotation angle
+ // of -45.0 .. +45.0 to complete rotation.
+
+ fastRotationPerformed = Rotate270( pixelsIn,
+ widthIn,
+ heightIn,
+ pixelSize,
+ pixelsOut,
+ widthOut,
+ heightOut );
+
+ if( !fastRotationPerformed )
+ {
+ // The fast rotation failed.
+ return;
+ }
+
+ radians -= RAD_270;
+ }
+
+ if( fabs( radians ) < Dali::Math::MACHINE_EPSILON_10 )
+ {
+ // Nothing else to do if the angle is zero.
+ // The rotation angle was 90, 180 or 270.
+
+ // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
+ return;
+ }
+
+ const uint8_t* const firstHorizontalSkewPixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
+ std::unique_ptr<uint8_t, void(*)(void*)> tmpPixelsInPtr( ( fastRotationPerformed ? pixelsOut : nullptr ), free );
+
+ // Reset the input/output
+ widthIn = widthOut;
+ heightIn = heightOut;
+ pixelsOut = nullptr;
+
+ const float angleSinus = sin( radians );
+ const float angleCosinus = cos( radians );
+ const float angleTangent = tan( 0.5f * radians );
+
+ ///////////////////////////////////////
+ // Perform 1st shear (horizontal)
+ ///////////////////////////////////////
+
+ // Calculate first shear (horizontal) destination image dimensions
+
+ widthOut = widthIn + static_cast<unsigned int>( fabs( angleTangent ) * static_cast<float>( heightIn ) );
+ heightOut = heightIn;
+
+ // Allocate the buffer for the 1st shear
+ pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
+
+ if( nullptr == pixelsOut )
+ {
+ widthOut = 0u;
+ heightOut = 0u;
+
+ // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Fast rotations'.
+ // Nothing else to do if the memory allocation fails.
+ return;
+ }
+
+ for( unsigned int y = 0u; y < heightOut; ++y )
+ {
+ const float shear = angleTangent * ( ( angleTangent >= 0.f ) ? ( 0.5f + static_cast<float>( y ) ) : ( 0.5f + static_cast<float>( y ) - static_cast<float>( heightOut ) ) );
+
+ const int intShear = static_cast<int>( floor( shear ) );
+ HorizontalSkew( firstHorizontalSkewPixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>( intShear ) );
+ }
+
+ // Reset the 'pixel in' pointer with the output of the 'First Horizontal Skew' and free the memory allocated by the 'Fast Rotations'.
+ tmpPixelsInPtr.reset( pixelsOut );
+ unsigned int tmpWidthIn = widthOut;
+ unsigned int tmpHeightIn = heightOut;
+
+ // Reset the input/output
+ pixelsOut = nullptr;
+
+ ///////////////////////////////////////
+ // Perform 2nd shear (vertical)
+ ///////////////////////////////////////
+
+ // Calc 2nd shear (vertical) destination image dimensions
+ heightOut = static_cast<unsigned int>( static_cast<float>( widthIn ) * fabs( angleSinus ) + static_cast<float>( heightIn ) * angleCosinus );
+
+ // Allocate the buffer for the 2nd shear
+ pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
+
+ if( nullptr == pixelsOut )
+ {
+ widthOut = 0u;
+ heightOut = 0u;
+
+ // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'First Horizontal Skew'.
+ // Nothing else to do if the memory allocation fails.
+ return;
+ }
+
+ // Variable skew offset
+ float offset = angleSinus * ( ( angleSinus > 0.f ) ? static_cast<float>( widthIn - 1u ) : -( static_cast<float>( widthIn ) - static_cast<float>( widthOut ) ) );
+
+ unsigned int column = 0u;
+ for( column = 0u; column < widthOut; ++column, offset -= angleSinus )
+ {
+ const int shear = static_cast<int>( floor( offset ) );
+ VerticalSkew( tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>( shear ) );
+ }
+ // Reset the 'pixel in' pointer with the output of the 'Vertical Skew' and free the memory allocated by the 'First Horizontal Skew'.
+ // Reset the input/output
+ tmpPixelsInPtr.reset( pixelsOut );
+ tmpWidthIn = widthOut;
+ tmpHeightIn = heightOut;
+ pixelsOut = nullptr;
+
+ ///////////////////////////////////////
+ // Perform 3rd shear (horizontal)
+ ///////////////////////////////////////
+
+ // Calc 3rd shear (horizontal) destination image dimensions
+ widthOut = static_cast<unsigned int>( static_cast<float>( heightIn ) * fabs( angleSinus ) + static_cast<float>( widthIn ) * angleCosinus ) + 1u;
+
+ // Allocate the buffer for the 3rd shear
+ pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
+
+ if( nullptr == pixelsOut )
+ {
+ widthOut = 0u;
+ heightOut = 0u;
+
+ // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
+ // Nothing else to do if the memory allocation fails.
+ return;
+ }
+
+ offset = ( angleSinus >= 0.f ) ? -angleSinus * angleTangent * static_cast<float>( widthIn - 1u ) : angleTangent * ( static_cast<float>( widthIn - 1u ) * -angleSinus + ( 1.f - static_cast<float>( heightOut ) ) );
+
+ for( unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent )
+ {
+ const int shear = static_cast<int>( floor( offset ) );
+ HorizontalSkew( tmpPixelsInPtr.get(), tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>( shear ) );
+ }
+
+ // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
+ // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the caller to this function.
+}
+
} /* namespace Platform */
} /* namespace Internal */
} /* namespace Dali */