2 * Copyright (c) 2019 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/internal/imaging/common/image-operations.h>
26 #include <dali/integration-api/debug.h>
27 #include <dali/public-api/common/dali-vector.h>
28 #include <dali/public-api/math/vector2.h>
29 #include <third-party/resampler/resampler.h>
30 #include <dali/devel-api/adaptor-framework/image-loading.h>
44 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
45 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
46 // We can optionally use a Vector4 color here, but at reduced fill speed.
47 const uint8_t BORDER_FILL_VALUE( 0x00 );
48 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
49 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
51 // Constants used by the ImageResampler.
52 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.
53 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.
55 const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
56 const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians;
57 const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians;
58 const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians;
60 using Integration::Bitmap;
61 using Integration::BitmapPtr;
62 typedef unsigned char PixelBuffer;
65 * @brief 4 byte pixel structure.
73 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
76 * @brief RGB888 pixel structure.
83 } __attribute__((packed, aligned(1)));
86 * @brief RGB565 pixel typedefed from a short.
88 * Access fields by manual shifting and masking.
90 typedef uint16_t PixelRGB565;
93 * @brief a Pixel composed of two independent byte components.
99 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
102 #if defined(DEBUG_ENABLED)
104 * Disable logging of image operations or make it verbose from the commandline
105 * as follows (e.g., for dali demo app):
107 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
108 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
111 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
114 /** @return The greatest even number less than or equal to the argument. */
115 inline unsigned int EvenDown( const unsigned int a )
117 const unsigned int evened = a & ~1u;
122 * @brief Log bad parameters.
124 void ValidateScalingParameters( const unsigned int inputWidth,
125 const unsigned int inputHeight,
126 const unsigned int desiredWidth,
127 const unsigned int desiredHeight )
129 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
131 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
134 if( desiredWidth == 0u || desiredHeight == 0u )
136 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
139 if( inputWidth == 0u || inputHeight == 0u )
141 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
146 * @brief Do debug assertions common to all scanline halving functions.
147 * @note Inline and in anon namespace so should boil away in release builds.
149 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
151 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
152 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
153 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
157 * @brief Assertions on params to functions averaging pairs of scanlines.
158 * @note Inline as intended to boil away in release.
160 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
161 const uint8_t * const scanline2,
162 uint8_t* const outputScanline,
163 const size_t widthInComponents )
165 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
166 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
167 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
168 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
169 DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
173 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
175 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
177 BoxDimensionTest dimensionTest;
178 dimensionTest = BoxDimensionTestEither;
180 switch( fittingMode )
182 // Shrink to fit attempts to make one or zero dimensions smaller than the
183 // desired dimensions and one or two dimensions exactly the same as the desired
184 // ones, so as long as one dimension is larger than the desired size, box
185 // filtering can continue even if the second dimension is smaller than the
186 // desired dimensions:
187 case FittingMode::SHRINK_TO_FIT:
189 dimensionTest = BoxDimensionTestEither;
192 // Scale to fill mode keeps both dimensions at least as large as desired:
193 case FittingMode::SCALE_TO_FILL:
195 dimensionTest = BoxDimensionTestBoth;
198 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
199 case FittingMode::FIT_WIDTH:
201 dimensionTest = BoxDimensionTestX;
204 // X Dimension is ignored by definition in FIT_HEIGHT mode:
205 case FittingMode::FIT_HEIGHT:
207 dimensionTest = BoxDimensionTestY;
212 return dimensionTest;
216 * @brief Work out the dimensions for a uniform scaling of the input to map it
217 * into the target while effecting ShinkToFit scaling mode.
219 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
221 // Scale the input by the least extreme of the two dimensions:
222 const float widthScale = target.GetX() / float(source.GetX());
223 const float heightScale = target.GetY() / float(source.GetY());
224 const float scale = widthScale < heightScale ? widthScale : heightScale;
226 // Do no scaling at all if the result would increase area:
232 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
236 * @brief Work out the dimensions for a uniform scaling of the input to map it
237 * into the target while effecting SCALE_TO_FILL scaling mode.
238 * @note An image scaled into the output dimensions will need either top and
239 * bottom or left and right to be cropped away unless the source was pre-cropped
240 * to match the destination aspect ratio.
242 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
244 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
245 // Scale the input by the least extreme of the two dimensions:
246 const float widthScale = target.GetX() / float(source.GetX());
247 const float heightScale = target.GetY() / float(source.GetY());
248 const float scale = widthScale > heightScale ? widthScale : heightScale;
250 // Do no scaling at all if the result would increase area:
256 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
260 * @brief Work out the dimensions for a uniform scaling of the input to map it
261 * into the target while effecting FIT_WIDTH scaling mode.
263 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
265 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
266 const float scale = target.GetX() / float(source.GetX());
268 // Do no scaling at all if the result would increase area:
273 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
277 * @brief Work out the dimensions for a uniform scaling of the input to map it
278 * into the target while effecting FIT_HEIGHT scaling mode.
280 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
282 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
283 const float scale = target.GetY() / float(source.GetY());
285 // Do no scaling at all if the result would increase area:
291 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
295 * @brief Generate the rectangle to use as the target of a pixel sampling pass
296 * (e.g., nearest or linear).
298 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
300 ImageDimensions fitDimensions;
301 switch( fittingMode )
303 case FittingMode::SHRINK_TO_FIT:
305 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
308 case FittingMode::SCALE_TO_FILL:
310 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
313 case FittingMode::FIT_WIDTH:
315 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
318 case FittingMode::FIT_HEIGHT:
320 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
325 return fitDimensions;
329 * @brief Calculate the number of lines on the X and Y axis that need to be
330 * either added or removed with repect to the specified fitting mode.
331 * (e.g., nearest or linear).
332 * @param[in] sourceSize The size of the source image
333 * @param[in] fittingMode The fitting mode to use
334 * @param[in/out] requestedSize The target size that the image will be fitted to.
335 * If the source image is smaller than the requested size, the source is not scaled up.
336 * So we reduce the target size while keeping aspect by lowering resolution.
337 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
338 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
340 void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
342 const unsigned int sourceWidth( sourceSize.GetWidth() );
343 const unsigned int sourceHeight( sourceSize.GetHeight() );
344 const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
348 switch( fittingMode )
350 case FittingMode::FIT_WIDTH:
352 finalWidth = sourceWidth;
353 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
356 scanlinesToCrop = -( finalHeight - sourceHeight );
360 case FittingMode::FIT_HEIGHT:
362 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
363 finalHeight = sourceHeight;
365 columnsToCrop = -( finalWidth - sourceWidth );
370 case FittingMode::SHRINK_TO_FIT:
372 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
373 if( sourceAspect > targetAspect )
375 finalWidth = sourceWidth;
376 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
379 scanlinesToCrop = -( finalHeight - sourceHeight );
383 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
384 finalHeight = sourceHeight;
386 columnsToCrop = -( finalWidth - sourceWidth );
392 case FittingMode::SCALE_TO_FILL:
394 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
395 if( sourceAspect > targetAspect )
397 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
398 finalHeight = sourceHeight;
400 columnsToCrop = -( finalWidth - sourceWidth );
405 finalWidth = sourceWidth;
406 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
409 scanlinesToCrop = -( finalHeight - sourceHeight );
415 requestedSize.SetWidth( finalWidth );
416 requestedSize.SetHeight( finalHeight );
420 * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
422 Dali::Devel::PixelBuffer MakePixelBuffer( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
424 DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
426 // Allocate a pixel buffer to hold the image passed in:
427 auto newBitmap = Dali::Devel::PixelBuffer::New( width, height, pixelFormat );
429 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
430 memcpy( newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
435 * @brief Work out the desired width and height, accounting for zeros.
437 * @param[in] bitmapWidth Width of image before processing.
438 * @param[in] bitmapHeight Height of image before processing.
439 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
440 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
441 * @return Dimensions of area to scale image into after special rules are applied.
443 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
445 unsigned int maxSize = Dali::GetMaxTextureSize();
447 // If no dimensions have been requested, default to the source ones:
448 if( requestedWidth == 0 && requestedHeight == 0 )
450 if( bitmapWidth <= maxSize && bitmapHeight <= maxSize )
452 return ImageDimensions( bitmapWidth, bitmapHeight );
456 // Calculate the size from the max texture size and the source image aspect ratio
457 if( bitmapWidth > bitmapHeight )
459 return ImageDimensions( maxSize, bitmapHeight * maxSize / static_cast< float >( bitmapWidth ) + 0.5f );
463 return ImageDimensions( bitmapWidth * maxSize / static_cast< float >( bitmapHeight ) + 0.5f, maxSize );
468 // If both dimensions have values requested, use them both:
469 if( requestedWidth != 0 && requestedHeight != 0 )
471 if( requestedWidth <= maxSize && requestedHeight <= maxSize )
473 return ImageDimensions( requestedWidth, requestedHeight );
477 // Calculate the size from the max texture size and the source image aspect ratio
478 if( requestedWidth > requestedHeight )
480 return ImageDimensions( maxSize, requestedHeight * maxSize / static_cast< float >( requestedWidth ) + 0.5f );
484 return ImageDimensions( requestedWidth * maxSize / static_cast< float >( requestedHeight ) + 0.5f, maxSize );
489 // Only one of the dimensions has been requested. Calculate the other from
490 // the requested one and the source image aspect ratio:
491 if( requestedWidth != 0 )
493 requestedWidth = std::min( requestedWidth, maxSize );
494 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
497 requestedHeight = std::min( requestedHeight, maxSize );
498 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
502 * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
504 * @note It allocates memory for the returned @p pixelsOut buffer.
505 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
506 * @note It may fail if malloc() fails to allocate memory.
508 * @param[in] pixelsIn The input buffer.
509 * @param[in] widthIn The width of the input buffer.
510 * @param[in] heightIn The height of the input buffer.
511 * @param[in] pixelSize The size of the pixel.
512 * @param[out] pixelsOut The rotated output buffer.
513 * @param[out] widthOut The width of the output buffer.
514 * @param[out] heightOut The height of the output buffer.
516 * @return Whether the rotation succeded.
518 bool Rotate90( const uint8_t* const pixelsIn,
519 unsigned int widthIn,
520 unsigned int heightIn,
521 unsigned int pixelSize,
523 unsigned int& widthOut,
524 unsigned int& heightOut )
526 // The new size of the image.
530 // Allocate memory for the rotated buffer.
531 pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
532 if( nullptr == pixelsOut )
537 // Return if the memory allocations fails.
541 // Rotate the buffer.
542 for( unsigned int y = 0u; y < heightIn; ++y )
544 const unsigned int srcLineIndex = y * widthIn;
545 const unsigned int dstX = y;
546 for( unsigned int x = 0u; x < widthIn; ++x )
548 const unsigned int dstY = heightOut - x - 1u;
549 const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
550 const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
552 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
554 *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
563 * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
565 * @note It allocates memory for the returned @p pixelsOut buffer.
566 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
567 * @note It may fail if malloc() fails to allocate memory.
569 * @param[in] pixelsIn The input buffer.
570 * @param[in] widthIn The width of the input buffer.
571 * @param[in] heightIn The height of the input buffer.
572 * @param[in] pixelSize The size of the pixel.
573 * @param[out] pixelsOut The rotated output buffer.
575 * @return Whether the rotation succeded.
577 bool Rotate180( const uint8_t* const pixelsIn,
578 unsigned int widthIn,
579 unsigned int heightIn,
580 unsigned int pixelSize,
581 uint8_t*& pixelsOut )
583 // Allocate memory for the rotated buffer.
584 pixelsOut = static_cast<uint8_t*>( malloc ( widthIn * heightIn * pixelSize ) );
585 if( nullptr == pixelsOut )
587 // Return if the memory allocations fails.
591 // Rotate the buffer.
592 for( unsigned int y = 0u; y < heightIn; ++y )
594 const unsigned int srcLineIndex = y * widthIn;
595 const unsigned int dstY = heightIn - y - 1u;
596 for( unsigned int x = 0u; x < widthIn; ++x )
598 const unsigned int dstX = widthIn - x - 1u;
599 const unsigned int dstIndex = pixelSize * ( dstY * widthIn + dstX );
600 const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
602 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
604 *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
613 * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
615 * @note It allocates memory for the returned @p pixelsOut buffer.
616 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
617 * @note It may fail if malloc() fails to allocate memory.
619 * @param[in] pixelsIn The input buffer.
620 * @param[in] widthIn The width of the input buffer.
621 * @param[in] heightIn The height of the input buffer.
622 * @param[in] pixelSize The size of the pixel.
623 * @param[out] pixelsOut The rotated output buffer.
624 * @param[out] widthOut The width of the output buffer.
625 * @param[out] heightOut The height of the output buffer.
627 * @return Whether the rotation succeded.
629 bool Rotate270( const uint8_t* const pixelsIn,
630 unsigned int widthIn,
631 unsigned int heightIn,
632 unsigned int pixelSize,
634 unsigned int& widthOut,
635 unsigned int& heightOut )
637 // The new size of the image.
641 // Allocate memory for the rotated buffer.
642 pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
643 if( nullptr == pixelsOut )
648 // Return if the memory allocations fails.
652 // Rotate the buffer.
653 for( unsigned int y = 0u; y < heightIn; ++y )
655 const unsigned int srcLineIndex = y * widthIn;
656 const unsigned int dstX = widthOut - y - 1u;
657 for( unsigned int x = 0u; x < widthIn; ++x )
659 const unsigned int dstY = x;
660 const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
661 const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
663 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
665 *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
674 * @brief Skews a row horizontally (with filtered weights)
676 * @note Limited to 45 degree skewing only.
677 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
679 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
680 * @param[in] srcWidth The width of the input pixel buffer.
681 * @param[in] pixelSize The size of the pixel.
682 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
683 * @param[in] dstWidth The width of the output pixel buffer.
684 * @param[in] row The row index.
685 * @param[in] offset The skew offset.
686 * @param[in] weight The relative weight of right pixel.
688 void HorizontalSkew( const uint8_t* const srcBufferPtr,
690 unsigned int pixelSize,
691 uint8_t*& dstBufferPtr,
699 // Fill gap left of skew with background.
700 memset( dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset );
703 unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
706 for( i = 0u; i < srcWidth; ++i )
708 // Loop through row pixels
709 const unsigned int srcIndex = pixelSize * ( row * srcWidth + i );
711 unsigned char src[4u] = { 0u, 0u, 0u, 0u };
712 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
714 src[channel] = *( srcBufferPtr + srcIndex + channel );
718 unsigned char left[4u] = { 0u, 0u, 0u, 0u };
719 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
721 left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
723 // Update left over on source
724 src[channel] -= ( left[channel] - oldLeft[channel] );
728 if( ( i + offset >= 0 ) && ( i + offset < dstWidth ) )
730 const unsigned int dstIndex = pixelSize * ( row * dstWidth + i + offset );
732 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
734 *( dstBufferPtr + dstIndex + channel ) = src[channel];
738 // Save leftover for next pixel in scan
739 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
741 oldLeft[channel] = left[channel];
745 // Go to rightmost point of skew
749 // If still in image bounds, put leftovers there
750 const unsigned int dstIndex = pixelSize * ( row * dstWidth + i );
752 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
754 *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
757 // Clear to the right of the skewed line with background
759 memset( dstBufferPtr + pixelSize * ( row * dstWidth + i ), 0u, pixelSize * ( dstWidth - i ) );
764 * @brief Skews a column vertically (with filtered weights)
766 * @note Limited to 45 degree skewing only.
767 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
769 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
770 * @param[in] srcWidth The width of the input pixel buffer.
771 * @param[in] srcHeight The height of the input pixel buffer.
772 * @param[in] pixelSize The size of the pixel.
773 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
774 * @param[in] dstWidth The width of the output pixel buffer.
775 * @param[in] dstHeight The height of the output pixel buffer.
776 * @param[in] column The column index.
777 * @param[in] offset The skew offset.
778 * @param[in] weight The relative weight of uppeer pixel.
780 void VerticalSkew( const uint8_t* const srcBufferPtr,
783 unsigned int pixelSize,
784 uint8_t*& dstBufferPtr,
791 for( int i = 0; i < offset; ++i )
793 // Fill gap above skew with background
794 const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
796 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
798 *( dstBufferPtr + dstIndex + channel ) = 0u;
802 unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
806 for( i = 0; i < srcHeight; ++i )
808 // Loop through column pixels
809 const unsigned int srcIndex = pixelSize * ( i * srcWidth + column );
811 unsigned char src[4u] = { 0u, 0u, 0u, 0u };
812 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
814 src[channel] = *( srcBufferPtr + srcIndex + channel );
820 unsigned char left[4u] = { 0u, 0u, 0u, 0u };
821 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
823 left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
824 // Update left over on source
825 src[channel] -= ( left[channel] - oldLeft[channel] );
829 if( ( yPos >= 0 ) && ( yPos < dstHeight ) )
831 const unsigned int dstIndex = pixelSize * ( yPos * dstWidth + column );
833 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
835 *( dstBufferPtr + dstIndex + channel ) = src[channel];
839 // Save leftover for next pixel in scan
840 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
842 oldLeft[channel] = left[channel];
846 // Go to bottom point of skew
850 // If still in image bounds, put leftovers there
851 const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
853 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
855 *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
859 while( ++i < dstHeight )
861 // Clear below skewed line with background
862 const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
864 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
866 *( dstBufferPtr + dstIndex + channel ) = 0u;
871 } // namespace - unnamed
873 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
875 return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
879 * @brief Apply cropping and padding for specified fitting mode.
881 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
882 * based on the fitting mode.
884 * This will add vertical or horizontal borders if necessary.
885 * Crop the source image data vertically or horizontally if necessary.
886 * The aspect of the source image is preserved.
887 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
888 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
889 * GPU scaling to be performed at render time giving the same result with less texture traversal.
891 * @param[in] bitmap The source pixel buffer to perform modifications on.
892 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
893 * @param[in] fittingMode The fitting mode to use.
895 * @return A new bitmap with the padding and cropping required for fitting mode applied.
896 * If no modification is needed or possible, the passed in bitmap is returned.
898 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
901 * @brief Adds horizontal or vertical borders to the source image.
903 * @param[in] targetPixels The destination image pointer to draw the borders on.
904 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
905 * @param[in] targetDimensions The dimensions of the destination image.
906 * @param[in] padDimensions The columns and scanlines to pad with borders.
908 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
910 Dali::Devel::PixelBuffer ApplyAttributesToBitmap( Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
914 // Calculate the desired box, accounting for a possible zero component:
915 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
917 // If a different size than the raw one has been requested, resize the image
918 // maximally using a repeated box filter without making it smaller than the
919 // requested size in either dimension:
920 bitmap = DownscaleBitmap( bitmap, desiredDimensions, fittingMode, samplingMode );
922 // Cut the bitmap according to the desired width and height so that the
923 // resulting bitmap has the same aspect ratio as the desired dimensions.
924 // Add crop and add borders if necessary depending on fitting mode.
927 bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
934 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
936 const unsigned int inputWidth = bitmap.GetWidth();
937 const unsigned int inputHeight = bitmap.GetHeight();
939 if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
941 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
943 else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
945 // Calculate any padding or cropping that needs to be done based on the fitting mode.
946 // Note: If the desired size is larger than the original image, the desired size will be
947 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
948 int scanlinesToCrop = 0;
949 int columnsToCrop = 0;
951 CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
953 unsigned int desiredWidth( desiredDimensions.GetWidth() );
954 unsigned int desiredHeight( desiredDimensions.GetHeight() );
956 // Action the changes by making a new bitmap with the central part of the loaded one if required.
957 if( scanlinesToCrop != 0 || columnsToCrop != 0 )
959 // Split the adding and removing of scanlines and columns into separate variables,
960 // so we can use one piece of generic code to action the changes.
961 unsigned int scanlinesToPad = 0;
962 unsigned int columnsToPad = 0;
963 if( scanlinesToCrop < 0 )
965 scanlinesToPad = -scanlinesToCrop;
968 if( columnsToCrop < 0 )
970 columnsToPad = -columnsToCrop;
974 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
975 if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
976 ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
978 DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
982 // Create new PixelBuffer with the desired size.
983 const auto pixelFormat = bitmap.GetPixelFormat();
985 auto croppedBitmap = Devel::PixelBuffer::New( desiredWidth, desiredHeight, pixelFormat );
987 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
988 // The cropping is added to the source pointer, and the padding is added to the destination.
989 const auto bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
990 const PixelBuffer * const sourcePixels = bitmap.GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
991 PixelBuffer * const targetPixels = croppedBitmap.GetBuffer();
992 PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
993 DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
995 // Copy the image data to the new bitmap.
996 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
997 unsigned int outputSpan( desiredWidth * bytesPerPixel );
998 if( columnsToCrop == 0 && columnsToPad == 0 )
1000 memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
1004 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
1005 // Precalculate any constants to optimize the inner loop.
1006 const unsigned int inputSpan( inputWidth * bytesPerPixel );
1007 const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
1008 const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
1010 for( unsigned int y = 0; y < scanlinesToCopy; ++y )
1012 memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
1016 // Add vertical or horizontal borders to the final image (if required).
1017 desiredDimensions.SetWidth( desiredWidth );
1018 desiredDimensions.SetHeight( desiredHeight );
1019 AddBorders( croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
1020 // Overwrite the loaded bitmap with the cropped version
1021 bitmap = croppedBitmap;
1028 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
1030 // Assign ints for faster access.
1031 unsigned int desiredWidth( targetDimensions.GetWidth() );
1032 unsigned int desiredHeight( targetDimensions.GetHeight() );
1033 unsigned int columnsToPad( padDimensions.GetWidth() );
1034 unsigned int scanlinesToPad( padDimensions.GetHeight() );
1035 unsigned int outputSpan( desiredWidth * bytesPerPixel );
1037 // Add letterboxing (symmetrical borders) if needed.
1038 if( scanlinesToPad > 0 )
1040 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1041 memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
1043 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1044 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1045 unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
1048 memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
1050 else if( columnsToPad > 0 )
1052 // Add a left and right border.
1054 // Pre-calculate span size outside of loop.
1055 unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
1056 for( unsigned int y = 0; y < desiredHeight; ++y )
1058 memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
1062 // Pre-calculate the initial x offset as it is always the same for a small optimization.
1063 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1064 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1065 unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
1066 PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
1067 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1069 for( unsigned int y = 0; y < desiredHeight; ++y )
1071 memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
1076 Dali::Devel::PixelBuffer DownscaleBitmap( Dali::Devel::PixelBuffer bitmap,
1077 ImageDimensions desired,
1078 FittingMode::Type fittingMode,
1079 SamplingMode::Type samplingMode )
1081 // Source dimensions as loaded from resources (e.g. filesystem):
1082 auto bitmapWidth = bitmap.GetWidth();
1083 auto bitmapHeight = bitmap.GetHeight();
1084 // Desired dimensions (the rectangle to fit the source image to):
1085 auto desiredWidth = desired.GetWidth();
1086 auto desiredHeight = desired.GetHeight();
1088 Dali::Devel::PixelBuffer outputBitmap { bitmap };
1090 // If a different size than the raw one has been requested, resize the image:
1092 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1093 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
1095 auto pixelFormat = bitmap.GetPixelFormat();
1097 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1098 unsigned int shrunkWidth = -1, shrunkHeight = -1;
1099 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
1101 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1102 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
1103 const unsigned int filteredWidth = filteredDimensions.GetWidth();
1104 const unsigned int filteredHeight = filteredDimensions.GetHeight();
1106 // Run a filter to scale down the bitmap if it needs it:
1107 bool filtered = false;
1108 if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
1110 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1111 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
1113 outputBitmap = Dali::Devel::PixelBuffer::New( filteredWidth, filteredHeight, pixelFormat );
1117 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1119 LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions );
1123 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight );
1129 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1130 if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
1132 outputBitmap = MakePixelBuffer( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
1136 return outputBitmap;
1142 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1143 * @param test Which combination of the two dimensions matter for terminating the filtering.
1144 * @param scaledWidth The width of the current downscaled image.
1145 * @param scaledHeight The height of the current downscaled image.
1146 * @param desiredWidth The target width for the downscaling.
1147 * @param desiredHeight The target height for the downscaling.
1149 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
1151 bool keepScaling = false;
1152 const unsigned int nextWidth = scaledWidth >> 1u;
1153 const unsigned int nextHeight = scaledHeight >> 1u;
1155 if( nextWidth >= 1u && nextHeight >= 1u )
1159 case BoxDimensionTestEither:
1161 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1164 case BoxDimensionTestBoth:
1166 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1169 case BoxDimensionTestX:
1171 keepScaling = nextWidth >= desiredWidth;
1174 case BoxDimensionTestY:
1176 keepScaling = nextHeight >= desiredHeight;
1186 * @brief A shared implementation of the overall iterative box filter
1187 * downscaling algorithm.
1189 * Specialise this for particular pixel formats by supplying the number of bytes
1190 * per pixel and two functions: one for averaging pairs of neighbouring pixels
1191 * on a single scanline, and a second for averaging pixels at corresponding
1192 * positions on different scanlines.
1195 int BYTES_PER_PIXEL,
1196 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
1197 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
1199 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
1200 const unsigned int inputWidth,
1201 const unsigned int inputHeight,
1202 const unsigned int desiredWidth,
1203 const unsigned int desiredHeight,
1204 BoxDimensionTest dimensionTest,
1206 unsigned& outHeight )
1212 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
1214 // Scale the image until it would be smaller than desired, stopping if the
1215 // resulting height or width would be less than 1:
1216 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
1217 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
1219 const unsigned int lastWidth = scaledWidth;
1221 scaledHeight >>= 1u;
1223 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
1225 const unsigned int lastScanlinePair = scaledHeight - 1;
1227 // Scale pairs of scanlines until any spare one at the end is dropped:
1228 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
1230 // Scale two scanlines horizontally:
1231 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
1232 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
1234 // Scale vertical pairs of pixels while the last two scanlines are still warm in
1235 // the CPU cache(s):
1236 // Note, better access patterns for cache-coherence are possible for very large
1237 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1238 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1240 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
1241 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
1242 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1247 ///@note: we could finish off with one of two mutually exclusive passes, one squashing horizontally as far as possible, and the other vertically, if we knew a following cpu point or bilinear filter would restore the desired aspect ratio.
1248 outWidth = scaledWidth;
1249 outHeight = scaledHeight;
1254 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
1256 DebugAssertScanlineParameters( pixels, width );
1258 const unsigned int lastPair = EvenDown( width - 2 );
1260 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1262 // Load all the byte pixel components we need:
1263 const unsigned int c11 = pixels[pixel * 3];
1264 const unsigned int c12 = pixels[pixel * 3 + 1];
1265 const unsigned int c13 = pixels[pixel * 3 + 2];
1266 const unsigned int c21 = pixels[pixel * 3 + 3];
1267 const unsigned int c22 = pixels[pixel * 3 + 4];
1268 const unsigned int c23 = pixels[pixel * 3 + 5];
1270 // Save the averaged byte pixel components:
1271 pixels[outPixel * 3] = static_cast<unsigned char>( AverageComponent( c11, c21 ) );
1272 pixels[outPixel * 3 + 1] = static_cast<unsigned char>( AverageComponent( c12, c22 ) );
1273 pixels[outPixel * 3 + 2] = static_cast<unsigned char>( AverageComponent( c13, c23 ) );
1277 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
1279 DebugAssertScanlineParameters( pixels, width );
1280 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1282 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1284 const unsigned int lastPair = EvenDown( width - 2 );
1286 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1288 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
1289 alignedPixels[outPixel] = averaged;
1293 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
1295 DebugAssertScanlineParameters( pixels, width );
1296 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1298 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1300 const unsigned int lastPair = EvenDown( width - 2 );
1302 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1304 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
1305 alignedPixels[outPixel] = averaged;
1309 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
1311 DebugAssertScanlineParameters( pixels, width );
1313 const unsigned int lastPair = EvenDown( width - 2 );
1315 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1317 // Load all the byte pixel components we need:
1318 const unsigned int c11 = pixels[pixel * 2];
1319 const unsigned int c12 = pixels[pixel * 2 + 1];
1320 const unsigned int c21 = pixels[pixel * 2 + 2];
1321 const unsigned int c22 = pixels[pixel * 2 + 3];
1323 // Save the averaged byte pixel components:
1324 pixels[outPixel * 2] = static_cast<unsigned char>( AverageComponent( c11, c21 ) );
1325 pixels[outPixel * 2 + 1] = static_cast<unsigned char>( AverageComponent( c12, c22 ) );
1329 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
1331 DebugAssertScanlineParameters( pixels, width );
1333 const unsigned int lastPair = EvenDown( width - 2 );
1335 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1337 // Load all the byte pixel components we need:
1338 const unsigned int c1 = pixels[pixel];
1339 const unsigned int c2 = pixels[pixel + 1];
1341 // Save the averaged byte pixel component:
1342 pixels[outPixel] = static_cast<unsigned char>( AverageComponent( c1, c2 ) );
1347 * @ToDo: Optimise for ARM using a 4 bytes at a time loop wrapped around the single ARMV6 instruction: UHADD8 R4, R0, R5. Note, this is not neon. It runs in the normal integer pipeline so there is no downside like a stall moving between integer and copro, or extra power for clocking-up the idle copro.
1348 * if (widthInComponents >= 7) { word32* aligned1 = scanline1 + 3 & 3; word32* aligned1_end = scanline1 + widthInPixels & 3; while(aligned1 < aligned1_end) { UHADD8 *aligned1++, *aligned2++, *alignedoutput++ } .. + 0 to 3 spare pixels at each end.
1350 void AverageScanlines1( const unsigned char * const scanline1,
1351 const unsigned char * const __restrict__ scanline2,
1352 unsigned char* const outputScanline,
1353 const unsigned int width )
1355 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
1357 for( unsigned int component = 0; component < width; ++component )
1359 outputScanline[component] = static_cast<unsigned char>( AverageComponent( scanline1[component], scanline2[component] ) );
1363 void AverageScanlines2( const unsigned char * const scanline1,
1364 const unsigned char * const __restrict__ scanline2,
1365 unsigned char* const outputScanline,
1366 const unsigned int width )
1368 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1370 for( unsigned int component = 0; component < width * 2; ++component )
1372 outputScanline[component] = static_cast<unsigned char>( AverageComponent( scanline1[component], scanline2[component] ) );
1376 void AverageScanlines3( const unsigned char * const scanline1,
1377 const unsigned char * const __restrict__ scanline2,
1378 unsigned char* const outputScanline,
1379 const unsigned int width )
1381 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
1383 for( unsigned int component = 0; component < width * 3; ++component )
1385 outputScanline[component] = static_cast<unsigned char>( AverageComponent( scanline1[component], scanline2[component] ) );
1389 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
1390 const unsigned char * const __restrict__ scanline2,
1391 unsigned char * const outputScanline,
1392 const unsigned int width )
1394 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
1395 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1396 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1397 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1399 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1400 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1401 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1403 for( unsigned int pixel = 0; pixel < width; ++pixel )
1405 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1409 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1410 const unsigned char * const __restrict__ scanline2,
1411 unsigned char * const outputScanline,
1412 const unsigned int width )
1414 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1415 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1416 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1417 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1419 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1420 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1421 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1423 for( unsigned int pixel = 0; pixel < width; ++pixel )
1425 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1429 /// Dispatch to pixel format appropriate box filter downscaling functions.
1430 void DownscaleInPlacePow2( unsigned char * const pixels,
1431 Pixel::Format pixelFormat,
1432 unsigned int inputWidth,
1433 unsigned int inputHeight,
1434 unsigned int desiredWidth,
1435 unsigned int desiredHeight,
1436 FittingMode::Type fittingMode,
1437 SamplingMode::Type samplingMode,
1439 unsigned& outHeight )
1441 outWidth = inputWidth;
1442 outHeight = inputHeight;
1443 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1444 if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1446 // Check the pixel format is one that is supported:
1447 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1449 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1451 if( pixelFormat == Pixel::RGBA8888 )
1453 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1455 else if( pixelFormat == Pixel::RGB888 )
1457 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1459 else if( pixelFormat == Pixel::RGB565 )
1461 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1463 else if( pixelFormat == Pixel::LA88 )
1465 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1467 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1469 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1473 DALI_ASSERT_DEBUG( false && "Inner branch conditions don't match outer branch." );
1479 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1483 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1484 unsigned int inputWidth,
1485 unsigned int inputHeight,
1486 unsigned int desiredWidth,
1487 unsigned int desiredHeight,
1488 BoxDimensionTest dimensionTest,
1490 unsigned& outHeight )
1492 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1495 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1496 unsigned int inputWidth,
1497 unsigned int inputHeight,
1498 unsigned int desiredWidth,
1499 unsigned int desiredHeight,
1500 BoxDimensionTest dimensionTest,
1502 unsigned& outHeight )
1504 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1505 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1508 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1509 unsigned int inputWidth,
1510 unsigned int inputHeight,
1511 unsigned int desiredWidth,
1512 unsigned int desiredHeight,
1513 BoxDimensionTest dimensionTest,
1514 unsigned int& outWidth,
1515 unsigned int& outHeight )
1517 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1521 * @copydoc DownscaleInPlacePow2RGB888
1523 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1525 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1526 unsigned int inputWidth,
1527 unsigned int inputHeight,
1528 unsigned int desiredWidth,
1529 unsigned int desiredHeight,
1530 BoxDimensionTest dimensionTest,
1532 unsigned& outHeight )
1534 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1537 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1538 unsigned int inputWidth,
1539 unsigned int inputHeight,
1540 unsigned int desiredWidth,
1541 unsigned int desiredHeight,
1542 BoxDimensionTest dimensionTest,
1543 unsigned int& outWidth,
1544 unsigned int& outHeight )
1546 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1553 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1555 * Template is used purely as a type-safe code generator in this one
1556 * compilation unit. Generated code is inlined into type-specific wrapper
1557 * functions below which are exported to rest of module.
1559 template<typename PIXEL>
1560 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1561 unsigned int inputWidth,
1562 unsigned int inputHeight,
1563 uint8_t * outPixels,
1564 unsigned int desiredWidth,
1565 unsigned int desiredHeight )
1567 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1568 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1569 "The input and output buffers must not overlap for an upscaling.");
1570 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, ...)." );
1571 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, ...)." );
1573 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1577 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1578 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1579 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1580 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1582 unsigned int inY = 0;
1583 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1585 // Round fixed point y coordinate to nearest integer:
1586 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1587 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1588 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1590 DALI_ASSERT_DEBUG( integerY < inputHeight );
1591 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1592 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1594 unsigned int inX = 0;
1595 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1597 // Round the fixed-point x coordinate to an integer:
1598 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1599 const PIXEL* const inPixelAddress = &inScanline[integerX];
1600 const PIXEL pixel = *inPixelAddress;
1601 outScanline[outX] = pixel;
1611 void PointSample4BPP( const unsigned char * inPixels,
1612 unsigned int inputWidth,
1613 unsigned int inputHeight,
1614 unsigned char * outPixels,
1615 unsigned int desiredWidth,
1616 unsigned int desiredHeight )
1618 PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1622 void PointSample2BPP( const unsigned char * inPixels,
1623 unsigned int inputWidth,
1624 unsigned int inputHeight,
1625 unsigned char * outPixels,
1626 unsigned int desiredWidth,
1627 unsigned int desiredHeight )
1629 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1633 void PointSample1BPP( const unsigned char * inPixels,
1634 unsigned int inputWidth,
1635 unsigned int inputHeight,
1636 unsigned char * outPixels,
1637 unsigned int desiredWidth,
1638 unsigned int desiredHeight )
1640 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1644 * RGB888 is a special case as its pixels are not aligned addressable units.
1646 void PointSample3BPP( const uint8_t * inPixels,
1647 unsigned int inputWidth,
1648 unsigned int inputHeight,
1649 uint8_t * outPixels,
1650 unsigned int desiredWidth,
1651 unsigned int desiredHeight )
1653 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1657 const unsigned int BYTES_PER_PIXEL = 3;
1659 // Generate fixed-point 16.16 deltas in input image coordinates:
1660 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1661 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1663 // Step through output image in whole integer pixel steps while tracking the
1664 // corresponding locations in the input image using 16.16 fixed-point
1666 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1667 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1669 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1670 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1671 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1672 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1674 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1676 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1677 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1678 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1680 // Issue loads for all pixel color components up-front:
1681 const unsigned int c0 = inPixelAddress[0];
1682 const unsigned int c1 = inPixelAddress[1];
1683 const unsigned int c2 = inPixelAddress[2];
1684 ///@ToDo: Optimise - Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads, versus using an RGB packed, aligned(1) struct and letting compiler pick a strategy.
1686 // Output the pixel components:
1687 outScanline[outX] = static_cast<uint8_t>( c0 );
1688 outScanline[outX + 1] = static_cast<uint8_t>( c1 );
1689 outScanline[outX + 2] = static_cast<uint8_t>( c2 );
1691 // Increment the fixed-point input coordinate:
1699 // Dispatch to a format-appropriate point sampling function:
1700 void PointSample( const unsigned char * inPixels,
1701 unsigned int inputWidth,
1702 unsigned int inputHeight,
1703 Pixel::Format pixelFormat,
1704 unsigned char * outPixels,
1705 unsigned int desiredWidth,
1706 unsigned int desiredHeight )
1708 // Check the pixel format is one that is supported:
1709 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1711 if( pixelFormat == Pixel::RGB888 )
1713 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1715 else if( pixelFormat == Pixel::RGBA8888 )
1717 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1719 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1721 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1723 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1725 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1729 DALI_ASSERT_DEBUG( 0 == "Inner branch conditions don't match outer branch." );
1734 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1738 // Linear sampling group below
1743 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1744 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1746 return static_cast<uint8_t>( BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical ) );
1749 /** @copydoc BilinearFilter1BPPByte */
1750 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1753 pixel.l = static_cast<uint8_t>( BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical ) );
1754 pixel.a = static_cast<uint8_t>( BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical ) );
1758 /** @copydoc BilinearFilter1BPPByte */
1759 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1762 pixel.r = static_cast<uint8_t>( BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical ) );
1763 pixel.g = static_cast<uint8_t>( BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical ) );
1764 pixel.b = static_cast<uint8_t>( BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical ) );
1768 /** @copydoc BilinearFilter1BPPByte */
1769 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1771 const PixelRGB565 pixel = static_cast<PixelRGB565>( (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1772 (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1773 BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical ) );
1777 /** @copydoc BilinearFilter1BPPByte */
1778 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1781 pixel.r = static_cast<uint8_t>( BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical ) );
1782 pixel.g = static_cast<uint8_t>( BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical ) );
1783 pixel.b = static_cast<uint8_t>( BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical ) );
1784 pixel.a = static_cast<uint8_t>( BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical ) );
1789 * @brief Generic version of bilinear sampling image resize function.
1790 * @note Limited to one compilation unit and exposed through type-specific
1791 * wrapper functions below.
1795 PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1796 bool DEBUG_ASSERT_ALIGNMENT
1798 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1799 ImageDimensions inputDimensions,
1800 unsigned char * __restrict__ outPixels,
1801 ImageDimensions desiredDimensions )
1803 const unsigned int inputWidth = inputDimensions.GetWidth();
1804 const unsigned int inputHeight = inputDimensions.GetHeight();
1805 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1806 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1808 DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1809 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1810 "Input and output buffers cannot overlap.");
1811 if( DEBUG_ASSERT_ALIGNMENT )
1813 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, ...)." );
1814 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, ...)." );
1817 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1821 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1822 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1823 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1824 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1826 unsigned int inY = 0;
1827 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1829 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1831 // Find the two scanlines to blend and the weight to blend with:
1832 const unsigned int integerY1 = inY >> 16u;
1833 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1834 const unsigned int inputYWeight = inY & 65535u;
1836 DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1837 DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1839 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1840 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1842 unsigned int inX = 0;
1843 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1845 // Work out the two pixel scanline offsets for this cluster of four samples:
1846 const unsigned int integerX1 = inX >> 16u;
1847 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1849 // Execute the loads:
1850 const PIXEL pixel1 = inScanline1[integerX1];
1851 const PIXEL pixel2 = inScanline2[integerX1];
1852 const PIXEL pixel3 = inScanline1[integerX2];
1853 const PIXEL pixel4 = inScanline2[integerX2];
1854 ///@ToDo Optimise - for 1 and 2 and 4 byte types to execute a single 2, 4, or 8 byte load per pair (caveat clamping) and let half of them be unaligned.
1856 // Weighted bilinear filter:
1857 const unsigned int inputXWeight = inX & 65535u;
1858 outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1868 // Format-specific linear scaling instantiations:
1870 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1871 ImageDimensions inputDimensions,
1872 unsigned char * __restrict__ outPixels,
1873 ImageDimensions desiredDimensions )
1875 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1878 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1879 ImageDimensions inputDimensions,
1880 unsigned char * __restrict__ outPixels,
1881 ImageDimensions desiredDimensions )
1883 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1886 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1887 ImageDimensions inputDimensions,
1888 unsigned char * __restrict__ outPixels,
1889 ImageDimensions desiredDimensions )
1891 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1894 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1895 ImageDimensions inputDimensions,
1896 unsigned char * __restrict__ outPixels,
1897 ImageDimensions desiredDimensions )
1899 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1902 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1903 ImageDimensions inputDimensions,
1904 unsigned char * __restrict__ outPixels,
1905 ImageDimensions desiredDimensions )
1907 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1911 void Resample( const unsigned char * __restrict__ inPixels,
1912 ImageDimensions inputDimensions,
1913 unsigned char * __restrict__ outPixels,
1914 ImageDimensions desiredDimensions,
1915 Resampler::Filter filterType,
1916 int numChannels, bool hasAlpha )
1918 // Got from the test.cpp of the ImageResampler lib.
1919 const float ONE_DIV_255 = 1.0f / 255.0f;
1920 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1921 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1922 const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
1924 static bool loadColorSpaces = true;
1925 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1926 static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1928 if( loadColorSpaces ) // Only create the color space conversions on the first execution
1930 loadColorSpaces = false;
1932 for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
1934 srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
1937 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
1938 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1940 for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
1942 int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
1947 else if( k > MAX_UNSIGNED_CHAR )
1949 k = MAX_UNSIGNED_CHAR;
1951 linearToSrgb[i] = static_cast<unsigned char>( k );
1955 std::vector<Resampler*> resamplers( numChannels );
1956 std::vector<Vector<float>> samples(numChannels);
1958 const int srcWidth = inputDimensions.GetWidth();
1959 const int srcHeight = inputDimensions.GetHeight();
1960 const int dstWidth = desiredDimensions.GetWidth();
1961 const int dstHeight = desiredDimensions.GetHeight();
1963 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1964 // used for the other components (a memory and slight cache efficiency optimization).
1965 resamplers[0] = new Resampler( srcWidth,
1969 Resampler::BOUNDARY_CLAMP,
1970 0.0f, // sample_low,
1971 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1972 filterType, // The type of filter.
1974 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1975 FILTER_SCALE, // src_x_ofs,
1976 FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1977 samples[0].Resize( srcWidth );
1978 for( int i = 1; i < numChannels; ++i )
1980 resamplers[i] = new Resampler( srcWidth,
1984 Resampler::BOUNDARY_CLAMP,
1988 resamplers[0]->get_clist_x(),
1989 resamplers[0]->get_clist_y(),
1992 samples[i].Resize( srcWidth );
1995 const int srcPitch = srcWidth * numChannels;
1996 const int dstPitch = dstWidth * numChannels;
1999 for( int srcY = 0; srcY < srcHeight; ++srcY )
2001 const unsigned char* pSrc = &inPixels[srcY * srcPitch];
2003 for( int x = 0; x < srcWidth; ++x )
2005 for( int c = 0; c < numChannels; ++c )
2007 if( c == ALPHA_CHANNEL && hasAlpha )
2009 samples[c][x] = *pSrc++ * ONE_DIV_255;
2013 samples[c][x] = srgbToLinear[*pSrc++];
2018 for( int c = 0; c < numChannels; ++c )
2020 if( !resamplers[c]->put_line( &samples[c][0] ) )
2022 DALI_ASSERT_DEBUG( !"Out of memory" );
2029 for( compIndex = 0; compIndex < numChannels; ++compIndex )
2031 const float* pOutputSamples = resamplers[compIndex]->get_line();
2032 if( !pOutputSamples )
2037 const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
2038 DALI_ASSERT_DEBUG( dstY < dstHeight );
2039 unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
2041 for( int x = 0; x < dstWidth; ++x )
2043 if( isAlphaChannel )
2045 int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
2050 else if( c > MAX_UNSIGNED_CHAR )
2052 c = MAX_UNSIGNED_CHAR;
2054 *pDst = static_cast<unsigned char>( c );
2058 int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
2063 else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
2065 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2067 *pDst = linearToSrgb[j];
2070 pDst += numChannels;
2073 if( compIndex < numChannels )
2082 // Delete the resamplers.
2083 for( int i = 0; i < numChannels; ++i )
2085 delete resamplers[i];
2089 void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
2090 ImageDimensions inputDimensions,
2091 unsigned char * __restrict__ outPixels,
2092 ImageDimensions desiredDimensions )
2094 Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true );
2097 void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
2098 ImageDimensions inputDimensions,
2099 unsigned char * __restrict__ outPixels,
2100 ImageDimensions desiredDimensions )
2103 Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false );
2106 // Dispatch to a format-appropriate linear sampling function:
2107 void LinearSample( const unsigned char * __restrict__ inPixels,
2108 ImageDimensions inDimensions,
2109 Pixel::Format pixelFormat,
2110 unsigned char * __restrict__ outPixels,
2111 ImageDimensions outDimensions )
2113 // Check the pixel format is one that is supported:
2114 if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
2116 if( pixelFormat == Pixel::RGB888 )
2118 LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
2120 else if( pixelFormat == Pixel::RGBA8888 )
2122 LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
2124 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
2126 LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
2128 else if( pixelFormat == Pixel::LA88 )
2130 LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
2132 else if ( pixelFormat == Pixel::RGB565 )
2134 LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
2138 DALI_ASSERT_DEBUG( 0 == "Inner branch conditions don't match outer branch." );
2143 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
2147 void RotateByShear( const uint8_t* const pixelsIn,
2148 unsigned int widthIn,
2149 unsigned int heightIn,
2150 unsigned int pixelSize,
2152 uint8_t*& pixelsOut,
2153 unsigned int& widthOut,
2154 unsigned int& heightOut )
2156 // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2158 // Do first the fast rotations to transform the angle into a (-45..45] range.
2160 float fastRotationPerformed = false;
2161 if( ( radians > Math::PI_4 ) && ( radians <= RAD_135 ) )
2163 // Angle in (45.0 .. 135.0]
2164 // Rotate image by 90 degrees into temporary image,
2165 // so it requires only an extra rotation angle
2166 // of -45.0 .. +45.0 to complete rotation.
2167 fastRotationPerformed = Rotate90( pixelsIn,
2175 if( !fastRotationPerformed )
2177 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2178 // The fast rotation failed.
2182 radians -= Math::PI_2;
2184 else if( ( radians > RAD_135 ) && ( radians <= RAD_225 ) )
2186 // Angle in (135.0 .. 225.0]
2187 // Rotate image by 180 degrees into temporary image,
2188 // so it requires only an extra rotation angle
2189 // of -45.0 .. +45.0 to complete rotation.
2191 fastRotationPerformed = Rotate180( pixelsIn,
2197 if( !fastRotationPerformed )
2199 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2200 // The fast rotation failed.
2204 radians -= Math::PI;
2206 heightOut = heightIn;
2208 else if( ( radians > RAD_225 ) && ( radians <= RAD_315 ) )
2210 // Angle in (225.0 .. 315.0]
2211 // Rotate image by 270 degrees into temporary image,
2212 // so it requires only an extra rotation angle
2213 // of -45.0 .. +45.0 to complete rotation.
2215 fastRotationPerformed = Rotate270( pixelsIn,
2223 if( !fastRotationPerformed )
2225 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2226 // The fast rotation failed.
2233 if( fabs( radians ) < Dali::Math::MACHINE_EPSILON_10 )
2235 // Nothing else to do if the angle is zero.
2236 // The rotation angle was 90, 180 or 270.
2238 // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2242 const uint8_t* const firstHorizontalSkewPixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2243 std::unique_ptr<uint8_t, void(*)(void*)> tmpPixelsInPtr( ( fastRotationPerformed ? pixelsOut : nullptr ), free );
2245 // Reset the input/output
2247 heightIn = heightOut;
2248 pixelsOut = nullptr;
2250 const float angleSinus = sin( radians );
2251 const float angleCosinus = cos( radians );
2252 const float angleTangent = tan( 0.5f * radians );
2254 ///////////////////////////////////////
2255 // Perform 1st shear (horizontal)
2256 ///////////////////////////////////////
2258 // Calculate first shear (horizontal) destination image dimensions
2260 widthOut = widthIn + static_cast<unsigned int>( fabs( angleTangent ) * static_cast<float>( heightIn ) );
2261 heightOut = heightIn;
2263 // Allocate the buffer for the 1st shear
2264 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2266 if( nullptr == pixelsOut )
2271 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2273 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Fast rotations'.
2274 // Nothing else to do if the memory allocation fails.
2278 for( unsigned int y = 0u; y < heightOut; ++y )
2280 const float shear = angleTangent * ( ( angleTangent >= 0.f ) ? ( 0.5f + static_cast<float>( y ) ) : ( 0.5f + static_cast<float>( y ) - static_cast<float>( heightOut ) ) );
2282 const int intShear = static_cast<int>( floor( shear ) );
2283 HorizontalSkew( firstHorizontalSkewPixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>( intShear ) );
2286 // Reset the 'pixel in' pointer with the output of the 'First Horizontal Skew' and free the memory allocated by the 'Fast Rotations'.
2287 tmpPixelsInPtr.reset( pixelsOut );
2288 unsigned int tmpWidthIn = widthOut;
2289 unsigned int tmpHeightIn = heightOut;
2291 // Reset the input/output
2292 pixelsOut = nullptr;
2294 ///////////////////////////////////////
2295 // Perform 2nd shear (vertical)
2296 ///////////////////////////////////////
2298 // Calc 2nd shear (vertical) destination image dimensions
2299 heightOut = static_cast<unsigned int>( static_cast<float>( widthIn ) * fabs( angleSinus ) + static_cast<float>( heightIn ) * angleCosinus );
2301 // Allocate the buffer for the 2nd shear
2302 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2304 if( nullptr == pixelsOut )
2309 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2310 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'First Horizontal Skew'.
2311 // Nothing else to do if the memory allocation fails.
2315 // Variable skew offset
2316 float offset = angleSinus * ( ( angleSinus > 0.f ) ? static_cast<float>( widthIn - 1u ) : -( static_cast<float>( widthIn ) - static_cast<float>( widthOut ) ) );
2318 unsigned int column = 0u;
2319 for( column = 0u; column < widthOut; ++column, offset -= angleSinus )
2321 const int shear = static_cast<int>( floor( offset ) );
2322 VerticalSkew( tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>( shear ) );
2324 // Reset the 'pixel in' pointer with the output of the 'Vertical Skew' and free the memory allocated by the 'First Horizontal Skew'.
2325 // Reset the input/output
2326 tmpPixelsInPtr.reset( pixelsOut );
2327 tmpWidthIn = widthOut;
2328 tmpHeightIn = heightOut;
2329 pixelsOut = nullptr;
2331 ///////////////////////////////////////
2332 // Perform 3rd shear (horizontal)
2333 ///////////////////////////////////////
2335 // Calc 3rd shear (horizontal) destination image dimensions
2336 widthOut = static_cast<unsigned int>( static_cast<float>( heightIn ) * fabs( angleSinus ) + static_cast<float>( widthIn ) * angleCosinus ) + 1u;
2338 // Allocate the buffer for the 3rd shear
2339 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2341 if( nullptr == pixelsOut )
2346 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2347 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2348 // Nothing else to do if the memory allocation fails.
2352 offset = ( angleSinus >= 0.f ) ? -angleSinus * angleTangent * static_cast<float>( widthIn - 1u ) : angleTangent * ( static_cast<float>( widthIn - 1u ) * -angleSinus + ( 1.f - static_cast<float>( heightOut ) ) );
2354 for( unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent )
2356 const int shear = static_cast<int>( floor( offset ) );
2357 HorizontalSkew( tmpPixelsInPtr.get(), tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>( shear ) );
2360 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2361 // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the caller to this function.
2364 void HorizontalShear( const uint8_t* const pixelsIn,
2365 unsigned int widthIn,
2366 unsigned int heightIn,
2367 unsigned int pixelSize,
2369 uint8_t*& pixelsOut,
2370 unsigned int& widthOut,
2371 unsigned int& heightOut )
2373 // Calculate the destination image dimensions.
2375 const float absRadians = fabs( radians );
2377 if( absRadians > Math::PI_4 )
2379 // Can't shear more than 45 degrees.
2383 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Can't shear more than 45 degrees (PI/4 radians). radians : %f\n", radians );
2387 widthOut = widthIn + static_cast<unsigned int>( absRadians * static_cast<float>( heightIn ) );
2388 heightOut = heightIn;
2390 // Allocate the buffer for the shear.
2391 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2393 if( nullptr == pixelsOut )
2398 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n" );
2402 for( unsigned int y = 0u; y < heightOut; ++y )
2404 const float shear = radians * ( ( radians >= 0.f ) ? ( 0.5f + static_cast<float>( y ) ) : ( 0.5f + static_cast<float>( y ) - static_cast<float>( heightOut ) ) );
2406 const int intShear = static_cast<int>( floor( shear ) );
2407 HorizontalSkew( pixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>( intShear ) );
2411 } /* namespace Platform */
2412 } /* namespace Internal */
2413 } /* namespace Dali */