X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=platform-abstractions%2Fportable%2Fimage-operations.cpp;h=f759d39a187059c27d09438cbcf692c98a43489a;hb=316892a6d5196a469bfda0bd56bca85dfce8271b;hp=bf6ff0b64042dd7c58d995a1451c95742985d0d6;hpb=5c0a37bfda9d6655dd4f17442dedcbb022b500fd;p=platform%2Fcore%2Fuifw%2Fdali-adaptor.git diff --git a/platform-abstractions/portable/image-operations.cpp b/platform-abstractions/portable/image-operations.cpp index bf6ff0b..f759d39 100644 --- a/platform-abstractions/portable/image-operations.cpp +++ b/platform-abstractions/portable/image-operations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,12 @@ #include #include #include +#include #include +#include #include +#include +#include // INTERNAL INCLUDES @@ -36,6 +40,18 @@ namespace Platform namespace { +// The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders. +// A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise. +// We can optionally use a Vector4 color here, but at reduced fill speed. +const uint8_t BORDER_FILL_VALUE( 0x00 ); +// A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions. +const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 ); + +// Constants used by the ImageResampler. +const float DEFAULT_SOURCE_GAMMA = 1.75f; ///< Default source gamma value used in the Resampler() function. Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction. +const float FILTER_SCALE = 1.f; ///< Default filter scale value used in the Resampler() function. Filter scale - values < 1.0 cause aliasing, but create sharper looking mips. +const Resampler::Filter FILTER_TYPE = Resampler::LANCZOS4; ///< Default filter used in the Resampler() function. Possible Lanczos filters are: lanczos3, lanczos4, lanczos6, lanczos12 + using Integration::Bitmap; using Integration::BitmapPtr; typedef unsigned char PixelBuffer; @@ -112,12 +128,12 @@ void ValidateScalingParameters( const unsigned int inputWidth, 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" ); } } @@ -145,41 +161,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." ); -} - -/** - * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class. - * - * @param[in] bitmapWidth Width of image before processing. - * @param[in] bitmapHeight Height of image before processing. - * @param[in] requestedWidth Width of area to scale image into. Can be zero. - * @param[in] requestedHeight Height of area to scale image into. Can be zero. - * @return Dimensions of area to scale image into after special rules are applied. - * @see ImageAttributes - */ -ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight ) -{ - // If no dimensions have been requested, default to the source ones: - if( requestedWidth == 0 && requestedHeight == 0 ) - { - return ImageDimensions( bitmapWidth, bitmapHeight ); - } - - // If both dimensions have values requested, use them both: - if( requestedWidth != 0 && requestedHeight != 0 ) - { - return ImageDimensions( requestedWidth, requestedHeight ); - } - - // If only one of the dimensions has been requested, calculate the other from - // the requested one and the source image aspect ratio: - - if( requestedWidth != 0 ) - { - return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f ); - } - return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight ); + DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." ); } /** @@ -231,7 +213,6 @@ BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode ) */ ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source ) { - DALI_ASSERT_DEBUG( true && " " ); // Scale the input by the least extreme of the two dimensions: const float widthScale = target.GetX() / float(source.GetX()); const float heightScale = target.GetY() / float(source.GetY()); @@ -249,9 +230,9 @@ ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions sourc /** * @brief Work out the dimensions for a uniform scaling of the input to map it * into the target while effecting SCALE_TO_FILL scaling mode. - * @note The output dimensions will need either top and bottom or left and right - * to be cropped away unless the source was pre-cropped to match the destination - * aspect ratio. + * @note An image scaled into the output dimensions will need either top and + * bottom or left and right to be cropped away unless the source was pre-cropped + * to match the destination aspect ratio. */ ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source ) { @@ -334,12 +315,103 @@ ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions fitDimensions = FitForFitHeight( requestedSize, sourceSize ); break; } - }; + } return fitDimensions; } /** + * @brief Calculate the number of lines on the X and Y axis that need to be + * either added or removed with repect to the specified fitting mode. + * (e.g., nearest or linear). + * @param[in] sourceSize The size of the source image + * @param[in] fittingMode The fitting mode to use + * @param[in/out] requestedSize The target size that the image will be fitted to. + * If the source image is smaller than the requested size, the source is not scaled up. + * So we reduce the target size while keeping aspect by lowering resolution. + * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required) + * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required) + */ +void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop ) +{ + const unsigned int sourceWidth( sourceSize.GetWidth() ); + const unsigned int sourceHeight( sourceSize.GetHeight() ); + const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) ); + int finalWidth = 0; + int finalHeight = 0; + + switch( fittingMode ) + { + case FittingMode::FIT_WIDTH: + { + finalWidth = sourceWidth; + finalHeight = static_cast< float >( sourceWidth ) / targetAspect; + + columnsToCrop = 0; + scanlinesToCrop = -( finalHeight - sourceHeight ); + break; + } + + case FittingMode::FIT_HEIGHT: + { + finalWidth = static_cast< float >( sourceHeight ) * targetAspect; + finalHeight = sourceHeight; + + columnsToCrop = -( finalWidth - sourceWidth ); + scanlinesToCrop = 0; + break; + } + + case FittingMode::SHRINK_TO_FIT: + { + const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) ); + if( sourceAspect > targetAspect ) + { + finalWidth = sourceWidth; + finalHeight = static_cast< float >( sourceWidth ) / targetAspect; + + columnsToCrop = 0; + scanlinesToCrop = -( finalHeight - sourceHeight ); + } + else + { + finalWidth = static_cast< float >( sourceHeight ) * targetAspect; + finalHeight = sourceHeight; + + columnsToCrop = -( finalWidth - sourceWidth ); + scanlinesToCrop = 0; + } + break; + } + + case FittingMode::SCALE_TO_FILL: + { + const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) ); + if( sourceAspect > targetAspect ) + { + finalWidth = static_cast< float >( sourceHeight ) * targetAspect; + finalHeight = sourceHeight; + + columnsToCrop = -( finalWidth - sourceWidth ); + scanlinesToCrop = 0; + } + else + { + finalWidth = sourceWidth; + finalHeight = static_cast< float >( sourceWidth ) / targetAspect; + + columnsToCrop = 0; + scanlinesToCrop = -( finalHeight - sourceHeight ); + } + break; + } + } + + requestedSize.SetWidth( finalWidth ); + requestedSize.SetHeight( finalHeight ); +} + +/** * @brief Construct a bitmap with format and dimensions requested. */ BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height ) @@ -347,7 +419,7 @@ BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsign 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::DISCARD ); + 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; } @@ -367,27 +439,81 @@ BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, u return newBitmap; } +/** + * @brief Work out the desired width and height, accounting for zeros. + * + * @param[in] bitmapWidth Width of image before processing. + * @param[in] bitmapHeight Height of image before processing. + * @param[in] requestedWidth Width of area to scale image into. Can be zero. + * @param[in] requestedHeight Height of area to scale image into. Can be zero. + * @return Dimensions of area to scale image into after special rules are applied. + */ +ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight ) +{ + unsigned int maxSize = Dali::GetMaxTextureSize(); + + // If no dimensions have been requested, default to the source ones: + if( requestedWidth == 0 && requestedHeight == 0 ) + { + return ImageDimensions( std::min( bitmapWidth, maxSize ), std::min( bitmapHeight, maxSize ) ); + } + + // If both dimensions have values requested, use them both: + if( requestedWidth != 0 && requestedHeight != 0 ) + { + return ImageDimensions( std::min( requestedWidth, maxSize ), std::min( requestedHeight, 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 ); +} + } // namespace - unnamed +ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions ) +{ + return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ; +} + /** - * @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 bitmap to perform modifications on. + * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode. + * @param[in] fittingMode The fitting mode to use. + * + * @return A new bitmap with the padding and cropping required for fitting mode applied. + * If no modification is needed or possible, the passed in bitmap is returned. + */ +Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode ); + +/** + * @brief Adds horizontal or vertical borders to the source image. * - * @return The bitmap passed in if no scaling is needed or possible, else a new, - * smaller bitmap with the cropping required for the scaling mode applied. + * @param[in] targetPixels The destination image pointer to draw the borders on. + * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer. + * @param[in] targetDimensions The dimensions of the destination image. + * @param[in] padDimensions The columns and scanlines to pad with borders. */ -Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions ); +void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions ); BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode ) { @@ -402,10 +528,11 @@ BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode ); // Cut the bitmap according to the desired width and height so that the - // resulting bitmap has the same aspect ratio as the desired dimensions: - if( bitmap && bitmap->GetPackedPixelsProfile() && fittingMode == FittingMode::SCALE_TO_FILL ) + // resulting bitmap has the same aspect ratio as the desired dimensions. + // Add crop and add borders if necessary depending on fitting mode. + if( bitmap && bitmap->GetPackedPixelsProfile() ) { - bitmap = CropForScaleToFill( bitmap, desiredDimensions ); + bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode ); } // Examine the image pixels remaining after cropping and scaling to see if all @@ -419,69 +546,95 @@ BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, return bitmap; } -BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions ) +BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode ) { - const unsigned inputWidth = bitmap->GetImageWidth(); - const unsigned inputHeight = bitmap->GetImageHeight(); - const unsigned desiredWidth = desiredDimensions.GetWidth(); - const unsigned desiredHeight = desiredDimensions.GetHeight(); + const unsigned int inputWidth = bitmap->GetImageWidth(); + const unsigned int inputHeight = bitmap->GetImageHeight(); - if( desiredWidth < 1U || desiredHeight < 1U ) + if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u ) { - DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight ); + DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() ); } - else if( inputWidth != desiredWidth || inputHeight != desiredHeight ) + else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() ) { - const Vector2 desiredDims( desiredWidth, desiredHeight ); - - // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap: - // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one. - const float widthsRatio = inputWidth / float(desiredWidth); - const Vector2 scaledByWidth = desiredDims * widthsRatio; - const float heightsRatio = inputHeight / float(desiredHeight); - const Vector2 scaledByHeight = desiredDims * heightsRatio; - // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides: - const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height; - const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight; - - // Work out how many pixels to trim from top and bottom, and left and right: - // (We only ever do one dimension) - const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0; - const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f ); - - DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, inputWidth, inputHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" ); - - // Make a new bitmap with the central part of the loaded one if required: - if( scanlinesToTrim > 0 || columnsToTrim > 0 ) - { - 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 ); + // Calculate any padding or cropping that needs to be done based on the fitting mode. + // Note: If the desired size is larger than the original image, the desired size will be + // reduced while maintaining the aspect, in order to save unnecessary memory usage. + int scanlinesToCrop = 0; + int columnsToCrop = 0; - const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat ); + CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop ); - const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel; - PixelBuffer * const destPixels = croppedBitmap->GetBuffer(); - DALI_ASSERT_DEBUG( srcPixels && destPixels ); + unsigned int desiredWidth( desiredDimensions.GetWidth() ); + unsigned int desiredHeight( desiredDimensions.GetHeight() ); - // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time: - if( trimTopAndBottom ) + // Action the changes by making a new bitmap with the central part of the loaded one if required. + if( scanlinesToCrop != 0 || columnsToCrop != 0 ) + { + // Split the adding and removing of scanlines and columns into separate variables, + // so we can use one piece of generic code to action the changes. + unsigned int scanlinesToPad = 0; + unsigned int columnsToPad = 0; + if( scanlinesToCrop < 0 ) + { + scanlinesToPad = -scanlinesToCrop; + scanlinesToCrop = 0; + } + if( columnsToCrop < 0 ) { - memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel ); + columnsToPad = -columnsToCrop; + columnsToCrop = 0; + } + + // If there is no filtering, then the final image size can become very large, exit if larger than maximum. + if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) || + ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) ) + { + DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight ); + return bitmap; + } + + // Create a new bitmap with the desired size. + BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD ); + Integration::Bitmap::PackedPixelsProfile *packedView = croppedBitmap->GetPackedPixelsProfile(); + DALI_ASSERT_DEBUG( packedView ); + const Pixel::Format pixelFormat = bitmap->GetPixelFormat(); + packedView->ReserveBuffer( pixelFormat, desiredWidth, desiredHeight, desiredWidth, desiredHeight ); + + // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop. + // The cropping is added to the source pointer, and the padding is added to the destination. + const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat ); + const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel ); + PixelBuffer * const targetPixels = croppedBitmap->GetBuffer(); + PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel ); + DALI_ASSERT_DEBUG( sourcePixels && targetPixels ); + + // Copy the image data to the new bitmap. + // Optimize to a single memcpy if the left and right edges don't need a crop or a pad. + unsigned int outputSpan( desiredWidth * bytesPerPixel ); + if( columnsToCrop == 0 && columnsToPad == 0 ) + { + memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan ); } else { - for( unsigned y = 0; y < newHeight; ++y ) + // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time. + // Precalculate any constants to optimize the inner loop. + const unsigned int inputSpan( inputWidth * bytesPerPixel ); + const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel ); + const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad ); + + for( unsigned int y = 0; y < scanlinesToCopy; ++y ) { - memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * 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; } } @@ -489,6 +642,54 @@ BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimension return bitmap; } +void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions ) +{ + // Assign ints for faster access. + unsigned int desiredWidth( targetDimensions.GetWidth() ); + unsigned int desiredHeight( targetDimensions.GetHeight() ); + unsigned int columnsToPad( padDimensions.GetWidth() ); + unsigned int scanlinesToPad( padDimensions.GetHeight() ); + unsigned int outputSpan( desiredWidth * bytesPerPixel ); + + // Add letterboxing (symmetrical borders) if needed. + if( scanlinesToPad > 0 ) + { + // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number. + memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan ); + + // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct + // offset for odd numbers (as the top border is 1 pixel smaller in these cases. + unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 ); + + // Bottom border. + memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan ); + } + else if( columnsToPad > 0 ) + { + // Add a left and right border. + // Left: + // Pre-calculate span size outside of loop. + unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel ); + for( unsigned int y = 0; y < desiredHeight; ++y ) + { + memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth ); + } + + // Right: + // Pre-calculate the initial x offset as it is always the same for a small optimization. + // We subtract columnsToPad/2 from columnsToPad so that we have the correct + // offset for odd numbers (as the left border is 1 pixel smaller in these cases. + unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 ); + PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) ); + unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel; + + for( unsigned int y = 0; y < desiredHeight; ++y ) + { + memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth ); + } + } +} + Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, ImageDimensions desired, FittingMode::Type fittingMode, @@ -982,8 +1183,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 ) { @@ -1225,8 +1426,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 ) @@ -1322,6 +1523,200 @@ void LinearSample4BPP( const unsigned char * __restrict__ inPixels, LinearSampleGeneric( inPixels, inputDimensions, outPixels, desiredDimensions ); } +void LanczosSample( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions, + int numChannels, bool hasAlpha ) +{ + // Got from the test.cpp of the ImageResampler lib. + const float ONE_DIV_255 = 1.0f / 255.0f; + const int MAX_UNSIGNED_CHAR = std::numeric_limits::max(); + const int LINEAR_TO_SRGB_TABLE_SIZE = 4096; + const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0; + + static bool loadColorSpaces = true; + static float srgbToLinear[MAX_UNSIGNED_CHAR + 1]; + static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE]; + + if( loadColorSpaces ) // Only create the color space conversions on the first execution + { + loadColorSpaces = false; + + for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i ) + { + srgbToLinear[i] = pow( static_cast( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA ); + } + + const float invLinearToSrgbTableSize = 1.0f / static_cast( LINEAR_TO_SRGB_TABLE_SIZE ); + const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA; + + for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i ) + { + int k = static_cast( 255.0f * pow( static_cast( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f ); + if( k < 0 ) + { + k = 0; + } + else if( k > MAX_UNSIGNED_CHAR ) + { + k = MAX_UNSIGNED_CHAR; + } + linearToSrgb[i] = static_cast( k ); + } + } + + Resampler* resamplers[numChannels]; + Vector samples[numChannels]; + + const int srcWidth = inputDimensions.GetWidth(); + const int srcHeight = inputDimensions.GetHeight(); + const int dstWidth = desiredDimensions.GetWidth(); + const int dstHeight = desiredDimensions.GetHeight(); + + // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers + // used for the other components (a memory and slight cache efficiency optimization). + resamplers[0] = new Resampler( srcWidth, + srcHeight, + dstWidth, + dstHeight, + Resampler::BOUNDARY_CLAMP, + 0.0f, // sample_low, + 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high. + FILTER_TYPE, // The type of filter. Currently Lanczos. + NULL, // Pclist_x, + NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler. + FILTER_SCALE, // src_x_ofs, + FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay). + samples[0].Resize( srcWidth ); + for( int i = 1; i < numChannels; ++i ) + { + resamplers[i] = new Resampler( srcWidth, + srcHeight, + dstWidth, + dstHeight, + Resampler::BOUNDARY_CLAMP, + 0.0f, + 1.0f, + FILTER_TYPE, + resamplers[0]->get_clist_x(), + resamplers[0]->get_clist_y(), + FILTER_SCALE, + FILTER_SCALE ); + samples[i].Resize( srcWidth ); + } + + const int srcPitch = srcWidth * numChannels; + const int dstPitch = dstWidth * numChannels; + int dstY = 0; + + for( int srcY = 0; srcY < srcHeight; ++srcY ) + { + const unsigned char* pSrc = &inPixels[srcY * srcPitch]; + + for( int x = 0; x < srcWidth; ++x ) + { + for( int c = 0; c < numChannels; ++c ) + { + if( c == ALPHA_CHANNEL && hasAlpha ) + { + samples[c][x] = *pSrc++ * ONE_DIV_255; + } + else + { + samples[c][x] = srgbToLinear[*pSrc++]; + } + } + } + + for( int c = 0; c < numChannels; ++c ) + { + if( !resamplers[c]->put_line( &samples[c][0] ) ) + { + DALI_ASSERT_DEBUG( !"Out of memory" ); + } + } + + for(;;) + { + int compIndex; + for( compIndex = 0; compIndex < numChannels; ++compIndex ) + { + const float* pOutputSamples = resamplers[compIndex]->get_line(); + if( !pOutputSamples ) + { + break; + } + + const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha ); + DALI_ASSERT_DEBUG( dstY < dstHeight ); + unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex]; + + for( int x = 0; x < dstWidth; ++x ) + { + if( isAlphaChannel ) + { + int c = static_cast( 255.0f * pOutputSamples[x] + 0.5f ); + if( c < 0 ) + { + c = 0; + } + else if( c > MAX_UNSIGNED_CHAR ) + { + c = MAX_UNSIGNED_CHAR; + } + *pDst = static_cast( c ); + } + else + { + int j = static_cast( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f ); + if( j < 0 ) + { + j = 0; + } + else if( j >= LINEAR_TO_SRGB_TABLE_SIZE ) + { + j = LINEAR_TO_SRGB_TABLE_SIZE - 1; + } + *pDst = linearToSrgb[j]; + } + + pDst += numChannels; + } + } + if( compIndex < numChannels ) + { + break; + } + + ++dstY; + } + } + + // Delete the resamplers. + for( int i = 0; i < numChannels; ++i ) + { + delete resamplers[i]; + } +} + +void LanczosSample4BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 4, true ); +} + +void LanczosSample1BPP( const unsigned char * __restrict__ inPixels, + ImageDimensions inputDimensions, + unsigned char * __restrict__ outPixels, + ImageDimensions desiredDimensions ) +{ + // For L8 images + LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 1, false ); +} + // Dispatch to a format-appropriate linear sampling function: void LinearSample( const unsigned char * __restrict__ inPixels, ImageDimensions inDimensions,