2 * Copyright (c) 2018 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>
25 #include <dali/integration-api/debug.h>
26 #include <dali/public-api/common/dali-vector.h>
27 #include <dali/public-api/math/vector2.h>
28 #include <third-party/resampler/resampler.h>
29 #include <dali/devel-api/adaptor-framework/image-loading.h>
43 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
44 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
45 // We can optionally use a Vector4 color here, but at reduced fill speed.
46 const uint8_t BORDER_FILL_VALUE( 0x00 );
47 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
48 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
50 // Constants used by the ImageResampler.
51 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.
52 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.
54 const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
55 const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians;
56 const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians;
57 const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians;
59 using Integration::Bitmap;
60 using Integration::BitmapPtr;
61 typedef unsigned char PixelBuffer;
64 * @brief 4 byte pixel structure.
72 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
75 * @brief RGB888 pixel structure.
82 } __attribute__((packed, aligned(1)));
85 * @brief RGB565 pixel typedefed from a short.
87 * Access fields by manual shifting and masking.
89 typedef uint16_t PixelRGB565;
92 * @brief a Pixel composed of two independent byte components.
98 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
101 #if defined(DEBUG_ENABLED)
103 * Disable logging of image operations or make it verbose from the commandline
104 * as follows (e.g., for dali demo app):
106 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
107 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
110 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
113 /** @return The greatest even number less than or equal to the argument. */
114 inline unsigned int EvenDown( const unsigned int a )
116 const unsigned int evened = a & ~1u;
121 * @brief Log bad parameters.
123 void ValidateScalingParameters( const unsigned int inputWidth,
124 const unsigned int inputHeight,
125 const unsigned int desiredWidth,
126 const unsigned int desiredHeight )
128 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
130 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
133 if( desiredWidth == 0u || desiredHeight == 0u )
135 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
138 if( inputWidth == 0u || inputHeight == 0u )
140 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
145 * @brief Do debug assertions common to all scanline halving functions.
146 * @note Inline and in anon namespace so should boil away in release builds.
148 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
150 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
151 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
152 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
156 * @brief Assertions on params to functions averaging pairs of scanlines.
157 * @note Inline as intended to boil away in release.
159 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
160 const uint8_t * const scanline2,
161 uint8_t* const outputScanline,
162 const size_t widthInComponents )
164 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
165 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
166 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
167 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
168 DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
172 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
174 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
176 BoxDimensionTest dimensionTest;
177 dimensionTest = BoxDimensionTestEither;
179 switch( fittingMode )
181 // Shrink to fit attempts to make one or zero dimensions smaller than the
182 // desired dimensions and one or two dimensions exactly the same as the desired
183 // ones, so as long as one dimension is larger than the desired size, box
184 // filtering can continue even if the second dimension is smaller than the
185 // desired dimensions:
186 case FittingMode::SHRINK_TO_FIT:
188 dimensionTest = BoxDimensionTestEither;
191 // Scale to fill mode keeps both dimensions at least as large as desired:
192 case FittingMode::SCALE_TO_FILL:
194 dimensionTest = BoxDimensionTestBoth;
197 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
198 case FittingMode::FIT_WIDTH:
200 dimensionTest = BoxDimensionTestX;
203 // X Dimension is ignored by definition in FIT_HEIGHT mode:
204 case FittingMode::FIT_HEIGHT:
206 dimensionTest = BoxDimensionTestY;
211 return dimensionTest;
215 * @brief Work out the dimensions for a uniform scaling of the input to map it
216 * into the target while effecting ShinkToFit scaling mode.
218 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
220 // Scale the input by the least extreme of the two dimensions:
221 const float widthScale = target.GetX() / float(source.GetX());
222 const float heightScale = target.GetY() / float(source.GetY());
223 const float scale = widthScale < heightScale ? widthScale : heightScale;
225 // Do no scaling at all if the result would increase area:
231 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
235 * @brief Work out the dimensions for a uniform scaling of the input to map it
236 * into the target while effecting SCALE_TO_FILL scaling mode.
237 * @note An image scaled into the output dimensions will need either top and
238 * bottom or left and right to be cropped away unless the source was pre-cropped
239 * to match the destination aspect ratio.
241 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
243 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
244 // Scale the input by the least extreme of the two dimensions:
245 const float widthScale = target.GetX() / float(source.GetX());
246 const float heightScale = target.GetY() / float(source.GetY());
247 const float scale = widthScale > heightScale ? widthScale : heightScale;
249 // Do no scaling at all if the result would increase area:
255 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
259 * @brief Work out the dimensions for a uniform scaling of the input to map it
260 * into the target while effecting FIT_WIDTH scaling mode.
262 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
264 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
265 const float scale = target.GetX() / float(source.GetX());
267 // Do no scaling at all if the result would increase area:
272 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
276 * @brief Work out the dimensions for a uniform scaling of the input to map it
277 * into the target while effecting FIT_HEIGHT scaling mode.
279 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
281 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
282 const float scale = target.GetY() / float(source.GetY());
284 // Do no scaling at all if the result would increase area:
290 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
294 * @brief Generate the rectangle to use as the target of a pixel sampling pass
295 * (e.g., nearest or linear).
297 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
299 ImageDimensions fitDimensions;
300 switch( fittingMode )
302 case FittingMode::SHRINK_TO_FIT:
304 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
307 case FittingMode::SCALE_TO_FILL:
309 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
312 case FittingMode::FIT_WIDTH:
314 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
317 case FittingMode::FIT_HEIGHT:
319 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
324 return fitDimensions;
328 * @brief Calculate the number of lines on the X and Y axis that need to be
329 * either added or removed with repect to the specified fitting mode.
330 * (e.g., nearest or linear).
331 * @param[in] sourceSize The size of the source image
332 * @param[in] fittingMode The fitting mode to use
333 * @param[in/out] requestedSize The target size that the image will be fitted to.
334 * If the source image is smaller than the requested size, the source is not scaled up.
335 * So we reduce the target size while keeping aspect by lowering resolution.
336 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
337 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
339 void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
341 const unsigned int sourceWidth( sourceSize.GetWidth() );
342 const unsigned int sourceHeight( sourceSize.GetHeight() );
343 const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
347 switch( fittingMode )
349 case FittingMode::FIT_WIDTH:
351 finalWidth = sourceWidth;
352 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
355 scanlinesToCrop = -( finalHeight - sourceHeight );
359 case FittingMode::FIT_HEIGHT:
361 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
362 finalHeight = sourceHeight;
364 columnsToCrop = -( finalWidth - sourceWidth );
369 case FittingMode::SHRINK_TO_FIT:
371 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
372 if( sourceAspect > targetAspect )
374 finalWidth = sourceWidth;
375 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
378 scanlinesToCrop = -( finalHeight - sourceHeight );
382 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
383 finalHeight = sourceHeight;
385 columnsToCrop = -( finalWidth - sourceWidth );
391 case FittingMode::SCALE_TO_FILL:
393 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
394 if( sourceAspect > targetAspect )
396 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
397 finalHeight = sourceHeight;
399 columnsToCrop = -( finalWidth - sourceWidth );
404 finalWidth = sourceWidth;
405 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
408 scanlinesToCrop = -( finalHeight - sourceHeight );
414 requestedSize.SetWidth( finalWidth );
415 requestedSize.SetHeight( finalHeight );
419 * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
421 Dali::Devel::PixelBuffer MakePixelBuffer( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
423 DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
425 // Allocate a pixel buffer to hold the image passed in:
426 auto newBitmap = Dali::Devel::PixelBuffer::New( width, height, pixelFormat );
428 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
429 memcpy( newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
434 * @brief Work out the desired width and height, accounting for zeros.
436 * @param[in] bitmapWidth Width of image before processing.
437 * @param[in] bitmapHeight Height of image before processing.
438 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
439 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
440 * @return Dimensions of area to scale image into after special rules are applied.
442 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
444 unsigned int maxSize = Dali::GetMaxTextureSize();
446 // If no dimensions have been requested, default to the source ones:
447 if( requestedWidth == 0 && requestedHeight == 0 )
449 if( bitmapWidth <= maxSize && bitmapHeight <= maxSize )
451 return ImageDimensions( bitmapWidth, bitmapHeight );
455 // Calculate the size from the max texture size and the source image aspect ratio
456 if( bitmapWidth > bitmapHeight )
458 return ImageDimensions( maxSize, bitmapHeight * maxSize / static_cast< float >( bitmapWidth ) + 0.5f );
462 return ImageDimensions( bitmapWidth * maxSize / static_cast< float >( bitmapHeight ) + 0.5f, maxSize );
467 // If both dimensions have values requested, use them both:
468 if( requestedWidth != 0 && requestedHeight != 0 )
470 if( requestedWidth <= maxSize && requestedHeight <= maxSize )
472 return ImageDimensions( requestedWidth, requestedHeight );
476 // Calculate the size from the max texture size and the source image aspect ratio
477 if( requestedWidth > requestedHeight )
479 return ImageDimensions( maxSize, requestedHeight * maxSize / static_cast< float >( requestedWidth ) + 0.5f );
483 return ImageDimensions( requestedWidth * maxSize / static_cast< float >( requestedHeight ) + 0.5f, maxSize );
488 // Only one of the dimensions has been requested. Calculate the other from
489 // the requested one and the source image aspect ratio:
490 if( requestedWidth != 0 )
492 requestedWidth = std::min( requestedWidth, maxSize );
493 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
496 requestedHeight = std::min( requestedHeight, maxSize );
497 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
501 * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
503 * @note It allocates memory for the returned @p pixelsOut buffer.
504 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
506 * @param[in] pixelsIn The input buffer.
507 * @param[in] widthIn The width of the input buffer.
508 * @param[in] heightIn The height of the input buffer.
509 * @param[in] pixelSize The size of the pixel.
510 * @param[out] pixelsOut The rotated output buffer.
511 * @param[out] widthOut The width of the output buffer.
512 * @param[out] heightOut The height of the output buffer.
514 void Rotate90( const uint8_t* const pixelsIn,
515 unsigned int widthIn,
516 unsigned int heightIn,
517 unsigned int pixelSize,
519 unsigned int& widthOut,
520 unsigned int& heightOut )
522 // The new size of the image.
526 // Allocate memory for the rotated buffer.
527 pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
529 // Rotate the buffer.
530 for( unsigned int y = 0u; y < heightIn; ++y )
532 const unsigned int srcLineIndex = y * widthIn;
533 const unsigned int dstX = y;
534 for( unsigned int x = 0u; x < widthIn; ++x )
536 const unsigned int dstY = heightOut - x - 1u;
537 const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
538 const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
540 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
542 *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
549 * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
551 * @note It allocates memory for the returned @p pixelsOut buffer.
552 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
554 * @param[in] pixelsIn The input buffer.
555 * @param[in] widthIn The width of the input buffer.
556 * @param[in] heightIn The height of the input buffer.
557 * @param[in] pixelSize The size of the pixel.
558 * @param[out] pixelsOut The rotated output buffer.
560 void Rotate180( const uint8_t* const pixelsIn,
561 unsigned int widthIn,
562 unsigned int heightIn,
563 unsigned int pixelSize,
564 uint8_t*& pixelsOut )
566 // Allocate memory for the rotated buffer.
567 pixelsOut = static_cast<uint8_t*>( malloc ( widthIn * heightIn * pixelSize ) );
569 // Rotate the buffer.
570 for( unsigned int y = 0u; y < heightIn; ++y )
572 const unsigned int srcLineIndex = y * widthIn;
573 const unsigned int dstY = heightIn - y - 1u;
574 for( unsigned int x = 0u; x < widthIn; ++x )
576 const unsigned int dstX = widthIn - x - 1u;
577 const unsigned int dstIndex = pixelSize * ( dstY * widthIn + dstX );
578 const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
580 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
582 *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
589 * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
591 * @note It allocates memory for the returned @p pixelsOut buffer.
592 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
594 * @param[in] pixelsIn The input buffer.
595 * @param[in] widthIn The width of the input buffer.
596 * @param[in] heightIn The height of the input buffer.
597 * @param[in] pixelSize The size of the pixel.
598 * @param[out] pixelsOut The rotated output buffer.
599 * @param[out] widthOut The width of the output buffer.
600 * @param[out] heightOut The height of the output buffer.
602 void Rotate270( const uint8_t* const pixelsIn,
603 unsigned int widthIn,
604 unsigned int heightIn,
605 unsigned int pixelSize,
607 unsigned int& widthOut,
608 unsigned int& heightOut )
610 // The new size of the image.
614 // Allocate memory for the rotated buffer.
615 pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
617 // Rotate the buffer.
618 for( unsigned int y = 0u; y < heightIn; ++y )
620 const unsigned int srcLineIndex = y * widthIn;
621 const unsigned int dstX = widthOut - y - 1u;
622 for( unsigned int x = 0u; x < widthIn; ++x )
624 const unsigned int dstY = x;
625 const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
626 const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
628 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
630 *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
637 * @brief Skews a row horizontally (with filtered weights)
639 * @note Limited to 45 degree skewing only.
640 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
642 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
643 * @param[in] srcWidth The width of the input pixel buffer.
644 * @param[in] pixelSize The size of the pixel.
645 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
646 * @param[in] dstWidth The width of the output pixel buffer.
647 * @param[in] row The row index.
648 * @param[in] offset The skew offset.
649 * @param[in] weight The relative weight of right pixel.
651 void HorizontalSkew( const uint8_t* const srcBufferPtr,
653 unsigned int pixelSize,
654 uint8_t*& dstBufferPtr,
662 // Fill gap left of skew with background.
663 memset( dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset );
666 unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
669 for( i = 0u; i < srcWidth; ++i )
671 // Loop through row pixels
672 const unsigned int srcIndex = pixelSize * ( row * srcWidth + i );
674 unsigned char src[4u] = { 0u, 0u, 0u, 0u };
675 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
677 src[channel] = *( srcBufferPtr + srcIndex + channel );
681 unsigned char left[4u] = { 0u, 0u, 0u, 0u };
682 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
684 left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
686 // Update left over on source
687 src[channel] -= ( left[channel] - oldLeft[channel] );
691 if( ( i + offset >= 0 ) && ( i + offset < dstWidth ) )
693 const unsigned int dstIndex = pixelSize * ( row * dstWidth + i + offset );
695 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
697 *( dstBufferPtr + dstIndex + channel ) = src[channel];
701 // Save leftover for next pixel in scan
702 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
704 oldLeft[channel] = left[channel];
708 // Go to rightmost point of skew
712 // If still in image bounds, put leftovers there
713 const unsigned int dstIndex = pixelSize * ( row * dstWidth + i );
715 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
717 *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
720 // Clear to the right of the skewed line with background
722 memset( dstBufferPtr + pixelSize * ( row * dstWidth + i ), 0u, pixelSize * ( dstWidth - i ) );
727 * @brief Skews a column vertically (with filtered weights)
729 * @note Limited to 45 degree skewing only.
730 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
732 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
733 * @param[in] srcWidth The width of the input pixel buffer.
734 * @param[in] srcHeight The height of the input pixel buffer.
735 * @param[in] pixelSize The size of the pixel.
736 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
737 * @param[in] dstWidth The width of the output pixel buffer.
738 * @param[in] dstHeight The height of the output pixel buffer.
739 * @param[in] column The column index.
740 * @param[in] offset The skew offset.
741 * @param[in] weight The relative weight of uppeer pixel.
743 void VerticalSkew( const uint8_t* const srcBufferPtr,
746 unsigned int pixelSize,
747 uint8_t*& dstBufferPtr,
754 for( int i = 0; i < offset; ++i )
756 // Fill gap above skew with background
757 const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
759 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
761 *( dstBufferPtr + dstIndex + channel ) = 0u;
765 unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
769 for( i = 0; i < srcHeight; ++i )
771 // Loop through column pixels
772 const unsigned int srcIndex = pixelSize * ( i * srcWidth + column );
774 unsigned char src[4u] = { 0u, 0u, 0u, 0u };
775 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
777 src[channel] = *( srcBufferPtr + srcIndex + channel );
783 unsigned char left[4u] = { 0u, 0u, 0u, 0u };
784 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
786 left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
787 // Update left over on source
788 src[channel] -= ( left[channel] - oldLeft[channel] );
792 if( ( yPos >= 0 ) && ( yPos < dstHeight ) )
794 const unsigned int dstIndex = pixelSize * ( yPos * dstWidth + column );
796 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
798 *( dstBufferPtr + dstIndex + channel ) = src[channel];
802 // Save leftover for next pixel in scan
803 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
805 oldLeft[channel] = left[channel];
809 // Go to bottom point of skew
813 // If still in image bounds, put leftovers there
814 const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
816 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
818 *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
822 while( ++i < dstHeight )
824 // Clear below skewed line with background
825 const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
827 for( unsigned int channel = 0u; channel < pixelSize; ++channel )
829 *( dstBufferPtr + dstIndex + channel ) = 0u;
834 } // namespace - unnamed
836 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
838 return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
842 * @brief Apply cropping and padding for specified fitting mode.
844 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
845 * based on the fitting mode.
847 * This will add vertical or horizontal borders if necessary.
848 * Crop the source image data vertically or horizontally if necessary.
849 * The aspect of the source image is preserved.
850 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
851 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
852 * GPU scaling to be performed at render time giving the same result with less texture traversal.
854 * @param[in] bitmap The source pixel buffer to perform modifications on.
855 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
856 * @param[in] fittingMode The fitting mode to use.
858 * @return A new bitmap with the padding and cropping required for fitting mode applied.
859 * If no modification is needed or possible, the passed in bitmap is returned.
861 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
864 * @brief Adds horizontal or vertical borders to the source image.
866 * @param[in] targetPixels The destination image pointer to draw the borders on.
867 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
868 * @param[in] targetDimensions The dimensions of the destination image.
869 * @param[in] padDimensions The columns and scanlines to pad with borders.
871 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
873 Dali::Devel::PixelBuffer ApplyAttributesToBitmap( Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
877 // Calculate the desired box, accounting for a possible zero component:
878 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
880 // If a different size than the raw one has been requested, resize the image
881 // maximally using a repeated box filter without making it smaller than the
882 // requested size in either dimension:
883 bitmap = DownscaleBitmap( bitmap, desiredDimensions, fittingMode, samplingMode );
885 // Cut the bitmap according to the desired width and height so that the
886 // resulting bitmap has the same aspect ratio as the desired dimensions.
887 // Add crop and add borders if necessary depending on fitting mode.
890 bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
897 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
899 const unsigned int inputWidth = bitmap.GetWidth();
900 const unsigned int inputHeight = bitmap.GetHeight();
902 if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
904 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
906 else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
908 // Calculate any padding or cropping that needs to be done based on the fitting mode.
909 // Note: If the desired size is larger than the original image, the desired size will be
910 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
911 int scanlinesToCrop = 0;
912 int columnsToCrop = 0;
914 CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
916 unsigned int desiredWidth( desiredDimensions.GetWidth() );
917 unsigned int desiredHeight( desiredDimensions.GetHeight() );
919 // Action the changes by making a new bitmap with the central part of the loaded one if required.
920 if( scanlinesToCrop != 0 || columnsToCrop != 0 )
922 // Split the adding and removing of scanlines and columns into separate variables,
923 // so we can use one piece of generic code to action the changes.
924 unsigned int scanlinesToPad = 0;
925 unsigned int columnsToPad = 0;
926 if( scanlinesToCrop < 0 )
928 scanlinesToPad = -scanlinesToCrop;
931 if( columnsToCrop < 0 )
933 columnsToPad = -columnsToCrop;
937 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
938 if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
939 ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
941 DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
945 // Create new PixelBuffer with the desired size.
946 const auto pixelFormat = bitmap.GetPixelFormat();
948 auto croppedBitmap = Devel::PixelBuffer::New( desiredWidth, desiredHeight, pixelFormat );
950 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
951 // The cropping is added to the source pointer, and the padding is added to the destination.
952 const auto bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
953 const PixelBuffer * const sourcePixels = bitmap.GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
954 PixelBuffer * const targetPixels = croppedBitmap.GetBuffer();
955 PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
956 DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
958 // Copy the image data to the new bitmap.
959 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
960 unsigned int outputSpan( desiredWidth * bytesPerPixel );
961 if( columnsToCrop == 0 && columnsToPad == 0 )
963 memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
967 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
968 // Precalculate any constants to optimize the inner loop.
969 const unsigned int inputSpan( inputWidth * bytesPerPixel );
970 const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
971 const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
973 for( unsigned int y = 0; y < scanlinesToCopy; ++y )
975 memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
979 // Add vertical or horizontal borders to the final image (if required).
980 desiredDimensions.SetWidth( desiredWidth );
981 desiredDimensions.SetHeight( desiredHeight );
982 AddBorders( croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
983 // Overwrite the loaded bitmap with the cropped version
984 bitmap = croppedBitmap;
991 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
993 // Assign ints for faster access.
994 unsigned int desiredWidth( targetDimensions.GetWidth() );
995 unsigned int desiredHeight( targetDimensions.GetHeight() );
996 unsigned int columnsToPad( padDimensions.GetWidth() );
997 unsigned int scanlinesToPad( padDimensions.GetHeight() );
998 unsigned int outputSpan( desiredWidth * bytesPerPixel );
1000 // Add letterboxing (symmetrical borders) if needed.
1001 if( scanlinesToPad > 0 )
1003 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1004 memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
1006 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1007 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1008 unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
1011 memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
1013 else if( columnsToPad > 0 )
1015 // Add a left and right border.
1017 // Pre-calculate span size outside of loop.
1018 unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
1019 for( unsigned int y = 0; y < desiredHeight; ++y )
1021 memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
1025 // Pre-calculate the initial x offset as it is always the same for a small optimization.
1026 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1027 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1028 unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
1029 PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
1030 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1032 for( unsigned int y = 0; y < desiredHeight; ++y )
1034 memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
1039 Dali::Devel::PixelBuffer DownscaleBitmap( Dali::Devel::PixelBuffer bitmap,
1040 ImageDimensions desired,
1041 FittingMode::Type fittingMode,
1042 SamplingMode::Type samplingMode )
1044 // Source dimensions as loaded from resources (e.g. filesystem):
1045 auto bitmapWidth = bitmap.GetWidth();
1046 auto bitmapHeight = bitmap.GetHeight();
1047 // Desired dimensions (the rectangle to fit the source image to):
1048 auto desiredWidth = desired.GetWidth();
1049 auto desiredHeight = desired.GetHeight();
1051 Dali::Devel::PixelBuffer outputBitmap { bitmap };
1053 // If a different size than the raw one has been requested, resize the image:
1055 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1056 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
1058 auto pixelFormat = bitmap.GetPixelFormat();
1060 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1061 unsigned int shrunkWidth = -1, shrunkHeight = -1;
1062 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
1064 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1065 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
1066 const unsigned int filteredWidth = filteredDimensions.GetWidth();
1067 const unsigned int filteredHeight = filteredDimensions.GetHeight();
1069 // Run a filter to scale down the bitmap if it needs it:
1070 bool filtered = false;
1071 if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
1073 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1074 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
1076 outputBitmap = Dali::Devel::PixelBuffer::New( filteredWidth, filteredHeight, pixelFormat );
1080 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1082 LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions );
1086 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight );
1092 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1093 if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
1095 outputBitmap = MakePixelBuffer( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
1099 return outputBitmap;
1105 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1106 * @param test Which combination of the two dimensions matter for terminating the filtering.
1107 * @param scaledWidth The width of the current downscaled image.
1108 * @param scaledHeight The height of the current downscaled image.
1109 * @param desiredWidth The target width for the downscaling.
1110 * @param desiredHeight The target height for the downscaling.
1112 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
1114 bool keepScaling = false;
1115 const unsigned int nextWidth = scaledWidth >> 1u;
1116 const unsigned int nextHeight = scaledHeight >> 1u;
1118 if( nextWidth >= 1u && nextHeight >= 1u )
1122 case BoxDimensionTestEither:
1124 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1127 case BoxDimensionTestBoth:
1129 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1132 case BoxDimensionTestX:
1134 keepScaling = nextWidth >= desiredWidth;
1137 case BoxDimensionTestY:
1139 keepScaling = nextHeight >= desiredHeight;
1149 * @brief A shared implementation of the overall iterative box filter
1150 * downscaling algorithm.
1152 * Specialise this for particular pixel formats by supplying the number of bytes
1153 * per pixel and two functions: one for averaging pairs of neighbouring pixels
1154 * on a single scanline, and a second for averaging pixels at corresponding
1155 * positions on different scanlines.
1158 int BYTES_PER_PIXEL,
1159 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
1160 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
1162 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
1163 const unsigned int inputWidth,
1164 const unsigned int inputHeight,
1165 const unsigned int desiredWidth,
1166 const unsigned int desiredHeight,
1167 BoxDimensionTest dimensionTest,
1169 unsigned& outHeight )
1175 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
1177 // Scale the image until it would be smaller than desired, stopping if the
1178 // resulting height or width would be less than 1:
1179 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
1180 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
1182 const unsigned int lastWidth = scaledWidth;
1184 scaledHeight >>= 1u;
1186 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
1188 const unsigned int lastScanlinePair = scaledHeight - 1;
1190 // Scale pairs of scanlines until any spare one at the end is dropped:
1191 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
1193 // Scale two scanlines horizontally:
1194 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
1195 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
1197 // Scale vertical pairs of pixels while the last two scanlines are still warm in
1198 // the CPU cache(s):
1199 // Note, better access patterns for cache-coherence are possible for very large
1200 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1201 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1203 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
1204 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
1205 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1210 ///@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.
1211 outWidth = scaledWidth;
1212 outHeight = scaledHeight;
1217 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
1219 DebugAssertScanlineParameters( pixels, width );
1221 const unsigned int lastPair = EvenDown( width - 2 );
1223 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1225 // Load all the byte pixel components we need:
1226 const unsigned int c11 = pixels[pixel * 3];
1227 const unsigned int c12 = pixels[pixel * 3 + 1];
1228 const unsigned int c13 = pixels[pixel * 3 + 2];
1229 const unsigned int c21 = pixels[pixel * 3 + 3];
1230 const unsigned int c22 = pixels[pixel * 3 + 4];
1231 const unsigned int c23 = pixels[pixel * 3 + 5];
1233 // Save the averaged byte pixel components:
1234 pixels[outPixel * 3] = AverageComponent( c11, c21 );
1235 pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
1236 pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
1240 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
1242 DebugAssertScanlineParameters( pixels, width );
1243 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1245 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1247 const unsigned int lastPair = EvenDown( width - 2 );
1249 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1251 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
1252 alignedPixels[outPixel] = averaged;
1256 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
1258 DebugAssertScanlineParameters( pixels, width );
1259 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1261 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1263 const unsigned int lastPair = EvenDown( width - 2 );
1265 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1267 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
1268 alignedPixels[outPixel] = averaged;
1272 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
1274 DebugAssertScanlineParameters( pixels, width );
1276 const unsigned int lastPair = EvenDown( width - 2 );
1278 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1280 // Load all the byte pixel components we need:
1281 const unsigned int c11 = pixels[pixel * 2];
1282 const unsigned int c12 = pixels[pixel * 2 + 1];
1283 const unsigned int c21 = pixels[pixel * 2 + 2];
1284 const unsigned int c22 = pixels[pixel * 2 + 3];
1286 // Save the averaged byte pixel components:
1287 pixels[outPixel * 2] = AverageComponent( c11, c21 );
1288 pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
1292 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
1294 DebugAssertScanlineParameters( pixels, width );
1296 const unsigned int lastPair = EvenDown( width - 2 );
1298 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1300 // Load all the byte pixel components we need:
1301 const unsigned int c1 = pixels[pixel];
1302 const unsigned int c2 = pixels[pixel + 1];
1304 // Save the averaged byte pixel component:
1305 pixels[outPixel] = AverageComponent( c1, c2 );
1310 * @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.
1311 * 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.
1313 void AverageScanlines1( const unsigned char * const scanline1,
1314 const unsigned char * const __restrict__ scanline2,
1315 unsigned char* const outputScanline,
1316 const unsigned int width )
1318 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
1320 for( unsigned int component = 0; component < width; ++component )
1322 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1326 void AverageScanlines2( const unsigned char * const scanline1,
1327 const unsigned char * const __restrict__ scanline2,
1328 unsigned char* const outputScanline,
1329 const unsigned int width )
1331 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1333 for( unsigned int component = 0; component < width * 2; ++component )
1335 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1339 void AverageScanlines3( const unsigned char * const scanline1,
1340 const unsigned char * const __restrict__ scanline2,
1341 unsigned char* const outputScanline,
1342 const unsigned int width )
1344 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
1346 for( unsigned int component = 0; component < width * 3; ++component )
1348 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1352 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
1353 const unsigned char * const __restrict__ scanline2,
1354 unsigned char * const outputScanline,
1355 const unsigned int width )
1357 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
1358 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1359 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1360 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1362 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1363 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1364 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1366 for( unsigned int pixel = 0; pixel < width; ++pixel )
1368 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1372 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1373 const unsigned char * const __restrict__ scanline2,
1374 unsigned char * const outputScanline,
1375 const unsigned int width )
1377 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1378 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1379 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1380 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1382 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1383 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1384 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1386 for( unsigned int pixel = 0; pixel < width; ++pixel )
1388 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1392 /// Dispatch to pixel format appropriate box filter downscaling functions.
1393 void DownscaleInPlacePow2( unsigned char * const pixels,
1394 Pixel::Format pixelFormat,
1395 unsigned int inputWidth,
1396 unsigned int inputHeight,
1397 unsigned int desiredWidth,
1398 unsigned int desiredHeight,
1399 FittingMode::Type fittingMode,
1400 SamplingMode::Type samplingMode,
1402 unsigned& outHeight )
1404 outWidth = inputWidth;
1405 outHeight = inputHeight;
1406 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1407 if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1409 // Check the pixel format is one that is supported:
1410 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1412 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1414 if( pixelFormat == Pixel::RGBA8888 )
1416 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1418 else if( pixelFormat == Pixel::RGB888 )
1420 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1422 else if( pixelFormat == Pixel::RGB565 )
1424 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1426 else if( pixelFormat == Pixel::LA88 )
1428 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1430 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1432 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1436 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1442 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1446 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1447 unsigned int inputWidth,
1448 unsigned int inputHeight,
1449 unsigned int desiredWidth,
1450 unsigned int desiredHeight,
1451 BoxDimensionTest dimensionTest,
1453 unsigned& outHeight )
1455 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1458 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1459 unsigned int inputWidth,
1460 unsigned int inputHeight,
1461 unsigned int desiredWidth,
1462 unsigned int desiredHeight,
1463 BoxDimensionTest dimensionTest,
1465 unsigned& outHeight )
1467 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1468 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1471 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1472 unsigned int inputWidth,
1473 unsigned int inputHeight,
1474 unsigned int desiredWidth,
1475 unsigned int desiredHeight,
1476 BoxDimensionTest dimensionTest,
1477 unsigned int& outWidth,
1478 unsigned int& outHeight )
1480 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1484 * @copydoc DownscaleInPlacePow2RGB888
1486 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1488 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1489 unsigned int inputWidth,
1490 unsigned int inputHeight,
1491 unsigned int desiredWidth,
1492 unsigned int desiredHeight,
1493 BoxDimensionTest dimensionTest,
1495 unsigned& outHeight )
1497 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1500 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1501 unsigned int inputWidth,
1502 unsigned int inputHeight,
1503 unsigned int desiredWidth,
1504 unsigned int desiredHeight,
1505 BoxDimensionTest dimensionTest,
1506 unsigned int& outWidth,
1507 unsigned int& outHeight )
1509 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1516 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1518 * Template is used purely as a type-safe code generator in this one
1519 * compilation unit. Generated code is inlined into type-specific wrapper
1520 * functions below which are exported to rest of module.
1522 template<typename PIXEL>
1523 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1524 unsigned int inputWidth,
1525 unsigned int inputHeight,
1526 uint8_t * outPixels,
1527 unsigned int desiredWidth,
1528 unsigned int desiredHeight )
1530 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1531 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1532 "The input and output buffers must not overlap for an upscaling.");
1533 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, ...)." );
1534 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, ...)." );
1536 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1540 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1541 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1542 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1543 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1545 unsigned int inY = 0;
1546 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1548 // Round fixed point y coordinate to nearest integer:
1549 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1550 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1551 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1553 DALI_ASSERT_DEBUG( integerY < inputHeight );
1554 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1555 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1557 unsigned int inX = 0;
1558 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1560 // Round the fixed-point x coordinate to an integer:
1561 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1562 const PIXEL* const inPixelAddress = &inScanline[integerX];
1563 const PIXEL pixel = *inPixelAddress;
1564 outScanline[outX] = pixel;
1574 void PointSample4BPP( const unsigned char * inPixels,
1575 unsigned int inputWidth,
1576 unsigned int inputHeight,
1577 unsigned char * outPixels,
1578 unsigned int desiredWidth,
1579 unsigned int desiredHeight )
1581 PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1585 void PointSample2BPP( const unsigned char * inPixels,
1586 unsigned int inputWidth,
1587 unsigned int inputHeight,
1588 unsigned char * outPixels,
1589 unsigned int desiredWidth,
1590 unsigned int desiredHeight )
1592 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1596 void PointSample1BPP( const unsigned char * inPixels,
1597 unsigned int inputWidth,
1598 unsigned int inputHeight,
1599 unsigned char * outPixels,
1600 unsigned int desiredWidth,
1601 unsigned int desiredHeight )
1603 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1607 * RGB888 is a special case as its pixels are not aligned addressable units.
1609 void PointSample3BPP( const uint8_t * inPixels,
1610 unsigned int inputWidth,
1611 unsigned int inputHeight,
1612 uint8_t * outPixels,
1613 unsigned int desiredWidth,
1614 unsigned int desiredHeight )
1616 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1620 const unsigned int BYTES_PER_PIXEL = 3;
1622 // Generate fixed-point 16.16 deltas in input image coordinates:
1623 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1624 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1626 // Step through output image in whole integer pixel steps while tracking the
1627 // corresponding locations in the input image using 16.16 fixed-point
1629 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1630 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1632 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1633 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1634 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1635 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1637 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1639 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1640 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1641 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1643 // Issue loads for all pixel color components up-front:
1644 const unsigned int c0 = inPixelAddress[0];
1645 const unsigned int c1 = inPixelAddress[1];
1646 const unsigned int c2 = inPixelAddress[2];
1647 ///@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.
1649 // Output the pixel components:
1650 outScanline[outX] = c0;
1651 outScanline[outX + 1] = c1;
1652 outScanline[outX + 2] = c2;
1654 // Increment the fixed-point input coordinate:
1662 // Dispatch to a format-appropriate point sampling function:
1663 void PointSample( const unsigned char * inPixels,
1664 unsigned int inputWidth,
1665 unsigned int inputHeight,
1666 Pixel::Format pixelFormat,
1667 unsigned char * outPixels,
1668 unsigned int desiredWidth,
1669 unsigned int desiredHeight )
1671 // Check the pixel format is one that is supported:
1672 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1674 if( pixelFormat == Pixel::RGB888 )
1676 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1678 else if( pixelFormat == Pixel::RGBA8888 )
1680 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1682 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1684 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1686 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1688 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1692 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1697 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1701 // Linear sampling group below
1706 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1707 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1709 return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1712 /** @copydoc BilinearFilter1BPPByte */
1713 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1716 pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1717 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1721 /** @copydoc BilinearFilter1BPPByte */
1722 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1725 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1726 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1727 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1731 /** @copydoc BilinearFilter1BPPByte */
1732 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1734 const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1735 (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1736 BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1740 /** @copydoc BilinearFilter1BPPByte */
1741 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1744 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1745 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1746 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1747 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1752 * @brief Generic version of bilinear sampling image resize function.
1753 * @note Limited to one compilation unit and exposed through type-specific
1754 * wrapper functions below.
1758 PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1759 bool DEBUG_ASSERT_ALIGNMENT
1761 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1762 ImageDimensions inputDimensions,
1763 unsigned char * __restrict__ outPixels,
1764 ImageDimensions desiredDimensions )
1766 const unsigned int inputWidth = inputDimensions.GetWidth();
1767 const unsigned int inputHeight = inputDimensions.GetHeight();
1768 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1769 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1771 DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1772 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1773 "Input and output buffers cannot overlap.");
1774 if( DEBUG_ASSERT_ALIGNMENT )
1776 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, ...)." );
1777 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, ...)." );
1780 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1784 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1785 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1786 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1787 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1789 unsigned int inY = 0;
1790 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1792 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1794 // Find the two scanlines to blend and the weight to blend with:
1795 const unsigned int integerY1 = inY >> 16u;
1796 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1797 const unsigned int inputYWeight = inY & 65535u;
1799 DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1800 DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1802 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1803 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1805 unsigned int inX = 0;
1806 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1808 // Work out the two pixel scanline offsets for this cluster of four samples:
1809 const unsigned int integerX1 = inX >> 16u;
1810 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1812 // Execute the loads:
1813 const PIXEL pixel1 = inScanline1[integerX1];
1814 const PIXEL pixel2 = inScanline2[integerX1];
1815 const PIXEL pixel3 = inScanline1[integerX2];
1816 const PIXEL pixel4 = inScanline2[integerX2];
1817 ///@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.
1819 // Weighted bilinear filter:
1820 const unsigned int inputXWeight = inX & 65535u;
1821 outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1831 // Format-specific linear scaling instantiations:
1833 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1834 ImageDimensions inputDimensions,
1835 unsigned char * __restrict__ outPixels,
1836 ImageDimensions desiredDimensions )
1838 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1841 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1842 ImageDimensions inputDimensions,
1843 unsigned char * __restrict__ outPixels,
1844 ImageDimensions desiredDimensions )
1846 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1849 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1850 ImageDimensions inputDimensions,
1851 unsigned char * __restrict__ outPixels,
1852 ImageDimensions desiredDimensions )
1854 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1857 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1858 ImageDimensions inputDimensions,
1859 unsigned char * __restrict__ outPixels,
1860 ImageDimensions desiredDimensions )
1862 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1865 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1866 ImageDimensions inputDimensions,
1867 unsigned char * __restrict__ outPixels,
1868 ImageDimensions desiredDimensions )
1870 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1874 void Resample( const unsigned char * __restrict__ inPixels,
1875 ImageDimensions inputDimensions,
1876 unsigned char * __restrict__ outPixels,
1877 ImageDimensions desiredDimensions,
1878 Resampler::Filter filterType,
1879 int numChannels, bool hasAlpha )
1881 // Got from the test.cpp of the ImageResampler lib.
1882 const float ONE_DIV_255 = 1.0f / 255.0f;
1883 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1884 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1885 const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
1887 static bool loadColorSpaces = true;
1888 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1889 static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1891 if( loadColorSpaces ) // Only create the color space conversions on the first execution
1893 loadColorSpaces = false;
1895 for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
1897 srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
1900 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
1901 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1903 for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
1905 int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
1910 else if( k > MAX_UNSIGNED_CHAR )
1912 k = MAX_UNSIGNED_CHAR;
1914 linearToSrgb[i] = static_cast<unsigned char>( k );
1918 std::vector<Resampler*> resamplers( numChannels );
1919 std::vector<Vector<float>> samples(numChannels);
1921 const int srcWidth = inputDimensions.GetWidth();
1922 const int srcHeight = inputDimensions.GetHeight();
1923 const int dstWidth = desiredDimensions.GetWidth();
1924 const int dstHeight = desiredDimensions.GetHeight();
1926 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1927 // used for the other components (a memory and slight cache efficiency optimization).
1928 resamplers[0] = new Resampler( srcWidth,
1932 Resampler::BOUNDARY_CLAMP,
1933 0.0f, // sample_low,
1934 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1935 filterType, // The type of filter.
1937 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1938 FILTER_SCALE, // src_x_ofs,
1939 FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1940 samples[0].Resize( srcWidth );
1941 for( int i = 1; i < numChannels; ++i )
1943 resamplers[i] = new Resampler( srcWidth,
1947 Resampler::BOUNDARY_CLAMP,
1951 resamplers[0]->get_clist_x(),
1952 resamplers[0]->get_clist_y(),
1955 samples[i].Resize( srcWidth );
1958 const int srcPitch = srcWidth * numChannels;
1959 const int dstPitch = dstWidth * numChannels;
1962 for( int srcY = 0; srcY < srcHeight; ++srcY )
1964 const unsigned char* pSrc = &inPixels[srcY * srcPitch];
1966 for( int x = 0; x < srcWidth; ++x )
1968 for( int c = 0; c < numChannels; ++c )
1970 if( c == ALPHA_CHANNEL && hasAlpha )
1972 samples[c][x] = *pSrc++ * ONE_DIV_255;
1976 samples[c][x] = srgbToLinear[*pSrc++];
1981 for( int c = 0; c < numChannels; ++c )
1983 if( !resamplers[c]->put_line( &samples[c][0] ) )
1985 DALI_ASSERT_DEBUG( !"Out of memory" );
1992 for( compIndex = 0; compIndex < numChannels; ++compIndex )
1994 const float* pOutputSamples = resamplers[compIndex]->get_line();
1995 if( !pOutputSamples )
2000 const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
2001 DALI_ASSERT_DEBUG( dstY < dstHeight );
2002 unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
2004 for( int x = 0; x < dstWidth; ++x )
2006 if( isAlphaChannel )
2008 int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
2013 else if( c > MAX_UNSIGNED_CHAR )
2015 c = MAX_UNSIGNED_CHAR;
2017 *pDst = static_cast<unsigned char>( c );
2021 int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
2026 else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
2028 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2030 *pDst = linearToSrgb[j];
2033 pDst += numChannels;
2036 if( compIndex < numChannels )
2045 // Delete the resamplers.
2046 for( int i = 0; i < numChannels; ++i )
2048 delete resamplers[i];
2052 void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
2053 ImageDimensions inputDimensions,
2054 unsigned char * __restrict__ outPixels,
2055 ImageDimensions desiredDimensions )
2057 Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true );
2060 void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
2061 ImageDimensions inputDimensions,
2062 unsigned char * __restrict__ outPixels,
2063 ImageDimensions desiredDimensions )
2066 Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false );
2069 // Dispatch to a format-appropriate linear sampling function:
2070 void LinearSample( const unsigned char * __restrict__ inPixels,
2071 ImageDimensions inDimensions,
2072 Pixel::Format pixelFormat,
2073 unsigned char * __restrict__ outPixels,
2074 ImageDimensions outDimensions )
2076 // Check the pixel format is one that is supported:
2077 if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
2079 if( pixelFormat == Pixel::RGB888 )
2081 LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
2083 else if( pixelFormat == Pixel::RGBA8888 )
2085 LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
2087 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
2089 LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
2091 else if( pixelFormat == Pixel::LA88 )
2093 LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
2095 else if ( pixelFormat == Pixel::RGB565 )
2097 LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
2101 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
2106 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
2110 void RotateByShear( const uint8_t* const pixelsIn,
2111 unsigned int widthIn,
2112 unsigned int heightIn,
2113 unsigned int pixelSize,
2115 uint8_t*& pixelsOut,
2116 unsigned int& widthOut,
2117 unsigned int& heightOut )
2119 // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2121 // Do first the fast rotations to transform the angle into a (-45..45] range.
2123 float fastRotationPerformed = false;
2124 if( ( radians > Math::PI_4 ) && ( radians <= RAD_135 ) )
2126 // Angle in (45.0 .. 135.0]
2127 // Rotate image by 90 degrees into temporary image,
2128 // so it requires only an extra rotation angle
2129 // of -45.0 .. +45.0 to complete rotation.
2137 radians -= Math::PI_2;
2138 fastRotationPerformed = true;
2140 else if( ( radians > RAD_135 ) && ( radians <= RAD_225 ) )
2142 // Angle in (135.0 .. 225.0]
2143 // Rotate image by 180 degrees into temporary image,
2144 // so it requires only an extra rotation angle
2145 // of -45.0 .. +45.0 to complete rotation.
2147 Rotate180( pixelsIn,
2152 radians -= Math::PI;
2154 heightOut = heightIn;
2155 fastRotationPerformed = true;
2157 else if( ( radians > RAD_225 ) && ( radians <= RAD_315 ) )
2159 // Angle in (225.0 .. 315.0]
2160 // Rotate image by 270 degrees into temporary image,
2161 // so it requires only an extra rotation angle
2162 // of -45.0 .. +45.0 to complete rotation.
2164 Rotate270( pixelsIn,
2172 fastRotationPerformed = true;
2175 if( fabs( radians ) < Dali::Math::MACHINE_EPSILON_10 )
2177 // Nothing else to do if the angle is zero.
2178 // The rotation angle was 90, 180 or 270.
2180 // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2184 const uint8_t* const firstHorizontalSkwePixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2185 uint8_t* tmpFirstHorizontalSkwePixelsIn = fastRotationPerformed ? pixelsOut : nullptr; // keep the pointer to free the memory.
2187 // Reset the input/output
2189 heightIn = heightOut;
2190 pixelsOut = nullptr;
2192 const float angleSinus = sin( radians );
2193 const float angleCosinus = cos( radians );
2194 const float angleTangent = tan( 0.5f * radians );
2196 ///////////////////////////////////////
2197 // Perform 1st shear (horizontal)
2198 ///////////////////////////////////////
2200 // Calculate first shear (horizontal) destination image dimensions
2202 widthOut = widthIn + static_cast<unsigned int>( fabs( angleTangent ) * static_cast<float>( heightIn ) );
2203 heightOut = heightIn;
2205 // Allocate the buffer for the 1st shear
2206 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2208 for( unsigned int y = 0u; y < heightOut; ++y )
2210 const float shear = angleTangent * ( ( angleTangent >= 0.f ) ? ( 0.5f + static_cast<float>( y ) ) : ( 0.5f + static_cast<float>( y ) - static_cast<float>( heightOut ) ) );
2212 const int intShear = static_cast<int>( floor( shear ) );
2213 HorizontalSkew( firstHorizontalSkwePixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>( intShear ) );
2216 // Free the memory allocated by the 'Fast Rotations'.
2217 free( tmpFirstHorizontalSkwePixelsIn );
2219 uint8_t* tmpPixelsIn = pixelsOut;
2220 unsigned int tmpWidthIn = widthOut;
2221 unsigned int tmpHeightIn = heightOut;
2223 // Reset the input/output
2224 pixelsOut = nullptr;
2226 ///////////////////////////////////////
2227 // Perform 2nd shear (vertical)
2228 ///////////////////////////////////////
2230 // Calc 2nd shear (vertical) destination image dimensions
2231 heightOut = static_cast<unsigned int>( static_cast<float>( widthIn ) * fabs( angleSinus ) + static_cast<float>( heightIn ) * angleCosinus );
2233 // Allocate the buffer for the 2nd shear
2234 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2236 // Variable skew offset
2237 float offset = angleSinus * ( ( angleSinus > 0.f ) ? static_cast<float>( widthIn - 1u ) : -( static_cast<float>( widthIn ) - static_cast<float>( widthOut ) ) );
2239 unsigned int column = 0u;
2240 for( column = 0u; column < widthOut; ++column, offset -= angleSinus )
2242 const int shear = static_cast<int>( floor( offset ) );
2243 VerticalSkew( tmpPixelsIn, tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>( shear ) );
2246 // Free the memory allocated by the 'First Horizontal Skew'.
2247 free( tmpPixelsIn );
2249 // Reset the input/output
2250 tmpPixelsIn = pixelsOut;
2251 tmpWidthIn = widthOut;
2252 tmpHeightIn = heightOut;
2253 pixelsOut = nullptr;
2255 ///////////////////////////////////////
2256 // Perform 3rd shear (horizontal)
2257 ///////////////////////////////////////
2259 // Calc 3rd shear (horizontal) destination image dimensions
2260 widthOut = static_cast<unsigned int>( static_cast<float>( heightIn ) * fabs( angleSinus ) + static_cast<float>( widthIn ) * angleCosinus ) + 1u;
2262 // Allocate the buffer for the 3rd shear
2263 pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2265 offset = ( angleSinus >= 0.f ) ? -angleSinus * angleTangent * static_cast<float>( widthIn - 1u ) : angleTangent * ( static_cast<float>( widthIn - 1u ) * -angleSinus + ( 1.f - static_cast<float>( heightOut ) ) );
2267 for( unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent )
2269 const int shear = static_cast<int>( floor( offset ) );
2270 HorizontalSkew( tmpPixelsIn, tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>( shear ) );
2273 // Free the memory allocated by the 'First Horizontal Skew'.
2274 free( tmpPixelsIn );
2276 // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the called to this function.
2279 } /* namespace Platform */
2280 } /* namespace Internal */
2281 } /* namespace Dali */