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"
23 #include <dali/integration-api/debug.h>
37 using Integration::Bitmap;
38 using Integration::BitmapPtr;
39 typedef unsigned char PixelBuffer;
42 * @brief 4 byte pixel structure.
50 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
53 * @brief RGB888 pixel structure.
60 } __attribute__((packed, aligned(1)));
63 * @brief RGB565 pixel typedefed from a short.
65 * Access fields by manual shifting and masking.
67 typedef uint16_t PixelRGB565;
70 * @brief a Pixel composed of two independent byte components.
76 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
79 #if defined(DEBUG_ENABLED)
81 * Disable logging of image operations or make it verbose from the commandline
82 * as follows (e.g., for dali demo app):
84 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
85 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
88 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
91 /** @return The greatest even number less than or equal to the argument. */
92 inline unsigned int EvenDown( const unsigned int a )
94 const unsigned int evened = a & ~1u;
99 * @brief Log bad parameters.
101 void ValidateScalingParameters( const unsigned int inputWidth,
102 const unsigned int inputHeight,
103 const unsigned int desiredWidth,
104 const unsigned int desiredHeight )
106 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
108 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
111 if( desiredWidth == 0u || desiredHeight == 0u )
113 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
116 if( inputWidth == 0u || inputHeight == 0u )
118 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
123 * @brief Do debug assertions common to all scanline halving functions.
124 * @note Inline and in anon namespace so should boil away in release builds.
126 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
128 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
129 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
130 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
134 * @brief Assertions on params to functions averaging pairs of scanlines.
135 * @note Inline as intended to boil away in release.
137 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
138 const uint8_t * const scanline2,
139 uint8_t* const outputScanline,
140 const size_t widthInComponents )
142 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
143 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
144 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
145 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
146 DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
150 * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class.
152 * @param[in] bitmapWidth Width of image before processing.
153 * @param[in] bitmapHeight Height of image before processing.
154 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
155 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
156 * @return Dimensions of area to scale image into after special rules are applied.
157 * @see ImageAttributes
159 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
161 // If no dimensions have been requested, default to the source ones:
162 if( requestedWidth == 0 && requestedHeight == 0 )
164 return ImageDimensions( bitmapWidth, bitmapHeight );
167 // If both dimensions have values requested, use them both:
168 if( requestedWidth != 0 && requestedHeight != 0 )
170 return ImageDimensions( requestedWidth, requestedHeight );
173 // If only one of the dimensions has been requested, calculate the other from
174 // the requested one and the source image aspect ratio:
176 if( requestedWidth != 0 )
178 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
180 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
184 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
186 BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
188 BoxDimensionTest dimensionTest;
189 dimensionTest = BoxDimensionTestEither;
191 switch( scalingMode )
193 // Shrink to fit attempts to make one or zero dimensions smaller than the
194 // desired dimensions and one or two dimensions exactly the same as the desired
195 // ones, so as long as one dimension is larger than the desired size, box
196 // filtering can continue even if the second dimension is smaller than the
197 // desired dimensions:
198 case ImageAttributes::ShrinkToFit:
200 dimensionTest = BoxDimensionTestEither;
203 // Scale to fill mode keeps both dimensions at least as large as desired:
204 case ImageAttributes::ScaleToFill:
206 dimensionTest = BoxDimensionTestBoth;
209 // Y dimension is irrelevant when downscaling in FitWidth mode:
210 case ImageAttributes::FitWidth:
212 dimensionTest = BoxDimensionTestX;
215 // X Dimension is ignored by definition in FitHeight mode:
216 case ImageAttributes::FitHeight:
218 dimensionTest = BoxDimensionTestY;
223 return dimensionTest;
227 * @brief Work out the dimensions for a uniform scaling of the input to map it
228 * into the target while effecting ShinkToFit scaling mode.
230 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
232 DALI_ASSERT_DEBUG( true && " " );
233 // Scale the input by the least extreme of the two dimensions:
234 const float widthScale = target.GetX() / float(source.GetX());
235 const float heightScale = target.GetY() / float(source.GetY());
236 const float scale = widthScale < heightScale ? widthScale : heightScale;
238 // Do no scaling at all if the result would increase area:
244 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
248 * @brief Work out the dimensions for a uniform scaling of the input to map it
249 * into the target while effecting ScaleToFill scaling mode.
250 * @note The output dimensions will need either top and bottom or left and right
251 * to be cropped away unless the source was pre-cropped to match the destination
254 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
256 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
257 // Scale the input by the least extreme of the two dimensions:
258 const float widthScale = target.GetX() / float(source.GetX());
259 const float heightScale = target.GetY() / float(source.GetY());
260 const float scale = widthScale > heightScale ? widthScale : heightScale;
262 // Do no scaling at all if the result would increase area:
268 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
272 * @brief Work out the dimensions for a uniform scaling of the input to map it
273 * into the target while effecting FitWidth scaling mode.
275 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
277 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
278 const float scale = target.GetX() / float(source.GetX());
280 // 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 Work out the dimensions for a uniform scaling of the input to map it
290 * into the target while effecting FitHeight scaling mode.
292 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
294 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
295 const float scale = target.GetY() / float(source.GetY());
297 // Do no scaling at all if the result would increase area:
303 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
307 * @brief Generate the rectangle to use as the target of a pixel sampling pass
308 * (e.g., nearest or linear).
310 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, ImageAttributes::ScalingMode scalingMode )
312 ImageDimensions fitDimensions;
313 switch( scalingMode )
315 case ImageAttributes::ShrinkToFit:
317 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
320 case ImageAttributes::ScaleToFill:
322 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
325 case ImageAttributes::FitWidth:
327 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
330 case ImageAttributes::FitHeight:
332 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
337 return fitDimensions;
341 * @brief Construct a bitmap with format and dimensions requested.
343 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
345 DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
347 // Allocate a pixel buffer to hold the image passed in:
348 Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
349 newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
354 * @brief Construct a bitmap object from a copy of the pixel array passed in.
356 BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
358 DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
360 // Allocate a pixel buffer to hold the image passed in:
361 Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
363 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
364 memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
368 } // namespace - unnamed
371 * @brief Implement ImageAttributes::ScaleTofill scaling mode cropping.
373 * Implement the cropping required for ImageAttributes::ScaleToFill mode,
374 * returning a new bitmap with the aspect ratio specified by the scaling mode.
375 * This scaling mode selects the central portion of a source image so any spare
376 * pixels off one of either the top or bottom edge need to be removed.
378 * @note If the input bitmap was not previously downscaled to exactly encompass
379 * the desired output size, the resulting bitmap will have the correct aspect
380 * ratio but will have larger dimensions than requested. This can be used to
381 * fake the scaling mode by relying on the GPU scaling at render time.
382 * If the input bitmap was previously maximally downscaled using a
383 * repeated box filter, this is a reasonable approach.
385 * @return The bitmap passed in if no scaling is needed or possible, else a new,
386 * smaller bitmap with the cropping required for the scaling mode applied.
388 Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
390 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
392 ///@ToDo: Optimisation - If Scaling Mode is ScaletoFill, either do cropping here at the front of the pipe, or equivalently modify all scaling functions to take a source rectangle and have the first one to be applied, pull in a subset of source pixels to crop on the fly. That would make every scaling slightly slower but would save the memcpy()s at the end for ScaleToFill.
396 // Calculate the desired box, accounting for a possible zero component:
397 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), requestedAttributes.GetWidth(), requestedAttributes.GetHeight() );
399 // If a different size than the raw one has been requested, resize the image
400 // maximally using a repeated box filter without making it smaller than the
401 // requested size in either dimension:
402 bitmap = DownscaleBitmap( *bitmap, desiredDimensions, requestedAttributes.GetScalingMode(), requestedAttributes.GetFilterMode() );
404 // Cut the bitmap according to the desired width and height so that the
405 // resulting bitmap has the same aspect ratio as the desired dimensions:
406 if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
408 bitmap = CropForScaleToFill( bitmap, desiredDimensions );
411 // Examine the image pixels remaining after cropping and scaling to see if all
412 // are opaque, allowing faster rendering, or some have non-1.0 alpha:
413 if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
415 bitmap->GetPackedPixelsProfile()->TestForTransparency();
422 BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
424 const unsigned inputWidth = bitmap->GetImageWidth();
425 const unsigned inputHeight = bitmap->GetImageHeight();
426 const unsigned desiredWidth = desiredDimensions.GetWidth();
427 const unsigned desiredHeight = desiredDimensions.GetHeight();
429 if( desiredWidth < 1U || desiredHeight < 1U )
431 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
433 else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
435 const Vector2 desiredDims( desiredWidth, desiredHeight );
437 // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
438 // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
439 const float widthsRatio = inputWidth / float(desiredWidth);
440 const Vector2 scaledByWidth = desiredDims * widthsRatio;
441 const float heightsRatio = inputHeight / float(desiredHeight);
442 const Vector2 scaledByHeight = desiredDims * heightsRatio;
443 // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
444 const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
445 const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
447 // Work out how many pixels to trim from top and bottom, and left and right:
448 // (We only ever do one dimension)
449 const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
450 const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
452 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" );
454 // Make a new bitmap with the central part of the loaded one if required:
455 if( scanlinesToTrim > 0 || columnsToTrim > 0 )
457 const unsigned newWidth = inputWidth - 2 * columnsToTrim;
458 const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
459 BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
460 Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
461 DALI_ASSERT_DEBUG( packedView );
462 const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
463 packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
465 const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
467 const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
468 PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
469 DALI_ASSERT_DEBUG( srcPixels && destPixels );
471 // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
472 if( trimTopAndBottom )
474 memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
478 for( unsigned y = 0; y < newHeight; ++y )
480 memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
484 // Overwrite the loaded bitmap with the cropped version:
485 bitmap = croppedBitmap;
492 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
493 ImageDimensions desired,
494 ImageAttributes::ScalingMode scalingMode,
495 ImageAttributes::FilterMode filterMode )
497 // Source dimensions as loaded from resources (e.g. filesystem):
498 const unsigned int bitmapWidth = bitmap.GetImageWidth();
499 const unsigned int bitmapHeight = bitmap.GetImageHeight();
500 // Desired dimensions (the rectangle to fit the source image to):
501 const unsigned int desiredWidth = desired.GetWidth();
502 const unsigned int desiredHeight = desired.GetHeight();
504 BitmapPtr outputBitmap( &bitmap );
506 // If a different size than the raw one has been requested, resize the image:
507 if( bitmap.GetPackedPixelsProfile() &&
508 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
509 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
511 const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
513 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
514 unsigned int shrunkWidth = -1, shrunkHeight = -1;
515 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, scalingMode, filterMode, shrunkWidth, shrunkHeight );
517 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
518 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), scalingMode );
519 const unsigned int filteredWidth = filteredDimensions.GetWidth();
520 const unsigned int filteredHeight = filteredDimensions.GetHeight();
522 // Run a filter to scale down the bitmap if it needs it:
523 bool filtered = false;
524 if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
526 if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear ||
527 filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::BoxThenNearest )
529 outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
532 if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear )
534 LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
538 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
544 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
545 if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
547 outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
557 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
558 * @param test Which combination of the two dimensions matter for terminating the filtering.
559 * @param scaledWidth The width of the current downscaled image.
560 * @param scaledHeight The height of the current downscaled image.
561 * @param desiredWidth The target width for the downscaling.
562 * @param desiredHeight The target height for the downscaling.
564 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
566 bool keepScaling = false;
567 const unsigned int nextWidth = scaledWidth >> 1u;
568 const unsigned int nextHeight = scaledHeight >> 1u;
570 if( nextWidth >= 1u && nextHeight >= 1u )
574 case BoxDimensionTestEither:
576 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
579 case BoxDimensionTestBoth:
581 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
584 case BoxDimensionTestX:
586 keepScaling = nextWidth >= desiredWidth;
589 case BoxDimensionTestY:
591 keepScaling = nextHeight >= desiredHeight;
601 * @brief A shared implementation of the overall iterative box filter
602 * downscaling algorithm.
604 * Specialise this for particular pixel formats by supplying the number of bytes
605 * per pixel and two functions: one for averaging pairs of neighbouring pixels
606 * on a single scanline, and a second for averaging pixels at corresponding
607 * positions on different scanlines.
611 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
612 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
614 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
615 const unsigned int inputWidth,
616 const unsigned int inputHeight,
617 const unsigned int desiredWidth,
618 const unsigned int desiredHeight,
619 BoxDimensionTest dimensionTest,
621 unsigned& outHeight )
627 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
629 // Scale the image until it would be smaller than desired, stopping if the
630 // resulting height or width would be less than 1:
631 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
632 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
634 const unsigned int lastWidth = scaledWidth;
638 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
640 const unsigned int lastScanlinePair = scaledHeight - 1;
642 // Scale pairs of scanlines until any spare one at the end is dropped:
643 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
645 // Scale two scanlines horizontally:
646 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
647 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
649 // Scale vertical pairs of pixels while the last two scanlines are still warm in
651 // Note, better access patterns for cache-coherence are possible for very large
652 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
653 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
655 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
656 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
657 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
662 ///@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.
663 outWidth = scaledWidth;
664 outHeight = scaledHeight;
669 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
671 DebugAssertScanlineParameters( pixels, width );
673 const unsigned int lastPair = EvenDown( width - 2 );
675 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
677 // Load all the byte pixel components we need:
678 const unsigned int c11 = pixels[pixel * 3];
679 const unsigned int c12 = pixels[pixel * 3 + 1];
680 const unsigned int c13 = pixels[pixel * 3 + 2];
681 const unsigned int c21 = pixels[pixel * 3 + 3];
682 const unsigned int c22 = pixels[pixel * 3 + 4];
683 const unsigned int c23 = pixels[pixel * 3 + 5];
685 // Save the averaged byte pixel components:
686 pixels[outPixel * 3] = AverageComponent( c11, c21 );
687 pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
688 pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
692 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
694 DebugAssertScanlineParameters( pixels, width );
695 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
697 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
699 const unsigned int lastPair = EvenDown( width - 2 );
701 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
703 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
704 alignedPixels[outPixel] = averaged;
708 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
710 DebugAssertScanlineParameters( pixels, width );
711 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
713 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
715 const unsigned int lastPair = EvenDown( width - 2 );
717 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
719 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
720 alignedPixels[outPixel] = averaged;
724 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
726 DebugAssertScanlineParameters( pixels, width );
728 const unsigned int lastPair = EvenDown( width - 2 );
730 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
732 // Load all the byte pixel components we need:
733 const unsigned int c11 = pixels[pixel * 2];
734 const unsigned int c12 = pixels[pixel * 2 + 1];
735 const unsigned int c21 = pixels[pixel * 2 + 2];
736 const unsigned int c22 = pixels[pixel * 2 + 3];
738 // Save the averaged byte pixel components:
739 pixels[outPixel * 2] = AverageComponent( c11, c21 );
740 pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
744 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
746 DebugAssertScanlineParameters( pixels, width );
748 const unsigned int lastPair = EvenDown( width - 2 );
750 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
752 // Load all the byte pixel components we need:
753 const unsigned int c1 = pixels[pixel];
754 const unsigned int c2 = pixels[pixel + 1];
756 // Save the averaged byte pixel component:
757 pixels[outPixel] = AverageComponent( c1, c2 );
762 * @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.
763 * 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.
765 void AverageScanlines1( const unsigned char * const scanline1,
766 const unsigned char * const __restrict__ scanline2,
767 unsigned char* const outputScanline,
768 const unsigned int width )
770 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
772 for( unsigned int component = 0; component < width; ++component )
774 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
778 void AverageScanlines2( const unsigned char * const scanline1,
779 const unsigned char * const __restrict__ scanline2,
780 unsigned char* const outputScanline,
781 const unsigned int width )
783 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
785 for( unsigned int component = 0; component < width * 2; ++component )
787 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
791 void AverageScanlines3( const unsigned char * const scanline1,
792 const unsigned char * const __restrict__ scanline2,
793 unsigned char* const outputScanline,
794 const unsigned int width )
796 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
798 for( unsigned int component = 0; component < width * 3; ++component )
800 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
804 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
805 const unsigned char * const __restrict__ scanline2,
806 unsigned char * const outputScanline,
807 const unsigned int width )
809 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
810 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
811 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
812 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
814 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
815 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
816 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
818 for( unsigned int pixel = 0; pixel < width; ++pixel )
820 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
824 void AverageScanlinesRGB565( const unsigned char * const scanline1,
825 const unsigned char * const __restrict__ scanline2,
826 unsigned char * const outputScanline,
827 const unsigned int width )
829 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
830 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
831 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
832 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
834 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
835 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
836 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
838 for( unsigned int pixel = 0; pixel < width; ++pixel )
840 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
844 /// Dispatch to pixel format appropriate box filter downscaling functions.
845 void DownscaleInPlacePow2( unsigned char * const pixels,
846 Pixel::Format pixelFormat,
847 unsigned int inputWidth,
848 unsigned int inputHeight,
849 unsigned int desiredWidth,
850 unsigned int desiredHeight,
851 ImageAttributes::ScalingMode scalingMode,
852 ImageAttributes::FilterMode filterMode,
854 unsigned& outHeight )
856 outWidth = inputWidth;
857 outHeight = inputHeight;
858 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
859 if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
861 // Check the pixel format is one that is supported:
862 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
864 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
866 if( pixelFormat == Pixel::RGBA8888 )
868 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
870 else if( pixelFormat == Pixel::RGB888 )
872 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
874 else if( pixelFormat == Pixel::RGB565 )
876 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
878 else if( pixelFormat == Pixel::LA88 )
880 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
882 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
884 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
888 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
894 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
898 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
899 unsigned int inputWidth,
900 unsigned int inputHeight,
901 unsigned int desiredWidth,
902 unsigned int desiredHeight,
903 BoxDimensionTest dimensionTest,
905 unsigned& outHeight )
907 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
910 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
911 unsigned int inputWidth,
912 unsigned int inputHeight,
913 unsigned int desiredWidth,
914 unsigned int desiredHeight,
915 BoxDimensionTest dimensionTest,
917 unsigned& outHeight )
919 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
920 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
923 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
924 unsigned int inputWidth,
925 unsigned int inputHeight,
926 unsigned int desiredWidth,
927 unsigned int desiredHeight,
928 BoxDimensionTest dimensionTest,
929 unsigned int& outWidth,
930 unsigned int& outHeight )
932 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
936 * @copydoc DownscaleInPlacePow2RGB888
938 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
940 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
941 unsigned int inputWidth,
942 unsigned int inputHeight,
943 unsigned int desiredWidth,
944 unsigned int desiredHeight,
945 BoxDimensionTest dimensionTest,
947 unsigned& outHeight )
949 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
952 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
953 unsigned int inputWidth,
954 unsigned int inputHeight,
955 unsigned int desiredWidth,
956 unsigned int desiredHeight,
957 BoxDimensionTest dimensionTest,
958 unsigned int& outWidth,
959 unsigned int& outHeight )
961 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
968 * @brief Point sample an image to a new resolution (like GL_NEAREST).
970 * Template is used purely as a type-safe code generator in this one
971 * compilation unit. Generated code is inlined into type-specific wrapper
972 * functions below which are exported to rest of module.
974 template<typename PIXEL>
975 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
976 unsigned int inputWidth,
977 unsigned int inputHeight,
979 unsigned int desiredWidth,
980 unsigned int desiredHeight )
982 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
983 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
984 "The input and output buffers must not overlap for an upscaling.");
985 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, ...)." );
986 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, ...)." );
988 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
992 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
993 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
994 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
995 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
997 unsigned int inY = 0;
998 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1000 // Round fixed point y coordinate to nearest integer:
1001 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1002 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1003 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1005 DALI_ASSERT_DEBUG( integerY < inputHeight );
1006 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1007 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1009 unsigned int inX = 0;
1010 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1012 // Round the fixed-point x coordinate to an integer:
1013 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1014 const PIXEL* const inPixelAddress = &inScanline[integerX];
1015 const PIXEL pixel = *inPixelAddress;
1016 outScanline[outX] = pixel;
1026 void PointSample4BPP( const unsigned char * inPixels,
1027 unsigned int inputWidth,
1028 unsigned int inputHeight,
1029 unsigned char * outPixels,
1030 unsigned int desiredWidth,
1031 unsigned int desiredHeight )
1033 PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1037 void PointSample2BPP( const unsigned char * inPixels,
1038 unsigned int inputWidth,
1039 unsigned int inputHeight,
1040 unsigned char * outPixels,
1041 unsigned int desiredWidth,
1042 unsigned int desiredHeight )
1044 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1048 void PointSample1BPP( const unsigned char * inPixels,
1049 unsigned int inputWidth,
1050 unsigned int inputHeight,
1051 unsigned char * outPixels,
1052 unsigned int desiredWidth,
1053 unsigned int desiredHeight )
1055 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1059 * RGB888 is a special case as its pixels are not aligned addressable units.
1061 void PointSample3BPP( const uint8_t * inPixels,
1062 unsigned int inputWidth,
1063 unsigned int inputHeight,
1064 uint8_t * outPixels,
1065 unsigned int desiredWidth,
1066 unsigned int desiredHeight )
1068 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1072 const unsigned int BYTES_PER_PIXEL = 3;
1074 // Generate fixed-point 16.16 deltas in input image coordinates:
1075 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1076 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1078 // Step through output image in whole integer pixel steps while tracking the
1079 // corresponding locations in the input image using 16.16 fixed-point
1081 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1082 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1084 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1085 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1086 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1087 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1089 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1091 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1092 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1093 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1095 // Issue loads for all pixel color components up-front:
1096 const unsigned int c0 = inPixelAddress[0];
1097 const unsigned int c1 = inPixelAddress[1];
1098 const unsigned int c2 = inPixelAddress[2];
1099 ///@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.
1101 // Output the pixel components:
1102 outScanline[outX] = c0;
1103 outScanline[outX + 1] = c1;
1104 outScanline[outX + 2] = c2;
1106 // Increment the fixed-point input coordinate:
1114 // Dispatch to a format-appropriate point sampling function:
1115 void PointSample( const unsigned char * inPixels,
1116 unsigned int inputWidth,
1117 unsigned int inputHeight,
1118 Pixel::Format pixelFormat,
1119 unsigned char * outPixels,
1120 unsigned int desiredWidth,
1121 unsigned int desiredHeight )
1123 // Check the pixel format is one that is supported:
1124 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1126 if( pixelFormat == Pixel::RGB888 )
1128 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1130 else if( pixelFormat == Pixel::RGBA8888 )
1132 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1134 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1136 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1138 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1140 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1144 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1149 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1153 // Linear sampling group below
1158 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1159 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1161 return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1164 /** @copydoc BilinearFilter1BPPByte */
1165 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1168 pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1169 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1173 /** @copydoc BilinearFilter1BPPByte */
1174 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1177 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1178 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1179 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1183 /** @copydoc BilinearFilter1BPPByte */
1184 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1186 const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1187 (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1188 BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1192 /** @copydoc BilinearFilter1BPPByte */
1193 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1196 pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1197 pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1198 pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1199 pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1204 * @brief Generic version of bilinear sampling image resize function.
1205 * @note Limited to one compilation unit and exposed through type-specific
1206 * wrapper functions below.
1210 PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1211 bool DEBUG_ASSERT_ALIGNMENT
1213 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1214 ImageDimensions inputDimensions,
1215 unsigned char * __restrict__ outPixels,
1216 ImageDimensions desiredDimensions )
1218 const unsigned int inputWidth = inputDimensions.GetWidth();
1219 const unsigned int inputHeight = inputDimensions.GetHeight();
1220 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1221 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1223 DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1224 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1225 "Input and output buffers cannot overlap.");
1226 if( DEBUG_ASSERT_ALIGNMENT )
1228 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, ...)." );
1229 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, ...)." );
1232 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1236 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1237 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1238 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1239 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1241 unsigned int inY = 0;
1242 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1244 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1246 // Find the two scanlines to blend and the weight to blend with:
1247 const unsigned int integerY1 = inY >> 16u;
1248 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1249 const unsigned int inputYWeight = inY & 65535u;
1251 DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1252 DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1254 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1255 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1257 unsigned int inX = 0;
1258 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1260 // Work out the two pixel scanline offsets for this cluster of four samples:
1261 const unsigned int integerX1 = inX >> 16u;
1262 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1264 // Execute the loads:
1265 const PIXEL pixel1 = inScanline1[integerX1];
1266 const PIXEL pixel2 = inScanline2[integerX1];
1267 const PIXEL pixel3 = inScanline1[integerX2];
1268 const PIXEL pixel4 = inScanline2[integerX2];
1269 ///@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.
1271 // Weighted bilinear filter:
1272 const unsigned int inputXWeight = inX & 65535u;
1273 outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1283 // Format-specific linear scaling instantiations:
1285 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1286 ImageDimensions inputDimensions,
1287 unsigned char * __restrict__ outPixels,
1288 ImageDimensions desiredDimensions )
1290 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1293 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1294 ImageDimensions inputDimensions,
1295 unsigned char * __restrict__ outPixels,
1296 ImageDimensions desiredDimensions )
1298 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1301 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1302 ImageDimensions inputDimensions,
1303 unsigned char * __restrict__ outPixels,
1304 ImageDimensions desiredDimensions )
1306 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1309 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1310 ImageDimensions inputDimensions,
1311 unsigned char * __restrict__ outPixels,
1312 ImageDimensions desiredDimensions )
1314 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1317 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1318 ImageDimensions inputDimensions,
1319 unsigned char * __restrict__ outPixels,
1320 ImageDimensions desiredDimensions )
1322 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1325 // Dispatch to a format-appropriate linear sampling function:
1326 void LinearSample( const unsigned char * __restrict__ inPixels,
1327 ImageDimensions inDimensions,
1328 Pixel::Format pixelFormat,
1329 unsigned char * __restrict__ outPixels,
1330 ImageDimensions outDimensions )
1332 // Check the pixel format is one that is supported:
1333 if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1335 if( pixelFormat == Pixel::RGB888 )
1337 LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1339 else if( pixelFormat == Pixel::RGBA8888 )
1341 LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1343 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1345 LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1347 else if( pixelFormat == Pixel::LA88 )
1349 LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1351 else if ( pixelFormat == Pixel::RGB565 )
1353 LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1357 DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1362 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1366 } /* namespace Platform */
1367 } /* namespace Internal */
1368 } /* namespace Dali */