2 * Copyright (c) 2015 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 "image-operations.h"
24 #include <dali/integration-api/debug.h>
25 #include <dali/public-api/math/vector2.h>
39 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
40 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
41 // We can optionally use a Vector4 color here, but at reduced fill speed.
42 const uint8_t BORDER_FILL_VALUE( 0x00 );
43 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
44 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
46 using Integration::Bitmap;
47 using Integration::BitmapPtr;
48 typedef unsigned char PixelBuffer;
51 * @brief 4 byte pixel structure.
59 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
62 * @brief RGB888 pixel structure.
69 } __attribute__((packed, aligned(1)));
72 * @brief RGB565 pixel typedefed from a short.
74 * Access fields by manual shifting and masking.
76 typedef uint16_t PixelRGB565;
79 * @brief a Pixel composed of two independent byte components.
85 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
88 #if defined(DEBUG_ENABLED)
90 * Disable logging of image operations or make it verbose from the commandline
91 * as follows (e.g., for dali demo app):
93 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
94 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
97 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
100 /** @return The greatest even number less than or equal to the argument. */
101 inline unsigned int EvenDown( const unsigned int a )
103 const unsigned int evened = a & ~1u;
108 * @brief Log bad parameters.
110 void ValidateScalingParameters( const unsigned int inputWidth,
111 const unsigned int inputHeight,
112 const unsigned int desiredWidth,
113 const unsigned int desiredHeight )
115 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
117 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
120 if( desiredWidth == 0u || desiredHeight == 0u )
122 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
125 if( inputWidth == 0u || inputHeight == 0u )
127 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
132 * @brief Do debug assertions common to all scanline halving functions.
133 * @note Inline and in anon namespace so should boil away in release builds.
135 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
137 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
138 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
139 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
143 * @brief Assertions on params to functions averaging pairs of scanlines.
144 * @note Inline as intended to boil away in release.
146 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
147 const uint8_t * const scanline2,
148 uint8_t* const outputScanline,
149 const size_t widthInComponents )
151 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
152 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
153 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
154 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
155 DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
159 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
161 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
163 BoxDimensionTest dimensionTest;
164 dimensionTest = BoxDimensionTestEither;
166 switch( fittingMode )
168 // Shrink to fit attempts to make one or zero dimensions smaller than the
169 // desired dimensions and one or two dimensions exactly the same as the desired
170 // ones, so as long as one dimension is larger than the desired size, box
171 // filtering can continue even if the second dimension is smaller than the
172 // desired dimensions:
173 case FittingMode::SHRINK_TO_FIT:
175 dimensionTest = BoxDimensionTestEither;
178 // Scale to fill mode keeps both dimensions at least as large as desired:
179 case FittingMode::SCALE_TO_FILL:
181 dimensionTest = BoxDimensionTestBoth;
184 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
185 case FittingMode::FIT_WIDTH:
187 dimensionTest = BoxDimensionTestX;
190 // X Dimension is ignored by definition in FIT_HEIGHT mode:
191 case FittingMode::FIT_HEIGHT:
193 dimensionTest = BoxDimensionTestY;
198 return dimensionTest;
202 * @brief Work out the dimensions for a uniform scaling of the input to map it
203 * into the target while effecting ShinkToFit scaling mode.
205 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
207 // Scale the input by the least extreme of the two dimensions:
208 const float widthScale = target.GetX() / float(source.GetX());
209 const float heightScale = target.GetY() / float(source.GetY());
210 const float scale = widthScale < heightScale ? widthScale : heightScale;
212 // Do no scaling at all if the result would increase area:
218 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
222 * @brief Work out the dimensions for a uniform scaling of the input to map it
223 * into the target while effecting SCALE_TO_FILL scaling mode.
224 * @note An image scaled into the output dimensions will need either top and
225 * bottom or left and right to be cropped away unless the source was pre-cropped
226 * to match the destination aspect ratio.
228 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
230 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
231 // Scale the input by the least extreme of the two dimensions:
232 const float widthScale = target.GetX() / float(source.GetX());
233 const float heightScale = target.GetY() / float(source.GetY());
234 const float scale = widthScale > heightScale ? widthScale : heightScale;
236 // Do no scaling at all if the result would increase area:
242 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
246 * @brief Work out the dimensions for a uniform scaling of the input to map it
247 * into the target while effecting FIT_WIDTH scaling mode.
249 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
251 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
252 const float scale = target.GetX() / float(source.GetX());
254 // Do no scaling at all if the result would increase area:
259 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
263 * @brief Work out the dimensions for a uniform scaling of the input to map it
264 * into the target while effecting FIT_HEIGHT scaling mode.
266 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
268 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
269 const float scale = target.GetY() / float(source.GetY());
271 // Do no scaling at all if the result would increase area:
277 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
281 * @brief Generate the rectangle to use as the target of a pixel sampling pass
282 * (e.g., nearest or linear).
284 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
286 ImageDimensions fitDimensions;
287 switch( fittingMode )
289 case FittingMode::SHRINK_TO_FIT:
291 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
294 case FittingMode::SCALE_TO_FILL:
296 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
299 case FittingMode::FIT_WIDTH:
301 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
304 case FittingMode::FIT_HEIGHT:
306 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
311 return fitDimensions;
315 * @brief Calculate the number of lines on the X and Y axis that need to be
316 * either added or removed with repect to the specified fitting mode.
317 * (e.g., nearest or linear).
318 * @param[in] sourceSize The size of the source image
319 * @param[in] fittingMode The fitting mode to use
320 * @param[in/out] requestedSize The target size that the image will be fitted to.
321 * If the source image is smaller than the requested size, the source is not scaled up.
322 * So we reduce the target size while keeping aspect by lowering resolution.
323 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
324 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
326 void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
328 const unsigned int sourceWidth( sourceSize.GetWidth() );
329 const unsigned int sourceHeight( sourceSize.GetHeight() );
330 const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
334 switch( fittingMode )
336 case FittingMode::FIT_WIDTH:
338 finalWidth = sourceWidth;
339 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
342 scanlinesToCrop = -( finalHeight - sourceHeight );
346 case FittingMode::FIT_HEIGHT:
348 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
349 finalHeight = sourceHeight;
351 columnsToCrop = -( finalWidth - sourceWidth );
356 case FittingMode::SHRINK_TO_FIT:
358 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
359 if( sourceAspect > targetAspect )
361 finalWidth = sourceWidth;
362 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
365 scanlinesToCrop = -( finalHeight - sourceHeight );
369 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
370 finalHeight = sourceHeight;
372 columnsToCrop = -( finalWidth - sourceWidth );
378 case FittingMode::SCALE_TO_FILL:
380 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
381 if( sourceAspect > targetAspect )
383 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
384 finalHeight = sourceHeight;
386 columnsToCrop = -( finalWidth - sourceWidth );
391 finalWidth = sourceWidth;
392 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
395 scanlinesToCrop = -( finalHeight - sourceHeight );
401 requestedSize.SetWidth( finalWidth );
402 requestedSize.SetHeight( finalHeight );
406 * @brief Construct a bitmap with format and dimensions requested.
408 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
410 DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
412 // Allocate a pixel buffer to hold the image passed in:
413 Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
414 newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
419 * @brief Construct a bitmap object from a copy of the pixel array passed in.
421 BitmapPtr MakeBitmap( 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 Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
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 // If no dimensions have been requested, default to the source ones:
445 if( requestedWidth == 0 && requestedHeight == 0 )
447 return ImageDimensions( bitmapWidth, bitmapHeight );
450 // If both dimensions have values requested, use them both:
451 if( requestedWidth != 0 && requestedHeight != 0 )
453 return ImageDimensions( requestedWidth, requestedHeight );
456 // Only one of the dimensions has been requested. Calculate the other from
457 // the requested one and the source image aspect ratio:
458 if( requestedWidth != 0 )
460 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
462 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
465 } // namespace - unnamed
467 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
469 return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
473 * @brief Apply cropping and padding for specified fitting mode.
475 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
476 * based on the fitting mode.
478 * This will add vertical or horizontal borders if necessary.
479 * Crop the source image data vertically or horizontally if necessary.
480 * The aspect of the source image is preserved.
481 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
482 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
483 * GPU scaling to be performed at render time giving the same result with less texture traversal.
485 * @param[in] bitmap The source bitmap to perform modifications on.
486 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
487 * @param[in] fittingMode The fitting mode to use.
489 * @return A new bitmap with the padding and cropping required for fitting mode applied.
490 * If no modification is needed or possible, the passed in bitmap is returned.
492 Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
495 * @brief Adds horizontal or vertical borders to the source image.
497 * @param[in] targetPixels The destination image pointer to draw the borders on.
498 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
499 * @param[in] targetDimensions The dimensions of the destination image.
500 * @param[in] padDimensions The columns and scanlines to pad with borders.
502 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
504 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
508 // Calculate the desired box, accounting for a possible zero component:
509 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
511 // If a different size than the raw one has been requested, resize the image
512 // maximally using a repeated box filter without making it smaller than the
513 // requested size in either dimension:
514 bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode );
516 // Cut the bitmap according to the desired width and height so that the
517 // resulting bitmap has the same aspect ratio as the desired dimensions.
518 // Add crop and add borders if necessary depending on fitting mode.
519 if( bitmap && bitmap->GetPackedPixelsProfile() )
521 bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
524 // Examine the image pixels remaining after cropping and scaling to see if all
525 // are opaque, allowing faster rendering, or some have non-1.0 alpha:
526 if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
528 bitmap->GetPackedPixelsProfile()->TestForTransparency();
535 BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
537 const unsigned int inputWidth = bitmap->GetImageWidth();
538 const unsigned int inputHeight = bitmap->GetImageHeight();
540 if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
542 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
544 else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
546 // Calculate any padding or cropping that needs to be done based on the fitting mode.
547 // Note: If the desired size is larger than the original image, the desired size will be
548 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
549 int scanlinesToCrop = 0;
550 int columnsToCrop = 0;
552 CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
554 unsigned int desiredWidth( desiredDimensions.GetWidth() );
555 unsigned int desiredHeight( desiredDimensions.GetHeight() );
557 // Action the changes by making a new bitmap with the central part of the loaded one if required.
558 if( scanlinesToCrop != 0 || columnsToCrop != 0 )
560 // Split the adding and removing of scanlines and columns into separate variables,
561 // so we can use one piece of generic code to action the changes.
562 unsigned int scanlinesToPad = 0;
563 unsigned int columnsToPad = 0;
564 if( scanlinesToCrop < 0 )
566 scanlinesToPad = -scanlinesToCrop;
569 if( columnsToCrop < 0 )
571 columnsToPad = -columnsToCrop;
575 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
576 if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
577 ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
579 DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
583 // Create a new bitmap with the desired size.
584 BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
585 Integration::Bitmap::PackedPixelsProfile *packedView = croppedBitmap->GetPackedPixelsProfile();
586 DALI_ASSERT_DEBUG( packedView );
587 const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
588 packedView->ReserveBuffer( pixelFormat, desiredWidth, desiredHeight, desiredWidth, desiredHeight );
590 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
591 // The cropping is added to the source pointer, and the padding is added to the destination.
592 const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
593 const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
594 PixelBuffer * const targetPixels = croppedBitmap->GetBuffer();
595 PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
596 DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
598 // Copy the image data to the new bitmap.
599 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
600 unsigned int outputSpan( desiredWidth * bytesPerPixel );
601 if( columnsToCrop == 0 && columnsToPad == 0 )
603 memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
607 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
608 // Precalculate any constants to optimize the inner loop.
609 const unsigned int inputSpan( inputWidth * bytesPerPixel );
610 const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
611 const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
613 for( unsigned int y = 0; y < scanlinesToCopy; ++y )
615 memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
619 // Add vertical or horizontal borders to the final image (if required).
620 desiredDimensions.SetWidth( desiredWidth );
621 desiredDimensions.SetHeight( desiredHeight );
622 AddBorders( croppedBitmap->GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
623 // Overwrite the loaded bitmap with the cropped version
624 bitmap = croppedBitmap;
631 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
633 // Assign ints for faster access.
634 unsigned int desiredWidth( targetDimensions.GetWidth() );
635 unsigned int desiredHeight( targetDimensions.GetHeight() );
636 unsigned int columnsToPad( padDimensions.GetWidth() );
637 unsigned int scanlinesToPad( padDimensions.GetHeight() );
638 unsigned int outputSpan( desiredWidth * bytesPerPixel );
640 // Add letterboxing (symmetrical borders) if needed.
641 if( scanlinesToPad > 0 )
643 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
644 memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
646 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
647 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
648 unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
651 memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
653 else if( columnsToPad > 0 )
655 // Add a left and right border.
657 // Pre-calculate span size outside of loop.
658 unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
659 for( unsigned int y = 0; y < desiredHeight; ++y )
661 memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
665 // Pre-calculate the initial x offset as it is always the same for a small optimization.
666 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
667 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
668 unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
669 PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
670 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
672 for( unsigned int y = 0; y < desiredHeight; ++y )
674 memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
679 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
680 ImageDimensions desired,
681 FittingMode::Type fittingMode,
682 SamplingMode::Type samplingMode )
684 // Source dimensions as loaded from resources (e.g. filesystem):
685 const unsigned int bitmapWidth = bitmap.GetImageWidth();
686 const unsigned int bitmapHeight = bitmap.GetImageHeight();
687 // Desired dimensions (the rectangle to fit the source image to):
688 const unsigned int desiredWidth = desired.GetWidth();
689 const unsigned int desiredHeight = desired.GetHeight();
691 BitmapPtr outputBitmap( &bitmap );
693 // If a different size than the raw one has been requested, resize the image:
694 if( bitmap.GetPackedPixelsProfile() &&
695 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
696 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
698 const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
700 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
701 unsigned int shrunkWidth = -1, shrunkHeight = -1;
702 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
704 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
705 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
706 const unsigned int filteredWidth = filteredDimensions.GetWidth();
707 const unsigned int filteredHeight = filteredDimensions.GetHeight();
709 // Run a filter to scale down the bitmap if it needs it:
710 bool filtered = false;
711 if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
713 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
714 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
716 outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
719 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
721 LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
725 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
731 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
732 if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
734 outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
744 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
745 * @param test Which combination of the two dimensions matter for terminating the filtering.
746 * @param scaledWidth The width of the current downscaled image.
747 * @param scaledHeight The height of the current downscaled image.
748 * @param desiredWidth The target width for the downscaling.
749 * @param desiredHeight The target height for the downscaling.
751 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
753 bool keepScaling = false;
754 const unsigned int nextWidth = scaledWidth >> 1u;
755 const unsigned int nextHeight = scaledHeight >> 1u;
757 if( nextWidth >= 1u && nextHeight >= 1u )
761 case BoxDimensionTestEither:
763 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
766 case BoxDimensionTestBoth:
768 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
771 case BoxDimensionTestX:
773 keepScaling = nextWidth >= desiredWidth;
776 case BoxDimensionTestY:
778 keepScaling = nextHeight >= desiredHeight;
788 * @brief A shared implementation of the overall iterative box filter
789 * downscaling algorithm.
791 * Specialise this for particular pixel formats by supplying the number of bytes
792 * per pixel and two functions: one for averaging pairs of neighbouring pixels
793 * on a single scanline, and a second for averaging pixels at corresponding
794 * positions on different scanlines.
798 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
799 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
801 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
802 const unsigned int inputWidth,
803 const unsigned int inputHeight,
804 const unsigned int desiredWidth,
805 const unsigned int desiredHeight,
806 BoxDimensionTest dimensionTest,
808 unsigned& outHeight )
814 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
816 // Scale the image until it would be smaller than desired, stopping if the
817 // resulting height or width would be less than 1:
818 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
819 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
821 const unsigned int lastWidth = scaledWidth;
825 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
827 const unsigned int lastScanlinePair = scaledHeight - 1;
829 // Scale pairs of scanlines until any spare one at the end is dropped:
830 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
832 // Scale two scanlines horizontally:
833 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
834 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
836 // Scale vertical pairs of pixels while the last two scanlines are still warm in
838 // Note, better access patterns for cache-coherence are possible for very large
839 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
840 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
842 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
843 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
844 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
849 ///@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.
850 outWidth = scaledWidth;
851 outHeight = scaledHeight;
856 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
858 DebugAssertScanlineParameters( pixels, width );
860 const unsigned int lastPair = EvenDown( width - 2 );
862 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
864 // Load all the byte pixel components we need:
865 const unsigned int c11 = pixels[pixel * 3];
866 const unsigned int c12 = pixels[pixel * 3 + 1];
867 const unsigned int c13 = pixels[pixel * 3 + 2];
868 const unsigned int c21 = pixels[pixel * 3 + 3];
869 const unsigned int c22 = pixels[pixel * 3 + 4];
870 const unsigned int c23 = pixels[pixel * 3 + 5];
872 // Save the averaged byte pixel components:
873 pixels[outPixel * 3] = AverageComponent( c11, c21 );
874 pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
875 pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
879 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
881 DebugAssertScanlineParameters( pixels, width );
882 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
884 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
886 const unsigned int lastPair = EvenDown( width - 2 );
888 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
890 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
891 alignedPixels[outPixel] = averaged;
895 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
897 DebugAssertScanlineParameters( pixels, width );
898 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
900 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
902 const unsigned int lastPair = EvenDown( width - 2 );
904 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
906 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
907 alignedPixels[outPixel] = averaged;
911 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
913 DebugAssertScanlineParameters( pixels, width );
915 const unsigned int lastPair = EvenDown( width - 2 );
917 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
919 // Load all the byte pixel components we need:
920 const unsigned int c11 = pixels[pixel * 2];
921 const unsigned int c12 = pixels[pixel * 2 + 1];
922 const unsigned int c21 = pixels[pixel * 2 + 2];
923 const unsigned int c22 = pixels[pixel * 2 + 3];
925 // Save the averaged byte pixel components:
926 pixels[outPixel * 2] = AverageComponent( c11, c21 );
927 pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
931 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
933 DebugAssertScanlineParameters( pixels, width );
935 const unsigned int lastPair = EvenDown( width - 2 );
937 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
939 // Load all the byte pixel components we need:
940 const unsigned int c1 = pixels[pixel];
941 const unsigned int c2 = pixels[pixel + 1];
943 // Save the averaged byte pixel component:
944 pixels[outPixel] = AverageComponent( c1, c2 );
949 * @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.
950 * 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.
952 void AverageScanlines1( const unsigned char * const scanline1,
953 const unsigned char * const __restrict__ scanline2,
954 unsigned char* const outputScanline,
955 const unsigned int width )
957 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
959 for( unsigned int component = 0; component < width; ++component )
961 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
965 void AverageScanlines2( const unsigned char * const scanline1,
966 const unsigned char * const __restrict__ scanline2,
967 unsigned char* const outputScanline,
968 const unsigned int width )
970 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
972 for( unsigned int component = 0; component < width * 2; ++component )
974 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
978 void AverageScanlines3( const unsigned char * const scanline1,
979 const unsigned char * const __restrict__ scanline2,
980 unsigned char* const outputScanline,
981 const unsigned int width )
983 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
985 for( unsigned int component = 0; component < width * 3; ++component )
987 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
991 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
992 const unsigned char * const __restrict__ scanline2,
993 unsigned char * const outputScanline,
994 const unsigned int width )
996 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
997 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
998 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
999 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1001 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1002 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1003 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1005 for( unsigned int pixel = 0; pixel < width; ++pixel )
1007 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1011 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1012 const unsigned char * const __restrict__ scanline2,
1013 unsigned char * const outputScanline,
1014 const unsigned int width )
1016 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1017 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1018 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1019 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1021 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1022 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1023 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1025 for( unsigned int pixel = 0; pixel < width; ++pixel )
1027 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1031 /// Dispatch to pixel format appropriate box filter downscaling functions.
1032 void DownscaleInPlacePow2( unsigned char * const pixels,
1033 Pixel::Format pixelFormat,
1034 unsigned int inputWidth,
1035 unsigned int inputHeight,
1036 unsigned int desiredWidth,
1037 unsigned int desiredHeight,
1038 FittingMode::Type fittingMode,
1039 SamplingMode::Type samplingMode,
1041 unsigned& outHeight )
1043 outWidth = inputWidth;
1044 outHeight = inputHeight;
1045 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1046 if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1048 // Check the pixel format is one that is supported:
1049 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1051 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1053 if( pixelFormat == Pixel::RGBA8888 )
1055 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1057 else if( pixelFormat == Pixel::RGB888 )
1059 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1061 else if( pixelFormat == Pixel::RGB565 )
1063 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1065 else if( pixelFormat == Pixel::LA88 )
1067 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1069 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1071 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1075 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1081 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1085 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1086 unsigned int inputWidth,
1087 unsigned int inputHeight,
1088 unsigned int desiredWidth,
1089 unsigned int desiredHeight,
1090 BoxDimensionTest dimensionTest,
1092 unsigned& outHeight )
1094 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1097 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1098 unsigned int inputWidth,
1099 unsigned int inputHeight,
1100 unsigned int desiredWidth,
1101 unsigned int desiredHeight,
1102 BoxDimensionTest dimensionTest,
1104 unsigned& outHeight )
1106 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1107 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1110 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1111 unsigned int inputWidth,
1112 unsigned int inputHeight,
1113 unsigned int desiredWidth,
1114 unsigned int desiredHeight,
1115 BoxDimensionTest dimensionTest,
1116 unsigned int& outWidth,
1117 unsigned int& outHeight )
1119 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1123 * @copydoc DownscaleInPlacePow2RGB888
1125 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1127 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1128 unsigned int inputWidth,
1129 unsigned int inputHeight,
1130 unsigned int desiredWidth,
1131 unsigned int desiredHeight,
1132 BoxDimensionTest dimensionTest,
1134 unsigned& outHeight )
1136 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1139 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1140 unsigned int inputWidth,
1141 unsigned int inputHeight,
1142 unsigned int desiredWidth,
1143 unsigned int desiredHeight,
1144 BoxDimensionTest dimensionTest,
1145 unsigned int& outWidth,
1146 unsigned int& outHeight )
1148 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1155 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1157 * Template is used purely as a type-safe code generator in this one
1158 * compilation unit. Generated code is inlined into type-specific wrapper
1159 * functions below which are exported to rest of module.
1161 template<typename PIXEL>
1162 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1163 unsigned int inputWidth,
1164 unsigned int inputHeight,
1165 uint8_t * outPixels,
1166 unsigned int desiredWidth,
1167 unsigned int desiredHeight )
1169 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1170 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1171 "The input and output buffers must not overlap for an upscaling.");
1172 DALI_ASSERT_DEBUG( ((uint64_t) inPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1173 DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1175 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1179 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1180 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1181 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1182 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1184 unsigned int inY = 0;
1185 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1187 // Round fixed point y coordinate to nearest integer:
1188 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1189 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1190 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1192 DALI_ASSERT_DEBUG( integerY < inputHeight );
1193 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1194 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1196 unsigned int inX = 0;
1197 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1199 // Round the fixed-point x coordinate to an integer:
1200 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1201 const PIXEL* const inPixelAddress = &inScanline[integerX];
1202 const PIXEL pixel = *inPixelAddress;
1203 outScanline[outX] = pixel;
1213 void PointSample4BPP( const unsigned char * inPixels,
1214 unsigned int inputWidth,
1215 unsigned int inputHeight,
1216 unsigned char * outPixels,
1217 unsigned int desiredWidth,
1218 unsigned int desiredHeight )
1220 PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1224 void PointSample2BPP( const unsigned char * inPixels,
1225 unsigned int inputWidth,
1226 unsigned int inputHeight,
1227 unsigned char * outPixels,
1228 unsigned int desiredWidth,
1229 unsigned int desiredHeight )
1231 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1235 void PointSample1BPP( const unsigned char * inPixels,
1236 unsigned int inputWidth,
1237 unsigned int inputHeight,
1238 unsigned char * outPixels,
1239 unsigned int desiredWidth,
1240 unsigned int desiredHeight )
1242 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1246 * RGB888 is a special case as its pixels are not aligned addressable units.
1248 void PointSample3BPP( const uint8_t * inPixels,
1249 unsigned int inputWidth,
1250 unsigned int inputHeight,
1251 uint8_t * outPixels,
1252 unsigned int desiredWidth,
1253 unsigned int desiredHeight )
1255 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1259 const unsigned int BYTES_PER_PIXEL = 3;
1261 // Generate fixed-point 16.16 deltas in input image coordinates:
1262 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1263 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1265 // Step through output image in whole integer pixel steps while tracking the
1266 // corresponding locations in the input image using 16.16 fixed-point
1268 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1269 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1271 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1272 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1273 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1274 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1276 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1278 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1279 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1280 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1282 // Issue loads for all pixel color components up-front:
1283 const unsigned int c0 = inPixelAddress[0];
1284 const unsigned int c1 = inPixelAddress[1];
1285 const unsigned int c2 = inPixelAddress[2];
1286 ///@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.
1288 // Output the pixel components:
1289 outScanline[outX] = c0;
1290 outScanline[outX + 1] = c1;
1291 outScanline[outX + 2] = c2;
1293 // Increment the fixed-point input coordinate:
1301 // Dispatch to a format-appropriate point sampling function:
1302 void PointSample( const unsigned char * inPixels,
1303 unsigned int inputWidth,
1304 unsigned int inputHeight,
1305 Pixel::Format pixelFormat,
1306 unsigned char * outPixels,
1307 unsigned int desiredWidth,
1308 unsigned int desiredHeight )
1310 // Check the pixel format is one that is supported:
1311 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1313 if( pixelFormat == Pixel::RGB888 )
1315 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1317 else if( pixelFormat == Pixel::RGBA8888 )
1319 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1321 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1323 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1325 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1327 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1331 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1336 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1340 // Linear sampling group below
1345 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1346 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1348 return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1351 /** @copydoc BilinearFilter1BPPByte */
1352 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1355 pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1356 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1360 /** @copydoc BilinearFilter1BPPByte */
1361 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1364 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1365 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1366 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1370 /** @copydoc BilinearFilter1BPPByte */
1371 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1373 const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1374 (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1375 BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1379 /** @copydoc BilinearFilter1BPPByte */
1380 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1383 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1384 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1385 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1386 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1391 * @brief Generic version of bilinear sampling image resize function.
1392 * @note Limited to one compilation unit and exposed through type-specific
1393 * wrapper functions below.
1397 PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1398 bool DEBUG_ASSERT_ALIGNMENT
1400 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1401 ImageDimensions inputDimensions,
1402 unsigned char * __restrict__ outPixels,
1403 ImageDimensions desiredDimensions )
1405 const unsigned int inputWidth = inputDimensions.GetWidth();
1406 const unsigned int inputHeight = inputDimensions.GetHeight();
1407 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1408 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1410 DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1411 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1412 "Input and output buffers cannot overlap.");
1413 if( DEBUG_ASSERT_ALIGNMENT )
1415 DALI_ASSERT_DEBUG( ((uint64_t) inPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1416 DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1419 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1423 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1424 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1425 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1426 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1428 unsigned int inY = 0;
1429 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1431 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1433 // Find the two scanlines to blend and the weight to blend with:
1434 const unsigned int integerY1 = inY >> 16u;
1435 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1436 const unsigned int inputYWeight = inY & 65535u;
1438 DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1439 DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1441 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1442 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1444 unsigned int inX = 0;
1445 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1447 // Work out the two pixel scanline offsets for this cluster of four samples:
1448 const unsigned int integerX1 = inX >> 16u;
1449 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1451 // Execute the loads:
1452 const PIXEL pixel1 = inScanline1[integerX1];
1453 const PIXEL pixel2 = inScanline2[integerX1];
1454 const PIXEL pixel3 = inScanline1[integerX2];
1455 const PIXEL pixel4 = inScanline2[integerX2];
1456 ///@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.
1458 // Weighted bilinear filter:
1459 const unsigned int inputXWeight = inX & 65535u;
1460 outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1470 // Format-specific linear scaling instantiations:
1472 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1473 ImageDimensions inputDimensions,
1474 unsigned char * __restrict__ outPixels,
1475 ImageDimensions desiredDimensions )
1477 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1480 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1481 ImageDimensions inputDimensions,
1482 unsigned char * __restrict__ outPixels,
1483 ImageDimensions desiredDimensions )
1485 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1488 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1489 ImageDimensions inputDimensions,
1490 unsigned char * __restrict__ outPixels,
1491 ImageDimensions desiredDimensions )
1493 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1496 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1497 ImageDimensions inputDimensions,
1498 unsigned char * __restrict__ outPixels,
1499 ImageDimensions desiredDimensions )
1501 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1504 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1505 ImageDimensions inputDimensions,
1506 unsigned char * __restrict__ outPixels,
1507 ImageDimensions desiredDimensions )
1509 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1512 // Dispatch to a format-appropriate linear sampling function:
1513 void LinearSample( const unsigned char * __restrict__ inPixels,
1514 ImageDimensions inDimensions,
1515 Pixel::Format pixelFormat,
1516 unsigned char * __restrict__ outPixels,
1517 ImageDimensions outDimensions )
1519 // Check the pixel format is one that is supported:
1520 if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1522 if( pixelFormat == Pixel::RGB888 )
1524 LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1526 else if( pixelFormat == Pixel::RGBA8888 )
1528 LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1530 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1532 LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1534 else if( pixelFormat == Pixel::LA88 )
1536 LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1538 else if ( pixelFormat == Pixel::RGB565 )
1540 LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1544 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1549 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1553 } /* namespace Platform */
1554 } /* namespace Internal */
1555 } /* namespace Dali */