X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=platform-abstractions%2Fportable%2Fimage-operations.cpp;h=a54eae018f453b5ecd39bf468cf11504c62a94fb;hb=8d61b6699d9f2466af87a35a894fb626fd5bf455;hp=6a5ee4b37eac12695fe601f3da5a4115a679bf2e;hpb=d96247d2bcba33c68ec23ccb56bb88913e37a6ae;p=platform%2Fcore%2Fuifw%2Fdali-adaptor.git diff --git a/platform-abstractions/portable/image-operations.cpp b/platform-abstractions/portable/image-operations.cpp index 6a5ee4b..a54eae0 100644 --- a/platform-abstractions/portable/image-operations.cpp +++ b/platform-abstractions/portable/image-operations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 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. @@ -21,8 +21,13 @@ #include #include #include +#include +#include #include +#include #include +#include +#include // INTERNAL INCLUDES @@ -43,6 +48,15 @@ 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; @@ -152,7 +166,7 @@ inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1, 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." ); } /** @@ -403,30 +417,17 @@ void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode:: } /** - * @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. + * @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; } @@ -441,27 +442,432 @@ BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, u */ 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( 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( 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( 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( static_cast( 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( static_cast( 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 ) @@ -482,14 +888,14 @@ ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, Image * 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] 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 A new bitmap with the padding and cropping required for fitting mode applied. * If no modification is needed or possible, the passed in bitmap is returned. */ -Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode ); +Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode ); /** * @brief Adds horizontal or vertical borders to the source image. @@ -501,41 +907,34 @@ Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, */ 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 ) +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. // Add crop and add borders if necessary depending on fitting mode. - if( bitmap && bitmap->GetPackedPixelsProfile() ) + if( bitmap ) { 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 ) +Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode ) { - const unsigned int inputWidth = bitmap->GetImageWidth(); - const unsigned int inputHeight = bitmap->GetImageHeight(); + const unsigned int inputWidth = bitmap.GetWidth(); + const unsigned int inputHeight = bitmap.GetHeight(); if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u ) { @@ -580,18 +979,16 @@ BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDim 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 ); + // 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 unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat ); - const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel ); - PixelBuffer * const targetPixels = croppedBitmap->GetBuffer(); + 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 ); @@ -619,7 +1016,7 @@ BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDim // 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 ) ); + AddBorders( croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) ); // Overwrite the loaded bitmap with the cropped version bitmap = croppedBitmap; } @@ -676,26 +1073,26 @@ void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, co } } -Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, +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; @@ -713,16 +1110,17 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, 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; } @@ -731,7 +1129,7 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, // 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 ); } } @@ -1169,8 +1567,8 @@ inline void PointSampleAddressablePixels( const uint8_t * inPixels, 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 ) { @@ -1412,8 +1810,8 @@ inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels, "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 ) @@ -1509,6 +1907,202 @@ void LinearSample4BPP( const unsigned char * __restrict__ inPixels, LinearSampleGeneric( 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::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. + 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( 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 ) +{ + 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, @@ -1550,6 +2144,216 @@ void LinearSample( const unsigned char * __restrict__ inPixels, } } +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 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( fabs( angleTangent ) * static_cast( heightIn ) ); + heightOut = heightIn; + + // Allocate the buffer for the 1st shear + pixelsOut = static_cast( 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( y ) ) : ( 0.5f + static_cast( y ) - static_cast( heightOut ) ) ); + + const int intShear = static_cast( floor( shear ) ); + HorizontalSkew( firstHorizontalSkewPixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast( 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( static_cast( widthIn ) * fabs( angleSinus ) + static_cast( heightIn ) * angleCosinus ); + + // Allocate the buffer for the 2nd shear + pixelsOut = static_cast( 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( widthIn - 1u ) : -( static_cast( widthIn ) - static_cast( widthOut ) ) ); + + unsigned int column = 0u; + for( column = 0u; column < widthOut; ++column, offset -= angleSinus ) + { + const int shear = static_cast( floor( offset ) ); + VerticalSkew( tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast( 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( static_cast( heightIn ) * fabs( angleSinus ) + static_cast( widthIn ) * angleCosinus ) + 1u; + + // Allocate the buffer for the 3rd shear + pixelsOut = static_cast( 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( widthIn - 1u ) : angleTangent * ( static_cast( widthIn - 1u ) * -angleSinus + ( 1.f - static_cast( heightOut ) ) ); + + for( unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent ) + { + const int shear = static_cast( floor( offset ) ); + HorizontalSkew( tmpPixelsInPtr.get(), tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast( 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 */