2 * Copyright (c) 2014 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 using Integration::Bitmap;
40 using Integration::BitmapPtr;
41 typedef unsigned char PixelBuffer;
44 * @brief 4 byte pixel structure.
52 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
55 * @brief RGB888 pixel structure.
62 } __attribute__((packed, aligned(1)));
65 * @brief RGB565 pixel typedefed from a short.
67 * Access fields by manual shifting and masking.
69 typedef uint16_t PixelRGB565;
72 * @brief a Pixel composed of two independent byte components.
78 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
81 #if defined(DEBUG_ENABLED)
83 * Disable logging of image operations or make it verbose from the commandline
84 * as follows (e.g., for dali demo app):
86 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
87 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
90 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
93 /** @return The greatest even number less than or equal to the argument. */
94 inline unsigned int EvenDown( const unsigned int a )
96 const unsigned int evened = a & ~1u;
101 * @brief Log bad parameters.
103 void ValidateScalingParameters( const unsigned int inputWidth,
104 const unsigned int inputHeight,
105 const unsigned int desiredWidth,
106 const unsigned int desiredHeight )
108 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
110 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
113 if( desiredWidth == 0u || desiredHeight == 0u )
115 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
118 if( inputWidth == 0u || inputHeight == 0u )
120 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
125 * @brief Do debug assertions common to all scanline halving functions.
126 * @note Inline and in anon namespace so should boil away in release builds.
128 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
130 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
131 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
132 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
136 * @brief Assertions on params to functions averaging pairs of scanlines.
137 * @note Inline as intended to boil away in release.
139 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
140 const uint8_t * const scanline2,
141 uint8_t* const outputScanline,
142 const size_t widthInComponents )
144 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
145 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
146 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
147 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
148 DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
152 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
154 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
156 BoxDimensionTest dimensionTest;
157 dimensionTest = BoxDimensionTestEither;
159 switch( fittingMode )
161 // Shrink to fit attempts to make one or zero dimensions smaller than the
162 // desired dimensions and one or two dimensions exactly the same as the desired
163 // ones, so as long as one dimension is larger than the desired size, box
164 // filtering can continue even if the second dimension is smaller than the
165 // desired dimensions:
166 case FittingMode::SHRINK_TO_FIT:
168 dimensionTest = BoxDimensionTestEither;
171 // Scale to fill mode keeps both dimensions at least as large as desired:
172 case FittingMode::SCALE_TO_FILL:
174 dimensionTest = BoxDimensionTestBoth;
177 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
178 case FittingMode::FIT_WIDTH:
180 dimensionTest = BoxDimensionTestX;
183 // X Dimension is ignored by definition in FIT_HEIGHT mode:
184 case FittingMode::FIT_HEIGHT:
186 dimensionTest = BoxDimensionTestY;
191 return dimensionTest;
195 * @brief Work out the dimensions for a uniform scaling of the input to map it
196 * into the target while effecting ShinkToFit scaling mode.
198 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
200 // Scale the input by the least extreme of the two dimensions:
201 const float widthScale = target.GetX() / float(source.GetX());
202 const float heightScale = target.GetY() / float(source.GetY());
203 const float scale = widthScale < heightScale ? widthScale : heightScale;
205 // Do no scaling at all if the result would increase area:
211 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
215 * @brief Work out the dimensions for a uniform scaling of the input to map it
216 * into the target while effecting SCALE_TO_FILL scaling mode.
217 * @note An image scaled into the output dimensions will need either top and
218 * bottom or left and right to be cropped away unless the source was pre-cropped
219 * to match the destination aspect ratio.
221 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
223 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
224 // Scale the input by the least extreme of the two dimensions:
225 const float widthScale = target.GetX() / float(source.GetX());
226 const float heightScale = target.GetY() / float(source.GetY());
227 const float scale = widthScale > heightScale ? widthScale : heightScale;
229 // Do no scaling at all if the result would increase area:
235 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
239 * @brief Work out the dimensions for a uniform scaling of the input to map it
240 * into the target while effecting FIT_WIDTH scaling mode.
242 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
244 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
245 const float scale = target.GetX() / float(source.GetX());
247 // Do no scaling at all if the result would increase area:
252 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
256 * @brief Work out the dimensions for a uniform scaling of the input to map it
257 * into the target while effecting FIT_HEIGHT scaling mode.
259 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
261 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
262 const float scale = target.GetY() / float(source.GetY());
264 // Do no scaling at all if the result would increase area:
270 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
274 * @brief Generate the rectangle to use as the target of a pixel sampling pass
275 * (e.g., nearest or linear).
277 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
279 ImageDimensions fitDimensions;
280 switch( fittingMode )
282 case FittingMode::SHRINK_TO_FIT:
284 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
287 case FittingMode::SCALE_TO_FILL:
289 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
292 case FittingMode::FIT_WIDTH:
294 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
297 case FittingMode::FIT_HEIGHT:
299 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
304 return fitDimensions;
308 * @brief Construct a bitmap with format and dimensions requested.
310 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
312 DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
314 // Allocate a pixel buffer to hold the image passed in:
315 Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
316 newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
321 * @brief Construct a bitmap object from a copy of the pixel array passed in.
323 BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
325 DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
327 // Allocate a pixel buffer to hold the image passed in:
328 Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
330 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
331 memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
336 * @brief Work out the desired width and height, accounting for zeros.
338 * @param[in] bitmapWidth Width of image before processing.
339 * @param[in] bitmapHeight Height of image before processing.
340 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
341 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
342 * @return Dimensions of area to scale image into after special rules are applied.
344 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
346 // If no dimensions have been requested, default to the source ones:
347 if( requestedWidth == 0 && requestedHeight == 0 )
349 return ImageDimensions( bitmapWidth, bitmapHeight );
352 // If both dimensions have values requested, use them both:
353 if( requestedWidth != 0 && requestedHeight != 0 )
355 return ImageDimensions( requestedWidth, requestedHeight );
358 // Only one of the dimensions has been requested. Calculate the other from
359 // the requested one and the source image aspect ratio:
360 if( requestedWidth != 0 )
362 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
364 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
367 } // namespace - unnamed
369 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
371 return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
375 * @brief Implement ScaleTofill scaling mode cropping.
377 * Implement the cropping required for SCALE_TO_FILL mode,
378 * returning a new bitmap with the aspect ratio specified by the scaling mode.
379 * This scaling mode selects the central portion of a source image so any spare
380 * pixels off one of either the top or bottom edge need to be removed.
382 * @note If the input bitmap was not previously downscaled to exactly encompass
383 * the desired output size, the resulting bitmap will have the correct aspect
384 * ratio but will have larger dimensions than requested. This can be used to
385 * fake the scaling mode by relying on the GPU scaling at render time.
386 * If the input bitmap was previously maximally downscaled using a
387 * repeated box filter, this is a reasonable approach.
389 * @return The bitmap passed in if no scaling is needed or possible, else a new,
390 * smaller bitmap with the cropping required for the scaling mode applied.
392 Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
394 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
398 // Calculate the desired box, accounting for a possible zero component:
399 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
401 // If a different size than the raw one has been requested, resize the image
402 // maximally using a repeated box filter without making it smaller than the
403 // requested size in either dimension:
404 bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode );
406 // Cut the bitmap according to the desired width and height so that the
407 // resulting bitmap has the same aspect ratio as the desired dimensions:
408 if( bitmap && bitmap->GetPackedPixelsProfile() && fittingMode == FittingMode::SCALE_TO_FILL )
410 bitmap = CropForScaleToFill( bitmap, desiredDimensions );
413 // Examine the image pixels remaining after cropping and scaling to see if all
414 // are opaque, allowing faster rendering, or some have non-1.0 alpha:
415 if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
417 bitmap->GetPackedPixelsProfile()->TestForTransparency();
424 BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
426 const unsigned inputWidth = bitmap->GetImageWidth();
427 const unsigned inputHeight = bitmap->GetImageHeight();
428 const unsigned desiredWidth = desiredDimensions.GetWidth();
429 const unsigned desiredHeight = desiredDimensions.GetHeight();
431 if( desiredWidth < 1U || desiredHeight < 1U )
433 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
435 else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
437 const Vector2 desiredDims( desiredWidth, desiredHeight );
439 // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
440 // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
441 const float widthsRatio = inputWidth / float(desiredWidth);
442 const Vector2 scaledByWidth = desiredDims * widthsRatio;
443 const float heightsRatio = inputHeight / float(desiredHeight);
444 const Vector2 scaledByHeight = desiredDims * heightsRatio;
445 // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
446 const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
447 const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
449 // Work out how many pixels to trim from top and bottom, and left and right:
450 // (We only ever do one dimension)
451 const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
452 const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
454 DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, inputWidth, inputHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
456 // Make a new bitmap with the central part of the loaded one if required:
457 if( scanlinesToTrim > 0 || columnsToTrim > 0 )
459 const unsigned newWidth = inputWidth - 2 * columnsToTrim;
460 const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
461 BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
462 Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
463 DALI_ASSERT_DEBUG( packedView );
464 const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
465 packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
467 const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
469 const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
470 PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
471 DALI_ASSERT_DEBUG( srcPixels && destPixels );
473 // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
474 if( trimTopAndBottom )
476 memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
480 for( unsigned y = 0; y < newHeight; ++y )
482 memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
486 // Overwrite the loaded bitmap with the cropped version:
487 bitmap = croppedBitmap;
494 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
495 ImageDimensions desired,
496 FittingMode::Type fittingMode,
497 SamplingMode::Type samplingMode )
499 // Source dimensions as loaded from resources (e.g. filesystem):
500 const unsigned int bitmapWidth = bitmap.GetImageWidth();
501 const unsigned int bitmapHeight = bitmap.GetImageHeight();
502 // Desired dimensions (the rectangle to fit the source image to):
503 const unsigned int desiredWidth = desired.GetWidth();
504 const unsigned int desiredHeight = desired.GetHeight();
506 BitmapPtr outputBitmap( &bitmap );
508 // If a different size than the raw one has been requested, resize the image:
509 if( bitmap.GetPackedPixelsProfile() &&
510 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
511 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
513 const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
515 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
516 unsigned int shrunkWidth = -1, shrunkHeight = -1;
517 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
519 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
520 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
521 const unsigned int filteredWidth = filteredDimensions.GetWidth();
522 const unsigned int filteredHeight = filteredDimensions.GetHeight();
524 // Run a filter to scale down the bitmap if it needs it:
525 bool filtered = false;
526 if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
528 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
529 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
531 outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
534 if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
536 LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
540 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
546 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
547 if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
549 outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
559 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
560 * @param test Which combination of the two dimensions matter for terminating the filtering.
561 * @param scaledWidth The width of the current downscaled image.
562 * @param scaledHeight The height of the current downscaled image.
563 * @param desiredWidth The target width for the downscaling.
564 * @param desiredHeight The target height for the downscaling.
566 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
568 bool keepScaling = false;
569 const unsigned int nextWidth = scaledWidth >> 1u;
570 const unsigned int nextHeight = scaledHeight >> 1u;
572 if( nextWidth >= 1u && nextHeight >= 1u )
576 case BoxDimensionTestEither:
578 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
581 case BoxDimensionTestBoth:
583 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
586 case BoxDimensionTestX:
588 keepScaling = nextWidth >= desiredWidth;
591 case BoxDimensionTestY:
593 keepScaling = nextHeight >= desiredHeight;
603 * @brief A shared implementation of the overall iterative box filter
604 * downscaling algorithm.
606 * Specialise this for particular pixel formats by supplying the number of bytes
607 * per pixel and two functions: one for averaging pairs of neighbouring pixels
608 * on a single scanline, and a second for averaging pixels at corresponding
609 * positions on different scanlines.
613 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
614 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
616 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
617 const unsigned int inputWidth,
618 const unsigned int inputHeight,
619 const unsigned int desiredWidth,
620 const unsigned int desiredHeight,
621 BoxDimensionTest dimensionTest,
623 unsigned& outHeight )
629 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
631 // Scale the image until it would be smaller than desired, stopping if the
632 // resulting height or width would be less than 1:
633 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
634 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
636 const unsigned int lastWidth = scaledWidth;
640 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
642 const unsigned int lastScanlinePair = scaledHeight - 1;
644 // Scale pairs of scanlines until any spare one at the end is dropped:
645 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
647 // Scale two scanlines horizontally:
648 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
649 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
651 // Scale vertical pairs of pixels while the last two scanlines are still warm in
653 // Note, better access patterns for cache-coherence are possible for very large
654 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
655 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
657 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
658 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
659 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
664 ///@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.
665 outWidth = scaledWidth;
666 outHeight = scaledHeight;
671 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
673 DebugAssertScanlineParameters( pixels, width );
675 const unsigned int lastPair = EvenDown( width - 2 );
677 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
679 // Load all the byte pixel components we need:
680 const unsigned int c11 = pixels[pixel * 3];
681 const unsigned int c12 = pixels[pixel * 3 + 1];
682 const unsigned int c13 = pixels[pixel * 3 + 2];
683 const unsigned int c21 = pixels[pixel * 3 + 3];
684 const unsigned int c22 = pixels[pixel * 3 + 4];
685 const unsigned int c23 = pixels[pixel * 3 + 5];
687 // Save the averaged byte pixel components:
688 pixels[outPixel * 3] = AverageComponent( c11, c21 );
689 pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
690 pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
694 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
696 DebugAssertScanlineParameters( pixels, width );
697 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
699 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
701 const unsigned int lastPair = EvenDown( width - 2 );
703 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
705 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
706 alignedPixels[outPixel] = averaged;
710 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
712 DebugAssertScanlineParameters( pixels, width );
713 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
715 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
717 const unsigned int lastPair = EvenDown( width - 2 );
719 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
721 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
722 alignedPixels[outPixel] = averaged;
726 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
728 DebugAssertScanlineParameters( pixels, width );
730 const unsigned int lastPair = EvenDown( width - 2 );
732 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
734 // Load all the byte pixel components we need:
735 const unsigned int c11 = pixels[pixel * 2];
736 const unsigned int c12 = pixels[pixel * 2 + 1];
737 const unsigned int c21 = pixels[pixel * 2 + 2];
738 const unsigned int c22 = pixels[pixel * 2 + 3];
740 // Save the averaged byte pixel components:
741 pixels[outPixel * 2] = AverageComponent( c11, c21 );
742 pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
746 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
748 DebugAssertScanlineParameters( pixels, width );
750 const unsigned int lastPair = EvenDown( width - 2 );
752 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
754 // Load all the byte pixel components we need:
755 const unsigned int c1 = pixels[pixel];
756 const unsigned int c2 = pixels[pixel + 1];
758 // Save the averaged byte pixel component:
759 pixels[outPixel] = AverageComponent( c1, c2 );
764 * @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.
765 * 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.
767 void AverageScanlines1( const unsigned char * const scanline1,
768 const unsigned char * const __restrict__ scanline2,
769 unsigned char* const outputScanline,
770 const unsigned int width )
772 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
774 for( unsigned int component = 0; component < width; ++component )
776 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
780 void AverageScanlines2( const unsigned char * const scanline1,
781 const unsigned char * const __restrict__ scanline2,
782 unsigned char* const outputScanline,
783 const unsigned int width )
785 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
787 for( unsigned int component = 0; component < width * 2; ++component )
789 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
793 void AverageScanlines3( const unsigned char * const scanline1,
794 const unsigned char * const __restrict__ scanline2,
795 unsigned char* const outputScanline,
796 const unsigned int width )
798 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
800 for( unsigned int component = 0; component < width * 3; ++component )
802 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
806 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
807 const unsigned char * const __restrict__ scanline2,
808 unsigned char * const outputScanline,
809 const unsigned int width )
811 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
812 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
813 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
814 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
816 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
817 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
818 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
820 for( unsigned int pixel = 0; pixel < width; ++pixel )
822 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
826 void AverageScanlinesRGB565( const unsigned char * const scanline1,
827 const unsigned char * const __restrict__ scanline2,
828 unsigned char * const outputScanline,
829 const unsigned int width )
831 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
832 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
833 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
834 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
836 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
837 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
838 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
840 for( unsigned int pixel = 0; pixel < width; ++pixel )
842 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
846 /// Dispatch to pixel format appropriate box filter downscaling functions.
847 void DownscaleInPlacePow2( unsigned char * const pixels,
848 Pixel::Format pixelFormat,
849 unsigned int inputWidth,
850 unsigned int inputHeight,
851 unsigned int desiredWidth,
852 unsigned int desiredHeight,
853 FittingMode::Type fittingMode,
854 SamplingMode::Type samplingMode,
856 unsigned& outHeight )
858 outWidth = inputWidth;
859 outHeight = inputHeight;
860 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
861 if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
863 // Check the pixel format is one that is supported:
864 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
866 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
868 if( pixelFormat == Pixel::RGBA8888 )
870 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
872 else if( pixelFormat == Pixel::RGB888 )
874 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
876 else if( pixelFormat == Pixel::RGB565 )
878 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
880 else if( pixelFormat == Pixel::LA88 )
882 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
884 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
886 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
890 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
896 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
900 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
901 unsigned int inputWidth,
902 unsigned int inputHeight,
903 unsigned int desiredWidth,
904 unsigned int desiredHeight,
905 BoxDimensionTest dimensionTest,
907 unsigned& outHeight )
909 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
912 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
913 unsigned int inputWidth,
914 unsigned int inputHeight,
915 unsigned int desiredWidth,
916 unsigned int desiredHeight,
917 BoxDimensionTest dimensionTest,
919 unsigned& outHeight )
921 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
922 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
925 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
926 unsigned int inputWidth,
927 unsigned int inputHeight,
928 unsigned int desiredWidth,
929 unsigned int desiredHeight,
930 BoxDimensionTest dimensionTest,
931 unsigned int& outWidth,
932 unsigned int& outHeight )
934 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
938 * @copydoc DownscaleInPlacePow2RGB888
940 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
942 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
943 unsigned int inputWidth,
944 unsigned int inputHeight,
945 unsigned int desiredWidth,
946 unsigned int desiredHeight,
947 BoxDimensionTest dimensionTest,
949 unsigned& outHeight )
951 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
954 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
955 unsigned int inputWidth,
956 unsigned int inputHeight,
957 unsigned int desiredWidth,
958 unsigned int desiredHeight,
959 BoxDimensionTest dimensionTest,
960 unsigned int& outWidth,
961 unsigned int& outHeight )
963 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
970 * @brief Point sample an image to a new resolution (like GL_NEAREST).
972 * Template is used purely as a type-safe code generator in this one
973 * compilation unit. Generated code is inlined into type-specific wrapper
974 * functions below which are exported to rest of module.
976 template<typename PIXEL>
977 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
978 unsigned int inputWidth,
979 unsigned int inputHeight,
981 unsigned int desiredWidth,
982 unsigned int desiredHeight )
984 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
985 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
986 "The input and output buffers must not overlap for an upscaling.");
987 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, ...)." );
988 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, ...)." );
990 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
994 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
995 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
996 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
997 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
999 unsigned int inY = 0;
1000 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1002 // Round fixed point y coordinate to nearest integer:
1003 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1004 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1005 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1007 DALI_ASSERT_DEBUG( integerY < inputHeight );
1008 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1009 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1011 unsigned int inX = 0;
1012 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1014 // Round the fixed-point x coordinate to an integer:
1015 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1016 const PIXEL* const inPixelAddress = &inScanline[integerX];
1017 const PIXEL pixel = *inPixelAddress;
1018 outScanline[outX] = pixel;
1028 void PointSample4BPP( const unsigned char * inPixels,
1029 unsigned int inputWidth,
1030 unsigned int inputHeight,
1031 unsigned char * outPixels,
1032 unsigned int desiredWidth,
1033 unsigned int desiredHeight )
1035 PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1039 void PointSample2BPP( const unsigned char * inPixels,
1040 unsigned int inputWidth,
1041 unsigned int inputHeight,
1042 unsigned char * outPixels,
1043 unsigned int desiredWidth,
1044 unsigned int desiredHeight )
1046 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1050 void PointSample1BPP( const unsigned char * inPixels,
1051 unsigned int inputWidth,
1052 unsigned int inputHeight,
1053 unsigned char * outPixels,
1054 unsigned int desiredWidth,
1055 unsigned int desiredHeight )
1057 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1061 * RGB888 is a special case as its pixels are not aligned addressable units.
1063 void PointSample3BPP( const uint8_t * inPixels,
1064 unsigned int inputWidth,
1065 unsigned int inputHeight,
1066 uint8_t * outPixels,
1067 unsigned int desiredWidth,
1068 unsigned int desiredHeight )
1070 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1074 const unsigned int BYTES_PER_PIXEL = 3;
1076 // Generate fixed-point 16.16 deltas in input image coordinates:
1077 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1078 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1080 // Step through output image in whole integer pixel steps while tracking the
1081 // corresponding locations in the input image using 16.16 fixed-point
1083 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1084 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1086 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1087 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1088 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1089 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1091 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1093 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1094 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1095 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1097 // Issue loads for all pixel color components up-front:
1098 const unsigned int c0 = inPixelAddress[0];
1099 const unsigned int c1 = inPixelAddress[1];
1100 const unsigned int c2 = inPixelAddress[2];
1101 ///@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.
1103 // Output the pixel components:
1104 outScanline[outX] = c0;
1105 outScanline[outX + 1] = c1;
1106 outScanline[outX + 2] = c2;
1108 // Increment the fixed-point input coordinate:
1116 // Dispatch to a format-appropriate point sampling function:
1117 void PointSample( const unsigned char * inPixels,
1118 unsigned int inputWidth,
1119 unsigned int inputHeight,
1120 Pixel::Format pixelFormat,
1121 unsigned char * outPixels,
1122 unsigned int desiredWidth,
1123 unsigned int desiredHeight )
1125 // Check the pixel format is one that is supported:
1126 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1128 if( pixelFormat == Pixel::RGB888 )
1130 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1132 else if( pixelFormat == Pixel::RGBA8888 )
1134 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1136 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1138 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1140 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1142 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1146 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1151 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1155 // Linear sampling group below
1160 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1161 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1163 return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1166 /** @copydoc BilinearFilter1BPPByte */
1167 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1170 pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1171 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1175 /** @copydoc BilinearFilter1BPPByte */
1176 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1179 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1180 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1181 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1185 /** @copydoc BilinearFilter1BPPByte */
1186 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1188 const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1189 (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1190 BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1194 /** @copydoc BilinearFilter1BPPByte */
1195 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1198 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1199 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1200 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1201 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1206 * @brief Generic version of bilinear sampling image resize function.
1207 * @note Limited to one compilation unit and exposed through type-specific
1208 * wrapper functions below.
1212 PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1213 bool DEBUG_ASSERT_ALIGNMENT
1215 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1216 ImageDimensions inputDimensions,
1217 unsigned char * __restrict__ outPixels,
1218 ImageDimensions desiredDimensions )
1220 const unsigned int inputWidth = inputDimensions.GetWidth();
1221 const unsigned int inputHeight = inputDimensions.GetHeight();
1222 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1223 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1225 DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1226 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1227 "Input and output buffers cannot overlap.");
1228 if( DEBUG_ASSERT_ALIGNMENT )
1230 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, ...)." );
1231 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, ...)." );
1234 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1238 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1239 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1240 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1241 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1243 unsigned int inY = 0;
1244 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1246 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1248 // Find the two scanlines to blend and the weight to blend with:
1249 const unsigned int integerY1 = inY >> 16u;
1250 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1251 const unsigned int inputYWeight = inY & 65535u;
1253 DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1254 DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1256 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1257 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1259 unsigned int inX = 0;
1260 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1262 // Work out the two pixel scanline offsets for this cluster of four samples:
1263 const unsigned int integerX1 = inX >> 16u;
1264 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1266 // Execute the loads:
1267 const PIXEL pixel1 = inScanline1[integerX1];
1268 const PIXEL pixel2 = inScanline2[integerX1];
1269 const PIXEL pixel3 = inScanline1[integerX2];
1270 const PIXEL pixel4 = inScanline2[integerX2];
1271 ///@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.
1273 // Weighted bilinear filter:
1274 const unsigned int inputXWeight = inX & 65535u;
1275 outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1285 // Format-specific linear scaling instantiations:
1287 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1288 ImageDimensions inputDimensions,
1289 unsigned char * __restrict__ outPixels,
1290 ImageDimensions desiredDimensions )
1292 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1295 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1296 ImageDimensions inputDimensions,
1297 unsigned char * __restrict__ outPixels,
1298 ImageDimensions desiredDimensions )
1300 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1303 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1304 ImageDimensions inputDimensions,
1305 unsigned char * __restrict__ outPixels,
1306 ImageDimensions desiredDimensions )
1308 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1311 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1312 ImageDimensions inputDimensions,
1313 unsigned char * __restrict__ outPixels,
1314 ImageDimensions desiredDimensions )
1316 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1319 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1320 ImageDimensions inputDimensions,
1321 unsigned char * __restrict__ outPixels,
1322 ImageDimensions desiredDimensions )
1324 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1327 // Dispatch to a format-appropriate linear sampling function:
1328 void LinearSample( const unsigned char * __restrict__ inPixels,
1329 ImageDimensions inDimensions,
1330 Pixel::Format pixelFormat,
1331 unsigned char * __restrict__ outPixels,
1332 ImageDimensions outDimensions )
1334 // Check the pixel format is one that is supported:
1335 if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1337 if( pixelFormat == Pixel::RGB888 )
1339 LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1341 else if( pixelFormat == Pixel::RGBA8888 )
1343 LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1345 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1347 LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1349 else if( pixelFormat == Pixel::LA88 )
1351 LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1353 else if ( pixelFormat == Pixel::RGB565 )
1355 LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1359 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1364 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1368 } /* namespace Platform */
1369 } /* namespace Internal */
1370 } /* namespace Dali */