2 * Copyright (c) 2017 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 using Integration::Bitmap;
55 using Integration::BitmapPtr;
56 typedef unsigned char PixelBuffer;
59 * @brief 4 byte pixel structure.
67 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
70 * @brief RGB888 pixel structure.
77 } __attribute__((packed, aligned(1)));
80 * @brief RGB565 pixel typedefed from a short.
82 * Access fields by manual shifting and masking.
84 typedef uint16_t PixelRGB565;
87 * @brief a Pixel composed of two independent byte components.
93 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
96 #if defined(DEBUG_ENABLED)
98 * Disable logging of image operations or make it verbose from the commandline
99 * as follows (e.g., for dali demo app):
101 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
102 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
105 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
108 /** @return The greatest even number less than or equal to the argument. */
109 inline unsigned int EvenDown( const unsigned int a )
111 const unsigned int evened = a & ~1u;
116 * @brief Log bad parameters.
118 void ValidateScalingParameters( const unsigned int inputWidth,
119 const unsigned int inputHeight,
120 const unsigned int desiredWidth,
121 const unsigned int desiredHeight )
123 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
125 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
128 if( desiredWidth == 0u || desiredHeight == 0u )
130 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
133 if( inputWidth == 0u || inputHeight == 0u )
135 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
140 * @brief Do debug assertions common to all scanline halving functions.
141 * @note Inline and in anon namespace so should boil away in release builds.
143 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
145 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
146 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
147 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
151 * @brief Assertions on params to functions averaging pairs of scanlines.
152 * @note Inline as intended to boil away in release.
154 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
155 const uint8_t * const scanline2,
156 uint8_t* const outputScanline,
157 const size_t widthInComponents )
159 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
160 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
161 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
162 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
163 DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
167 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
169 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
171 BoxDimensionTest dimensionTest;
172 dimensionTest = BoxDimensionTestEither;
174 switch( fittingMode )
176 // Shrink to fit attempts to make one or zero dimensions smaller than the
177 // desired dimensions and one or two dimensions exactly the same as the desired
178 // ones, so as long as one dimension is larger than the desired size, box
179 // filtering can continue even if the second dimension is smaller than the
180 // desired dimensions:
181 case FittingMode::SHRINK_TO_FIT:
183 dimensionTest = BoxDimensionTestEither;
186 // Scale to fill mode keeps both dimensions at least as large as desired:
187 case FittingMode::SCALE_TO_FILL:
189 dimensionTest = BoxDimensionTestBoth;
192 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
193 case FittingMode::FIT_WIDTH:
195 dimensionTest = BoxDimensionTestX;
198 // X Dimension is ignored by definition in FIT_HEIGHT mode:
199 case FittingMode::FIT_HEIGHT:
201 dimensionTest = BoxDimensionTestY;
206 return dimensionTest;
210 * @brief Work out the dimensions for a uniform scaling of the input to map it
211 * into the target while effecting ShinkToFit scaling mode.
213 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
215 // Scale the input by the least extreme of the two dimensions:
216 const float widthScale = target.GetX() / float(source.GetX());
217 const float heightScale = target.GetY() / float(source.GetY());
218 const float scale = widthScale < heightScale ? widthScale : heightScale;
220 // Do no scaling at all if the result would increase area:
226 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
230 * @brief Work out the dimensions for a uniform scaling of the input to map it
231 * into the target while effecting SCALE_TO_FILL scaling mode.
232 * @note An image scaled into the output dimensions will need either top and
233 * bottom or left and right to be cropped away unless the source was pre-cropped
234 * to match the destination aspect ratio.
236 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
238 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
239 // Scale the input by the least extreme of the two dimensions:
240 const float widthScale = target.GetX() / float(source.GetX());
241 const float heightScale = target.GetY() / float(source.GetY());
242 const float scale = widthScale > heightScale ? widthScale : heightScale;
244 // Do no scaling at all if the result would increase area:
250 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
254 * @brief Work out the dimensions for a uniform scaling of the input to map it
255 * into the target while effecting FIT_WIDTH scaling mode.
257 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
259 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
260 const float scale = target.GetX() / float(source.GetX());
262 // Do no scaling at all if the result would increase area:
267 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
271 * @brief Work out the dimensions for a uniform scaling of the input to map it
272 * into the target while effecting FIT_HEIGHT scaling mode.
274 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
276 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
277 const float scale = target.GetY() / float(source.GetY());
279 // Do no scaling at all if the result would increase area:
285 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
289 * @brief Generate the rectangle to use as the target of a pixel sampling pass
290 * (e.g., nearest or linear).
292 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
294 ImageDimensions fitDimensions;
295 switch( fittingMode )
297 case FittingMode::SHRINK_TO_FIT:
299 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
302 case FittingMode::SCALE_TO_FILL:
304 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
307 case FittingMode::FIT_WIDTH:
309 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
312 case FittingMode::FIT_HEIGHT:
314 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
319 return fitDimensions;
323 * @brief Calculate the number of lines on the X and Y axis that need to be
324 * either added or removed with repect to the specified fitting mode.
325 * (e.g., nearest or linear).
326 * @param[in] sourceSize The size of the source image
327 * @param[in] fittingMode The fitting mode to use
328 * @param[in/out] requestedSize The target size that the image will be fitted to.
329 * If the source image is smaller than the requested size, the source is not scaled up.
330 * So we reduce the target size while keeping aspect by lowering resolution.
331 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
332 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
334 void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
336 const unsigned int sourceWidth( sourceSize.GetWidth() );
337 const unsigned int sourceHeight( sourceSize.GetHeight() );
338 const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
342 switch( fittingMode )
344 case FittingMode::FIT_WIDTH:
346 finalWidth = sourceWidth;
347 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
350 scanlinesToCrop = -( finalHeight - sourceHeight );
354 case FittingMode::FIT_HEIGHT:
356 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
357 finalHeight = sourceHeight;
359 columnsToCrop = -( finalWidth - sourceWidth );
364 case FittingMode::SHRINK_TO_FIT:
366 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
367 if( sourceAspect > targetAspect )
369 finalWidth = sourceWidth;
370 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
373 scanlinesToCrop = -( finalHeight - sourceHeight );
377 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
378 finalHeight = sourceHeight;
380 columnsToCrop = -( finalWidth - sourceWidth );
386 case FittingMode::SCALE_TO_FILL:
388 const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
389 if( sourceAspect > targetAspect )
391 finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
392 finalHeight = sourceHeight;
394 columnsToCrop = -( finalWidth - sourceWidth );
399 finalWidth = sourceWidth;
400 finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
403 scanlinesToCrop = -( finalHeight - sourceHeight );
409 requestedSize.SetWidth( finalWidth );
410 requestedSize.SetHeight( finalHeight );
414 * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
416 Dali::Devel::PixelBuffer MakePixelBuffer( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
418 DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
420 // Allocate a pixel buffer to hold the image passed in:
421 auto newBitmap = Dali::Devel::PixelBuffer::New( width, height, pixelFormat );
423 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
424 memcpy( newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
429 * @brief Work out the desired width and height, accounting for zeros.
431 * @param[in] bitmapWidth Width of image before processing.
432 * @param[in] bitmapHeight Height of image before processing.
433 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
434 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
435 * @return Dimensions of area to scale image into after special rules are applied.
437 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
439 unsigned int maxSize = Dali::GetMaxTextureSize();
441 // If no dimensions have been requested, default to the source ones:
442 if( requestedWidth == 0 && requestedHeight == 0 )
444 if( bitmapWidth <= maxSize && bitmapHeight <= maxSize )
446 return ImageDimensions( bitmapWidth, bitmapHeight );
450 // Calculate the size from the max texture size and the source image aspect ratio
451 if( bitmapWidth > bitmapHeight )
453 return ImageDimensions( maxSize, bitmapHeight * maxSize / static_cast< float >( bitmapWidth ) + 0.5f );
457 return ImageDimensions( bitmapWidth * maxSize / static_cast< float >( bitmapHeight ) + 0.5f, maxSize );
462 // If both dimensions have values requested, use them both:
463 if( requestedWidth != 0 && requestedHeight != 0 )
465 if( requestedWidth <= maxSize && requestedHeight <= maxSize )
467 return ImageDimensions( requestedWidth, requestedHeight );
471 // Calculate the size from the max texture size and the source image aspect ratio
472 if( requestedWidth > requestedHeight )
474 return ImageDimensions( maxSize, requestedHeight * maxSize / static_cast< float >( requestedWidth ) + 0.5f );
478 return ImageDimensions( requestedWidth * maxSize / static_cast< float >( requestedHeight ) + 0.5f, maxSize );
483 // Only one of the dimensions has been requested. Calculate the other from
484 // the requested one and the source image aspect ratio:
485 if( requestedWidth != 0 )
487 requestedWidth = std::min( requestedWidth, maxSize );
488 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
491 requestedHeight = std::min( requestedHeight, maxSize );
492 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
495 } // namespace - unnamed
497 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
499 return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
503 * @brief Apply cropping and padding for specified fitting mode.
505 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
506 * based on the fitting mode.
508 * This will add vertical or horizontal borders if necessary.
509 * Crop the source image data vertically or horizontally if necessary.
510 * The aspect of the source image is preserved.
511 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
512 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
513 * GPU scaling to be performed at render time giving the same result with less texture traversal.
515 * @param[in] bitmap The source pixel buffer to perform modifications on.
516 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
517 * @param[in] fittingMode The fitting mode to use.
519 * @return A new bitmap with the padding and cropping required for fitting mode applied.
520 * If no modification is needed or possible, the passed in bitmap is returned.
522 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
525 * @brief Adds horizontal or vertical borders to the source image.
527 * @param[in] targetPixels The destination image pointer to draw the borders on.
528 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
529 * @param[in] targetDimensions The dimensions of the destination image.
530 * @param[in] padDimensions The columns and scanlines to pad with borders.
532 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
534 Dali::Devel::PixelBuffer ApplyAttributesToBitmap( Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
538 // Calculate the desired box, accounting for a possible zero component:
539 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
541 // If a different size than the raw one has been requested, resize the image
542 // maximally using a repeated box filter without making it smaller than the
543 // requested size in either dimension:
544 bitmap = DownscaleBitmap( bitmap, desiredDimensions, fittingMode, samplingMode );
546 // Cut the bitmap according to the desired width and height so that the
547 // resulting bitmap has the same aspect ratio as the desired dimensions.
548 // Add crop and add borders if necessary depending on fitting mode.
551 bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
558 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
560 const unsigned int inputWidth = bitmap.GetWidth();
561 const unsigned int inputHeight = bitmap.GetHeight();
563 if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
565 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
567 else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
569 // Calculate any padding or cropping that needs to be done based on the fitting mode.
570 // Note: If the desired size is larger than the original image, the desired size will be
571 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
572 int scanlinesToCrop = 0;
573 int columnsToCrop = 0;
575 CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
577 unsigned int desiredWidth( desiredDimensions.GetWidth() );
578 unsigned int desiredHeight( desiredDimensions.GetHeight() );
580 // Action the changes by making a new bitmap with the central part of the loaded one if required.
581 if( scanlinesToCrop != 0 || columnsToCrop != 0 )
583 // Split the adding and removing of scanlines and columns into separate variables,
584 // so we can use one piece of generic code to action the changes.
585 unsigned int scanlinesToPad = 0;
586 unsigned int columnsToPad = 0;
587 if( scanlinesToCrop < 0 )
589 scanlinesToPad = -scanlinesToCrop;
592 if( columnsToCrop < 0 )
594 columnsToPad = -columnsToCrop;
598 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
599 if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
600 ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
602 DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
606 // Create new PixelBuffer with the desired size.
607 const auto pixelFormat = bitmap.GetPixelFormat();
609 auto croppedBitmap = Devel::PixelBuffer::New( desiredWidth, desiredHeight, pixelFormat );
611 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
612 // The cropping is added to the source pointer, and the padding is added to the destination.
613 const auto bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
614 const PixelBuffer * const sourcePixels = bitmap.GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
615 PixelBuffer * const targetPixels = croppedBitmap.GetBuffer();
616 PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
617 DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
619 // Copy the image data to the new bitmap.
620 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
621 unsigned int outputSpan( desiredWidth * bytesPerPixel );
622 if( columnsToCrop == 0 && columnsToPad == 0 )
624 memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
628 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
629 // Precalculate any constants to optimize the inner loop.
630 const unsigned int inputSpan( inputWidth * bytesPerPixel );
631 const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
632 const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
634 for( unsigned int y = 0; y < scanlinesToCopy; ++y )
636 memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
640 // Add vertical or horizontal borders to the final image (if required).
641 desiredDimensions.SetWidth( desiredWidth );
642 desiredDimensions.SetHeight( desiredHeight );
643 AddBorders( croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
644 // Overwrite the loaded bitmap with the cropped version
645 bitmap = croppedBitmap;
652 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
654 // Assign ints for faster access.
655 unsigned int desiredWidth( targetDimensions.GetWidth() );
656 unsigned int desiredHeight( targetDimensions.GetHeight() );
657 unsigned int columnsToPad( padDimensions.GetWidth() );
658 unsigned int scanlinesToPad( padDimensions.GetHeight() );
659 unsigned int outputSpan( desiredWidth * bytesPerPixel );
661 // Add letterboxing (symmetrical borders) if needed.
662 if( scanlinesToPad > 0 )
664 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
665 memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
667 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
668 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
669 unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
672 memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
674 else if( columnsToPad > 0 )
676 // Add a left and right border.
678 // Pre-calculate span size outside of loop.
679 unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
680 for( unsigned int y = 0; y < desiredHeight; ++y )
682 memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
686 // Pre-calculate the initial x offset as it is always the same for a small optimization.
687 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
688 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
689 unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
690 PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
691 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
693 for( unsigned int y = 0; y < desiredHeight; ++y )
695 memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
700 Dali::Devel::PixelBuffer DownscaleBitmap( Dali::Devel::PixelBuffer bitmap,
701 ImageDimensions desired,
702 FittingMode::Type fittingMode,
703 SamplingMode::Type samplingMode )
705 // Source dimensions as loaded from resources (e.g. filesystem):
706 auto bitmapWidth = bitmap.GetWidth();
707 auto bitmapHeight = bitmap.GetHeight();
708 // Desired dimensions (the rectangle to fit the source image to):
709 auto desiredWidth = desired.GetWidth();
710 auto desiredHeight = desired.GetHeight();
712 Dali::Devel::PixelBuffer outputBitmap { bitmap };
714 // If a different size than the raw one has been requested, resize the image:
716 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
717 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
719 auto pixelFormat = bitmap.GetPixelFormat();
721 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
722 unsigned int shrunkWidth = -1, shrunkHeight = -1;
723 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
725 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
726 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
727 const unsigned int filteredWidth = filteredDimensions.GetWidth();
728 const unsigned int filteredHeight = filteredDimensions.GetHeight();
730 // Run a filter to scale down the bitmap if it needs it:
731 bool filtered = false;
732 if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
734 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
735 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
737 outputBitmap = Dali::Devel::PixelBuffer::New( filteredWidth, filteredHeight, pixelFormat );
741 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
743 LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions );
747 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight );
753 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
754 if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
756 outputBitmap = MakePixelBuffer( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
766 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
767 * @param test Which combination of the two dimensions matter for terminating the filtering.
768 * @param scaledWidth The width of the current downscaled image.
769 * @param scaledHeight The height of the current downscaled image.
770 * @param desiredWidth The target width for the downscaling.
771 * @param desiredHeight The target height for the downscaling.
773 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
775 bool keepScaling = false;
776 const unsigned int nextWidth = scaledWidth >> 1u;
777 const unsigned int nextHeight = scaledHeight >> 1u;
779 if( nextWidth >= 1u && nextHeight >= 1u )
783 case BoxDimensionTestEither:
785 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
788 case BoxDimensionTestBoth:
790 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
793 case BoxDimensionTestX:
795 keepScaling = nextWidth >= desiredWidth;
798 case BoxDimensionTestY:
800 keepScaling = nextHeight >= desiredHeight;
810 * @brief A shared implementation of the overall iterative box filter
811 * downscaling algorithm.
813 * Specialise this for particular pixel formats by supplying the number of bytes
814 * per pixel and two functions: one for averaging pairs of neighbouring pixels
815 * on a single scanline, and a second for averaging pixels at corresponding
816 * positions on different scanlines.
820 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
821 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
823 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
824 const unsigned int inputWidth,
825 const unsigned int inputHeight,
826 const unsigned int desiredWidth,
827 const unsigned int desiredHeight,
828 BoxDimensionTest dimensionTest,
830 unsigned& outHeight )
836 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
838 // Scale the image until it would be smaller than desired, stopping if the
839 // resulting height or width would be less than 1:
840 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
841 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
843 const unsigned int lastWidth = scaledWidth;
847 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
849 const unsigned int lastScanlinePair = scaledHeight - 1;
851 // Scale pairs of scanlines until any spare one at the end is dropped:
852 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
854 // Scale two scanlines horizontally:
855 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
856 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
858 // Scale vertical pairs of pixels while the last two scanlines are still warm in
860 // Note, better access patterns for cache-coherence are possible for very large
861 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
862 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
864 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
865 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
866 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
871 ///@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.
872 outWidth = scaledWidth;
873 outHeight = scaledHeight;
878 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
880 DebugAssertScanlineParameters( pixels, width );
882 const unsigned int lastPair = EvenDown( width - 2 );
884 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
886 // Load all the byte pixel components we need:
887 const unsigned int c11 = pixels[pixel * 3];
888 const unsigned int c12 = pixels[pixel * 3 + 1];
889 const unsigned int c13 = pixels[pixel * 3 + 2];
890 const unsigned int c21 = pixels[pixel * 3 + 3];
891 const unsigned int c22 = pixels[pixel * 3 + 4];
892 const unsigned int c23 = pixels[pixel * 3 + 5];
894 // Save the averaged byte pixel components:
895 pixels[outPixel * 3] = AverageComponent( c11, c21 );
896 pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
897 pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
901 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
903 DebugAssertScanlineParameters( pixels, width );
904 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
906 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
908 const unsigned int lastPair = EvenDown( width - 2 );
910 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
912 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
913 alignedPixels[outPixel] = averaged;
917 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
919 DebugAssertScanlineParameters( pixels, width );
920 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
922 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
924 const unsigned int lastPair = EvenDown( width - 2 );
926 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
928 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
929 alignedPixels[outPixel] = averaged;
933 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
935 DebugAssertScanlineParameters( pixels, width );
937 const unsigned int lastPair = EvenDown( width - 2 );
939 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
941 // Load all the byte pixel components we need:
942 const unsigned int c11 = pixels[pixel * 2];
943 const unsigned int c12 = pixels[pixel * 2 + 1];
944 const unsigned int c21 = pixels[pixel * 2 + 2];
945 const unsigned int c22 = pixels[pixel * 2 + 3];
947 // Save the averaged byte pixel components:
948 pixels[outPixel * 2] = AverageComponent( c11, c21 );
949 pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
953 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
955 DebugAssertScanlineParameters( pixels, width );
957 const unsigned int lastPair = EvenDown( width - 2 );
959 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
961 // Load all the byte pixel components we need:
962 const unsigned int c1 = pixels[pixel];
963 const unsigned int c2 = pixels[pixel + 1];
965 // Save the averaged byte pixel component:
966 pixels[outPixel] = AverageComponent( c1, c2 );
971 * @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.
972 * 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.
974 void AverageScanlines1( const unsigned char * const scanline1,
975 const unsigned char * const __restrict__ scanline2,
976 unsigned char* const outputScanline,
977 const unsigned int width )
979 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
981 for( unsigned int component = 0; component < width; ++component )
983 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
987 void AverageScanlines2( const unsigned char * const scanline1,
988 const unsigned char * const __restrict__ scanline2,
989 unsigned char* const outputScanline,
990 const unsigned int width )
992 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
994 for( unsigned int component = 0; component < width * 2; ++component )
996 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1000 void AverageScanlines3( const unsigned char * const scanline1,
1001 const unsigned char * const __restrict__ scanline2,
1002 unsigned char* const outputScanline,
1003 const unsigned int width )
1005 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
1007 for( unsigned int component = 0; component < width * 3; ++component )
1009 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1013 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
1014 const unsigned char * const __restrict__ scanline2,
1015 unsigned char * const outputScanline,
1016 const unsigned int width )
1018 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
1019 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1020 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1021 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1023 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1024 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1025 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1027 for( unsigned int pixel = 0; pixel < width; ++pixel )
1029 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1033 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1034 const unsigned char * const __restrict__ scanline2,
1035 unsigned char * const outputScanline,
1036 const unsigned int width )
1038 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1039 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1040 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1041 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1043 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1044 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1045 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1047 for( unsigned int pixel = 0; pixel < width; ++pixel )
1049 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1053 /// Dispatch to pixel format appropriate box filter downscaling functions.
1054 void DownscaleInPlacePow2( unsigned char * const pixels,
1055 Pixel::Format pixelFormat,
1056 unsigned int inputWidth,
1057 unsigned int inputHeight,
1058 unsigned int desiredWidth,
1059 unsigned int desiredHeight,
1060 FittingMode::Type fittingMode,
1061 SamplingMode::Type samplingMode,
1063 unsigned& outHeight )
1065 outWidth = inputWidth;
1066 outHeight = inputHeight;
1067 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1068 if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1070 // Check the pixel format is one that is supported:
1071 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1073 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1075 if( pixelFormat == Pixel::RGBA8888 )
1077 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1079 else if( pixelFormat == Pixel::RGB888 )
1081 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1083 else if( pixelFormat == Pixel::RGB565 )
1085 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1087 else if( pixelFormat == Pixel::LA88 )
1089 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1091 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1093 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1097 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1103 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1107 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1108 unsigned int inputWidth,
1109 unsigned int inputHeight,
1110 unsigned int desiredWidth,
1111 unsigned int desiredHeight,
1112 BoxDimensionTest dimensionTest,
1114 unsigned& outHeight )
1116 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1119 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1120 unsigned int inputWidth,
1121 unsigned int inputHeight,
1122 unsigned int desiredWidth,
1123 unsigned int desiredHeight,
1124 BoxDimensionTest dimensionTest,
1126 unsigned& outHeight )
1128 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1129 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1132 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1133 unsigned int inputWidth,
1134 unsigned int inputHeight,
1135 unsigned int desiredWidth,
1136 unsigned int desiredHeight,
1137 BoxDimensionTest dimensionTest,
1138 unsigned int& outWidth,
1139 unsigned int& outHeight )
1141 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1145 * @copydoc DownscaleInPlacePow2RGB888
1147 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1149 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1150 unsigned int inputWidth,
1151 unsigned int inputHeight,
1152 unsigned int desiredWidth,
1153 unsigned int desiredHeight,
1154 BoxDimensionTest dimensionTest,
1156 unsigned& outHeight )
1158 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1161 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1162 unsigned int inputWidth,
1163 unsigned int inputHeight,
1164 unsigned int desiredWidth,
1165 unsigned int desiredHeight,
1166 BoxDimensionTest dimensionTest,
1167 unsigned int& outWidth,
1168 unsigned int& outHeight )
1170 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1177 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1179 * Template is used purely as a type-safe code generator in this one
1180 * compilation unit. Generated code is inlined into type-specific wrapper
1181 * functions below which are exported to rest of module.
1183 template<typename PIXEL>
1184 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1185 unsigned int inputWidth,
1186 unsigned int inputHeight,
1187 uint8_t * outPixels,
1188 unsigned int desiredWidth,
1189 unsigned int desiredHeight )
1191 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1192 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1193 "The input and output buffers must not overlap for an upscaling.");
1194 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, ...)." );
1195 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, ...)." );
1197 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1201 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1202 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1203 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1204 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1206 unsigned int inY = 0;
1207 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1209 // Round fixed point y coordinate to nearest integer:
1210 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1211 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1212 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1214 DALI_ASSERT_DEBUG( integerY < inputHeight );
1215 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1216 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1218 unsigned int inX = 0;
1219 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1221 // Round the fixed-point x coordinate to an integer:
1222 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1223 const PIXEL* const inPixelAddress = &inScanline[integerX];
1224 const PIXEL pixel = *inPixelAddress;
1225 outScanline[outX] = pixel;
1235 void PointSample4BPP( 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<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1246 void PointSample2BPP( const unsigned char * inPixels,
1247 unsigned int inputWidth,
1248 unsigned int inputHeight,
1249 unsigned char * outPixels,
1250 unsigned int desiredWidth,
1251 unsigned int desiredHeight )
1253 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1257 void PointSample1BPP( const unsigned char * inPixels,
1258 unsigned int inputWidth,
1259 unsigned int inputHeight,
1260 unsigned char * outPixels,
1261 unsigned int desiredWidth,
1262 unsigned int desiredHeight )
1264 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1268 * RGB888 is a special case as its pixels are not aligned addressable units.
1270 void PointSample3BPP( const uint8_t * inPixels,
1271 unsigned int inputWidth,
1272 unsigned int inputHeight,
1273 uint8_t * outPixels,
1274 unsigned int desiredWidth,
1275 unsigned int desiredHeight )
1277 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1281 const unsigned int BYTES_PER_PIXEL = 3;
1283 // Generate fixed-point 16.16 deltas in input image coordinates:
1284 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1285 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1287 // Step through output image in whole integer pixel steps while tracking the
1288 // corresponding locations in the input image using 16.16 fixed-point
1290 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1291 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1293 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1294 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1295 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1296 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1298 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1300 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1301 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1302 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1304 // Issue loads for all pixel color components up-front:
1305 const unsigned int c0 = inPixelAddress[0];
1306 const unsigned int c1 = inPixelAddress[1];
1307 const unsigned int c2 = inPixelAddress[2];
1308 ///@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.
1310 // Output the pixel components:
1311 outScanline[outX] = c0;
1312 outScanline[outX + 1] = c1;
1313 outScanline[outX + 2] = c2;
1315 // Increment the fixed-point input coordinate:
1323 // Dispatch to a format-appropriate point sampling function:
1324 void PointSample( const unsigned char * inPixels,
1325 unsigned int inputWidth,
1326 unsigned int inputHeight,
1327 Pixel::Format pixelFormat,
1328 unsigned char * outPixels,
1329 unsigned int desiredWidth,
1330 unsigned int desiredHeight )
1332 // Check the pixel format is one that is supported:
1333 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1335 if( pixelFormat == Pixel::RGB888 )
1337 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1339 else if( pixelFormat == Pixel::RGBA8888 )
1341 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1343 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1345 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1347 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1349 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1353 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1358 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1362 // Linear sampling group below
1367 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1368 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1370 return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1373 /** @copydoc BilinearFilter1BPPByte */
1374 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1377 pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1378 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1382 /** @copydoc BilinearFilter1BPPByte */
1383 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1386 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1387 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1388 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1392 /** @copydoc BilinearFilter1BPPByte */
1393 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1395 const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1396 (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1397 BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1401 /** @copydoc BilinearFilter1BPPByte */
1402 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1405 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1406 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1407 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1408 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1413 * @brief Generic version of bilinear sampling image resize function.
1414 * @note Limited to one compilation unit and exposed through type-specific
1415 * wrapper functions below.
1419 PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1420 bool DEBUG_ASSERT_ALIGNMENT
1422 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1423 ImageDimensions inputDimensions,
1424 unsigned char * __restrict__ outPixels,
1425 ImageDimensions desiredDimensions )
1427 const unsigned int inputWidth = inputDimensions.GetWidth();
1428 const unsigned int inputHeight = inputDimensions.GetHeight();
1429 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1430 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1432 DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1433 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1434 "Input and output buffers cannot overlap.");
1435 if( DEBUG_ASSERT_ALIGNMENT )
1437 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, ...)." );
1438 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, ...)." );
1441 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1445 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1446 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1447 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1448 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1450 unsigned int inY = 0;
1451 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1453 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1455 // Find the two scanlines to blend and the weight to blend with:
1456 const unsigned int integerY1 = inY >> 16u;
1457 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1458 const unsigned int inputYWeight = inY & 65535u;
1460 DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1461 DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1463 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1464 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1466 unsigned int inX = 0;
1467 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1469 // Work out the two pixel scanline offsets for this cluster of four samples:
1470 const unsigned int integerX1 = inX >> 16u;
1471 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1473 // Execute the loads:
1474 const PIXEL pixel1 = inScanline1[integerX1];
1475 const PIXEL pixel2 = inScanline2[integerX1];
1476 const PIXEL pixel3 = inScanline1[integerX2];
1477 const PIXEL pixel4 = inScanline2[integerX2];
1478 ///@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.
1480 // Weighted bilinear filter:
1481 const unsigned int inputXWeight = inX & 65535u;
1482 outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1492 // Format-specific linear scaling instantiations:
1494 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1495 ImageDimensions inputDimensions,
1496 unsigned char * __restrict__ outPixels,
1497 ImageDimensions desiredDimensions )
1499 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1502 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1503 ImageDimensions inputDimensions,
1504 unsigned char * __restrict__ outPixels,
1505 ImageDimensions desiredDimensions )
1507 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1510 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1511 ImageDimensions inputDimensions,
1512 unsigned char * __restrict__ outPixels,
1513 ImageDimensions desiredDimensions )
1515 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1518 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1519 ImageDimensions inputDimensions,
1520 unsigned char * __restrict__ outPixels,
1521 ImageDimensions desiredDimensions )
1523 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1526 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1527 ImageDimensions inputDimensions,
1528 unsigned char * __restrict__ outPixels,
1529 ImageDimensions desiredDimensions )
1531 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1535 void Resample( const unsigned char * __restrict__ inPixels,
1536 ImageDimensions inputDimensions,
1537 unsigned char * __restrict__ outPixels,
1538 ImageDimensions desiredDimensions,
1539 Resampler::Filter filterType,
1540 int numChannels, bool hasAlpha )
1542 // Got from the test.cpp of the ImageResampler lib.
1543 const float ONE_DIV_255 = 1.0f / 255.0f;
1544 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1545 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1546 const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
1548 static bool loadColorSpaces = true;
1549 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1550 static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1552 if( loadColorSpaces ) // Only create the color space conversions on the first execution
1554 loadColorSpaces = false;
1556 for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
1558 srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
1561 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
1562 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1564 for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
1566 int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
1571 else if( k > MAX_UNSIGNED_CHAR )
1573 k = MAX_UNSIGNED_CHAR;
1575 linearToSrgb[i] = static_cast<unsigned char>( k );
1579 std::vector<Resampler*> resamplers( numChannels );
1580 std::vector<Vector<float>> samples(numChannels);
1582 const int srcWidth = inputDimensions.GetWidth();
1583 const int srcHeight = inputDimensions.GetHeight();
1584 const int dstWidth = desiredDimensions.GetWidth();
1585 const int dstHeight = desiredDimensions.GetHeight();
1587 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1588 // used for the other components (a memory and slight cache efficiency optimization).
1589 resamplers[0] = new Resampler( srcWidth,
1593 Resampler::BOUNDARY_CLAMP,
1594 0.0f, // sample_low,
1595 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1596 filterType, // The type of filter.
1598 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1599 FILTER_SCALE, // src_x_ofs,
1600 FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1601 samples[0].Resize( srcWidth );
1602 for( int i = 1; i < numChannels; ++i )
1604 resamplers[i] = new Resampler( srcWidth,
1608 Resampler::BOUNDARY_CLAMP,
1612 resamplers[0]->get_clist_x(),
1613 resamplers[0]->get_clist_y(),
1616 samples[i].Resize( srcWidth );
1619 const int srcPitch = srcWidth * numChannels;
1620 const int dstPitch = dstWidth * numChannels;
1623 for( int srcY = 0; srcY < srcHeight; ++srcY )
1625 const unsigned char* pSrc = &inPixels[srcY * srcPitch];
1627 for( int x = 0; x < srcWidth; ++x )
1629 for( int c = 0; c < numChannels; ++c )
1631 if( c == ALPHA_CHANNEL && hasAlpha )
1633 samples[c][x] = *pSrc++ * ONE_DIV_255;
1637 samples[c][x] = srgbToLinear[*pSrc++];
1642 for( int c = 0; c < numChannels; ++c )
1644 if( !resamplers[c]->put_line( &samples[c][0] ) )
1646 DALI_ASSERT_DEBUG( !"Out of memory" );
1653 for( compIndex = 0; compIndex < numChannels; ++compIndex )
1655 const float* pOutputSamples = resamplers[compIndex]->get_line();
1656 if( !pOutputSamples )
1661 const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
1662 DALI_ASSERT_DEBUG( dstY < dstHeight );
1663 unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
1665 for( int x = 0; x < dstWidth; ++x )
1667 if( isAlphaChannel )
1669 int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
1674 else if( c > MAX_UNSIGNED_CHAR )
1676 c = MAX_UNSIGNED_CHAR;
1678 *pDst = static_cast<unsigned char>( c );
1682 int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
1687 else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
1689 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
1691 *pDst = linearToSrgb[j];
1694 pDst += numChannels;
1697 if( compIndex < numChannels )
1706 // Delete the resamplers.
1707 for( int i = 0; i < numChannels; ++i )
1709 delete resamplers[i];
1713 void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
1714 ImageDimensions inputDimensions,
1715 unsigned char * __restrict__ outPixels,
1716 ImageDimensions desiredDimensions )
1718 Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true );
1721 void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
1722 ImageDimensions inputDimensions,
1723 unsigned char * __restrict__ outPixels,
1724 ImageDimensions desiredDimensions )
1727 Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false );
1730 // Dispatch to a format-appropriate linear sampling function:
1731 void LinearSample( const unsigned char * __restrict__ inPixels,
1732 ImageDimensions inDimensions,
1733 Pixel::Format pixelFormat,
1734 unsigned char * __restrict__ outPixels,
1735 ImageDimensions outDimensions )
1737 // Check the pixel format is one that is supported:
1738 if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1740 if( pixelFormat == Pixel::RGB888 )
1742 LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1744 else if( pixelFormat == Pixel::RGBA8888 )
1746 LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1748 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1750 LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1752 else if( pixelFormat == Pixel::LA88 )
1754 LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1756 else if ( pixelFormat == Pixel::RGB565 )
1758 LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1762 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1767 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1771 } /* namespace Platform */
1772 } /* namespace Internal */
1773 } /* namespace Dali */