Improved control of downscaling inside JPEG Loader
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / portable / image-operations.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 #include "image-operations.h"
19
20 // EXTERNAL INCLUDES
21 #include <cstring>
22 #include <stddef.h>
23 #include <cmath>
24 #include <dali/integration-api/debug.h>
25 #include <dali/public-api/math/vector2.h>
26
27 // INTERNAL INCLUDES
28
29 namespace Dali
30 {
31 namespace Internal
32 {
33 namespace Platform
34 {
35
36 namespace
37 {
38
39 using Integration::Bitmap;
40 using Integration::BitmapPtr;
41 typedef unsigned char PixelBuffer;
42
43 /**
44  * @brief 4 byte pixel structure.
45  */
46 struct Pixel4Bytes
47 {
48   uint8_t r;
49   uint8_t g;
50   uint8_t b;
51   uint8_t a;
52 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
53
54 /**
55  * @brief RGB888 pixel structure.
56  */
57 struct Pixel3Bytes
58 {
59   uint8_t r;
60   uint8_t g;
61   uint8_t b;
62 } __attribute__((packed, aligned(1)));
63
64 /**
65  * @brief RGB565 pixel typedefed from a short.
66  *
67  * Access fields by manual shifting and masking.
68  */
69 typedef uint16_t PixelRGB565;
70
71 /**
72  * @brief a Pixel composed of two independent byte components.
73  */
74 struct Pixel2Bytes
75 {
76   uint8_t l;
77   uint8_t a;
78 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
79
80
81 #if defined(DEBUG_ENABLED)
82 /**
83  * Disable logging of image operations or make it verbose from the commandline
84  * as follows (e.g., for dali demo app):
85  * <code>
86  * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
87  * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
88  * </code>
89  */
90 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
91 #endif
92
93 /** @return The greatest even number less than or equal to the argument. */
94 inline unsigned int EvenDown( const unsigned int a )
95 {
96   const unsigned int evened = a & ~1u;
97   return evened;
98 }
99
100 /**
101  * @brief Log bad parameters.
102  */
103 void ValidateScalingParameters( const unsigned int inputWidth,
104                                 const unsigned int inputHeight,
105                                 const unsigned int desiredWidth,
106                                 const unsigned int desiredHeight )
107 {
108   if( desiredWidth > inputWidth || desiredHeight > inputHeight )
109   {
110     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
111   }
112
113   if( desiredWidth == 0u || desiredHeight == 0u )
114   {
115     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
116   }
117
118   if( inputWidth == 0u || inputHeight == 0u )
119   {
120     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
121   }
122 }
123
124 /**
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.
127  */
128 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
129 {
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?" );
133 }
134
135 /**
136  * @brief Assertions on params to functions averaging pairs of scanlines.
137  * @note Inline as intended to boil away in release.
138  */
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 )
143 {
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." );
149 }
150
151 /**
152  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
153  */
154 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
155 {
156   BoxDimensionTest dimensionTest;
157   dimensionTest = BoxDimensionTestEither;
158
159   switch( fittingMode )
160   {
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:
167     {
168       dimensionTest = BoxDimensionTestEither;
169       break;
170     }
171     // Scale to fill mode keeps both dimensions at least as large as desired:
172     case FittingMode::SCALE_TO_FILL:
173     {
174       dimensionTest = BoxDimensionTestBoth;
175       break;
176     }
177     // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
178     case FittingMode::FIT_WIDTH:
179     {
180       dimensionTest = BoxDimensionTestX;
181       break;
182     }
183     // X Dimension is ignored by definition in FIT_HEIGHT mode:
184     case FittingMode::FIT_HEIGHT:
185     {
186       dimensionTest = BoxDimensionTestY;
187       break;
188     }
189   }
190
191   return dimensionTest;
192 }
193
194 /**
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.
197  */
198 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
199 {
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;
204
205   // Do no scaling at all if the result would increase area:
206   if( scale >= 1.0f )
207   {
208     return source;
209   }
210
211   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
212 }
213
214 /**
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.
220  */
221 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
222 {
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;
228
229   // Do no scaling at all if the result would increase area:
230   if( scale >= 1.0f )
231   {
232     return source;
233   }
234
235   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
236 }
237
238 /**
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.
241  */
242 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
243 {
244   DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
245   const float scale  = target.GetX() / float(source.GetX());
246
247   // Do no scaling at all if the result would increase area:
248   if( scale >= 1.0f )
249   {
250    return source;
251   }
252   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
253 }
254
255 /**
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.
258  */
259 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
260 {
261   DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
262   const float scale = target.GetY() / float(source.GetY());
263
264   // Do no scaling at all if the result would increase area:
265   if( scale >= 1.0f )
266   {
267     return source;
268   }
269
270   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
271 }
272
273 /**
274  * @brief Generate the rectangle to use as the target of a pixel sampling pass
275  * (e.g., nearest or linear).
276  */
277 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
278 {
279   ImageDimensions fitDimensions;
280   switch( fittingMode )
281   {
282     case FittingMode::SHRINK_TO_FIT:
283     {
284       fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
285       break;
286     }
287     case FittingMode::SCALE_TO_FILL:
288     {
289       fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
290       break;
291     }
292     case FittingMode::FIT_WIDTH:
293     {
294       fitDimensions = FitForFitWidth( requestedSize, sourceSize );
295       break;
296     }
297     case FittingMode::FIT_HEIGHT:
298     {
299       fitDimensions = FitForFitHeight( requestedSize, sourceSize );
300       break;
301     }
302   };
303
304   return fitDimensions;
305 }
306
307 /**
308  * @brief Construct a bitmap with format and dimensions requested.
309  */
310 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
311 {
312   DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
313
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 );
317   return newBitmap;
318 }
319
320 /**
321  * @brief Construct a bitmap object from a copy of the pixel array passed in.
322  */
323 BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
324 {
325   DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
326
327   // Allocate a pixel buffer to hold the image passed in:
328   Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
329
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 ) );
332   return newBitmap;
333 }
334
335 /**
336  * @brief Work out the desired width and height, accounting for zeros.
337  *
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.
343  */
344 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
345 {
346   // If no dimensions have been requested, default to the source ones:
347   if( requestedWidth == 0 && requestedHeight == 0 )
348   {
349     return ImageDimensions( bitmapWidth, bitmapHeight );
350   }
351
352   // If both dimensions have values requested, use them both:
353   if( requestedWidth != 0 && requestedHeight != 0 )
354   {
355     return ImageDimensions( requestedWidth, requestedHeight );
356   }
357
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 )
361   {
362     return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
363   }
364   return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
365 }
366
367 } // namespace - unnamed
368
369 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
370 {
371   return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
372 }
373
374 /**
375  * @brief Implement ScaleTofill scaling mode cropping.
376  *
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.
381  *
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.
388  *
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.
391  */
392 Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
393
394 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
395 {
396   if( bitmap )
397   {
398     // Calculate the desired box, accounting for a possible zero component:
399     const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
400
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 );
405
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 )
409     {
410       bitmap = CropForScaleToFill( bitmap, desiredDimensions );
411     }
412
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() ) )
416     {
417       bitmap->GetPackedPixelsProfile()->TestForTransparency();
418     }
419   }
420
421   return bitmap;
422 }
423
424 BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
425 {
426   const unsigned inputWidth = bitmap->GetImageWidth();
427   const unsigned inputHeight = bitmap->GetImageHeight();
428   const unsigned desiredWidth = desiredDimensions.GetWidth();
429   const unsigned desiredHeight = desiredDimensions.GetHeight();
430
431   if( desiredWidth < 1U || desiredHeight < 1U )
432   {
433     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
434   }
435   else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
436   {
437     const Vector2 desiredDims( desiredWidth, desiredHeight );
438
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;
448
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 );
453
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" );
455
456     // Make a new bitmap with the central part of the loaded one if required:
457     if( scanlinesToTrim > 0 || columnsToTrim > 0 )
458     {
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 );
466
467       const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
468
469       const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
470       PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
471       DALI_ASSERT_DEBUG( srcPixels && destPixels );
472
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 )
475       {
476         memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
477       }
478       else
479       {
480         for( unsigned y = 0; y < newHeight; ++y )
481         {
482           memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
483         }
484       }
485
486       // Overwrite the loaded bitmap with the cropped version:
487       bitmap = croppedBitmap;
488     }
489   }
490
491   return bitmap;
492 }
493
494 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
495                                         ImageDimensions desired,
496                                         FittingMode::Type fittingMode,
497                                         SamplingMode::Type samplingMode )
498 {
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();
505
506   BitmapPtr outputBitmap( &bitmap );
507
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)) )
512   {
513     const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
514
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 );
518
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();
523
524     // Run a filter to scale down the bitmap if it needs it:
525     bool filtered = false;
526     if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
527     {
528       if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
529           samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
530       {
531         outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
532         if( outputBitmap )
533         {
534           if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
535           {
536             LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
537           }
538           else
539           {
540             PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
541           }
542           filtered = true;
543         }
544       }
545     }
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 ) )
548     {
549       outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
550     }
551   }
552
553   return outputBitmap;
554 }
555
556 namespace
557 {
558 /**
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.
565  */
566 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
567 {
568   bool keepScaling = false;
569   const unsigned int nextWidth = scaledWidth >> 1u;
570   const unsigned int nextHeight = scaledHeight >> 1u;
571
572   if( nextWidth >= 1u && nextHeight >= 1u )
573   {
574     switch( test )
575     {
576       case BoxDimensionTestEither:
577       {
578         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
579         break;
580       }
581       case BoxDimensionTestBoth:
582       {
583         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
584         break;
585       }
586       case BoxDimensionTestX:
587       {
588         keepScaling = nextWidth >= desiredWidth;
589         break;
590       }
591       case BoxDimensionTestY:
592       {
593         keepScaling = nextHeight >= desiredHeight;
594         break;
595       }
596     }
597   }
598
599   return keepScaling;
600 }
601
602 /**
603  * @brief A shared implementation of the overall iterative box filter
604  * downscaling algorithm.
605  *
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.
610  **/
611 template<
612   int BYTES_PER_PIXEL,
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 )
615 >
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,
622                                   unsigned& outWidth,
623                                   unsigned& outHeight )
624 {
625   if( pixels == 0 )
626   {
627     return;
628   }
629   ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
630
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 ) )
635   {
636     const unsigned int lastWidth = scaledWidth;
637     scaledWidth  >>= 1u;
638     scaledHeight >>= 1u;
639
640     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
641
642     const unsigned int lastScanlinePair = scaledHeight - 1;
643
644     // Scale pairs of scanlines until any spare one at the end is dropped:
645     for( unsigned int y = 0; y <= lastScanlinePair; ++y )
646     {
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 );
650
651       // Scale vertical pairs of pixels while the last two scanlines are still warm in
652       // the CPU cache(s):
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.
656       AverageScanlines(
657           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
658           &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
659           &pixels[y * scaledWidth * BYTES_PER_PIXEL],
660           scaledWidth );
661     }
662   }
663
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;
667 }
668
669 }
670
671 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
672 {
673   DebugAssertScanlineParameters( pixels, width );
674
675   const unsigned int lastPair = EvenDown( width - 2 );
676
677   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
678   {
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];
686
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 );
691   }
692 }
693
694 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
695 {
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." );
698
699   uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
700
701   const unsigned int lastPair = EvenDown( width - 2 );
702
703   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
704   {
705     const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
706     alignedPixels[outPixel] = averaged;
707   }
708 }
709
710 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
711 {
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." );
714
715   uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
716
717   const unsigned int lastPair = EvenDown( width - 2 );
718
719   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
720   {
721     const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
722     alignedPixels[outPixel] = averaged;
723   }
724 }
725
726 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
727 {
728   DebugAssertScanlineParameters( pixels, width );
729
730   const unsigned int lastPair = EvenDown( width - 2 );
731
732   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
733   {
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];
739
740     // Save the averaged byte pixel components:
741     pixels[outPixel * 2]     = AverageComponent( c11, c21 );
742     pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
743   }
744 }
745
746 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
747 {
748   DebugAssertScanlineParameters( pixels, width );
749
750   const unsigned int lastPair = EvenDown( width - 2 );
751
752   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
753   {
754     // Load all the byte pixel components we need:
755     const unsigned int c1 = pixels[pixel];
756     const unsigned int c2 = pixels[pixel + 1];
757
758     // Save the averaged byte pixel component:
759     pixels[outPixel] = AverageComponent( c1, c2 );
760   }
761 }
762
763 /**
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.
766  */
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 )
771 {
772   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
773
774   for( unsigned int component = 0; component < width; ++component )
775   {
776     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
777   }
778 }
779
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 )
784 {
785   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
786
787   for( unsigned int component = 0; component < width * 2; ++component )
788   {
789     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
790   }
791 }
792
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 )
797 {
798   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
799
800   for( unsigned int component = 0; component < width * 3; ++component )
801   {
802     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
803   }
804 }
805
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 )
810 {
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." );
815
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);
819
820   for( unsigned int pixel = 0; pixel < width; ++pixel )
821   {
822     alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
823   }
824 }
825
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 )
830 {
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." );
835
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);
839
840   for( unsigned int pixel = 0; pixel < width; ++pixel )
841   {
842     alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
843   }
844 }
845
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,
855                            unsigned& outWidth,
856                            unsigned& outHeight )
857 {
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 )
862   {
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 )
865     {
866       const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
867
868       if( pixelFormat == Pixel::RGBA8888 )
869       {
870         Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
871       }
872       else if( pixelFormat == Pixel::RGB888 )
873       {
874         Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
875       }
876       else if( pixelFormat == Pixel::RGB565 )
877       {
878         Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
879       }
880       else if( pixelFormat == Pixel::LA88 )
881       {
882         Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
883       }
884       else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
885       {
886         Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
887       }
888       else
889       {
890         DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
891       }
892     }
893   }
894   else
895   {
896     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
897   }
898 }
899
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,
906                                  unsigned& outWidth,
907                                  unsigned& outHeight )
908 {
909   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
910 }
911
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,
918                                    unsigned& outWidth,
919                                    unsigned& outHeight )
920 {
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 );
923 }
924
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 )
933 {
934   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
935 }
936
937 /**
938  * @copydoc DownscaleInPlacePow2RGB888
939  *
940  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
941  */
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,
948                                         unsigned& outWidth,
949                                         unsigned& outHeight )
950 {
951   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
952 }
953
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 )
962 {
963   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
964 }
965
966 namespace
967 {
968
969 /**
970  * @brief Point sample an image to a new resolution (like GL_NEAREST).
971  *
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.
975  */
976 template<typename PIXEL>
977 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
978                                    unsigned int inputWidth,
979                                    unsigned int inputHeight,
980                                    uint8_t * outPixels,
981                                    unsigned int desiredWidth,
982                                    unsigned int desiredHeight )
983 {
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, ...)." );
989
990   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
991   {
992     return;
993   }
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;
998
999   unsigned int inY = 0;
1000   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1001   {
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];
1006
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) ) );
1010
1011     unsigned int inX = 0;
1012     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1013     {
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;
1019       inX += deltaX;
1020     }
1021     inY += deltaY;
1022   }
1023 }
1024
1025 }
1026
1027 // RGBA8888
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 )
1034 {
1035   PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1036 }
1037
1038 // RGB565, LA88
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 )
1045 {
1046   PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1047 }
1048
1049 // L8, A8
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 )
1056 {
1057   PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1058 }
1059
1060 /* RGB888
1061  * RGB888 is a special case as its pixels are not aligned addressable units.
1062  */
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 )
1069 {
1070   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1071   {
1072     return;
1073   }
1074   const unsigned int BYTES_PER_PIXEL = 3;
1075
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;
1079
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
1082   // coordinates:
1083   unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1084   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1085   {
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.
1090
1091     for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1092     {
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];
1096
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.
1102
1103       // Output the pixel components:
1104       outScanline[outX]     = c0;
1105       outScanline[outX + 1] = c1;
1106       outScanline[outX + 2] = c2;
1107
1108       // Increment the fixed-point input coordinate:
1109       inX += deltaX;
1110     }
1111
1112     inY += deltaY;
1113   }
1114 }
1115
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 )
1124 {
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 )
1127   {
1128     if( pixelFormat == Pixel::RGB888 )
1129     {
1130       PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1131     }
1132     else if( pixelFormat == Pixel::RGBA8888 )
1133     {
1134       PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1135     }
1136     else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1137     {
1138       PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1139     }
1140     else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1141     {
1142       PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1143     }
1144     else
1145     {
1146       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1147     }
1148   }
1149   else
1150   {
1151     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1152   }
1153 }
1154
1155 // Linear sampling group below
1156
1157 namespace
1158 {
1159
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 )
1162 {
1163   return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1164 }
1165
1166 /** @copydoc BilinearFilter1BPPByte */
1167 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1168 {
1169   Pixel2Bytes pixel;
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 );
1172   return pixel;
1173 }
1174
1175 /** @copydoc BilinearFilter1BPPByte */
1176 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1177 {
1178   Pixel3Bytes pixel;
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 );
1182   return pixel;
1183 }
1184
1185 /** @copydoc BilinearFilter1BPPByte */
1186 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1187 {
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 );
1191   return pixel;
1192 }
1193
1194 /** @copydoc BilinearFilter1BPPByte */
1195 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1196 {
1197   Pixel4Bytes pixel;
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 );
1202   return pixel;
1203 }
1204
1205 /**
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.
1209  */
1210 template<
1211   typename PIXEL,
1212   PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1213   bool DEBUG_ASSERT_ALIGNMENT
1214 >
1215 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1216                        ImageDimensions inputDimensions,
1217                        unsigned char * __restrict__ outPixels,
1218                        ImageDimensions desiredDimensions )
1219 {
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();
1224
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 )
1229   {
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, ...)." );
1232   }
1233
1234   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1235   {
1236     return;
1237   }
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;
1242
1243   unsigned int inY = 0;
1244   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1245   {
1246     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1247
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;
1252
1253     DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1254     DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1255
1256     const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1257     const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1258
1259     unsigned int inX = 0;
1260     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1261     {
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;
1265
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.
1272
1273       // Weighted bilinear filter:
1274       const unsigned int inputXWeight = inX & 65535u;
1275       outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1276
1277       inX += deltaX;
1278     }
1279     inY += deltaY;
1280   }
1281 }
1282
1283 }
1284
1285 // Format-specific linear scaling instantiations:
1286
1287 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1288                        ImageDimensions inputDimensions,
1289                        unsigned char * __restrict__ outPixels,
1290                        ImageDimensions desiredDimensions )
1291 {
1292   LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1293 }
1294
1295 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1296                        ImageDimensions inputDimensions,
1297                        unsigned char * __restrict__ outPixels,
1298                        ImageDimensions desiredDimensions )
1299 {
1300   LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1301 }
1302
1303 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1304                        ImageDimensions inputDimensions,
1305                        unsigned char * __restrict__ outPixels,
1306                        ImageDimensions desiredDimensions )
1307 {
1308   LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1309 }
1310
1311 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1312                        ImageDimensions inputDimensions,
1313                        unsigned char * __restrict__ outPixels,
1314                        ImageDimensions desiredDimensions )
1315 {
1316   LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1317 }
1318
1319 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1320                        ImageDimensions inputDimensions,
1321                        unsigned char * __restrict__ outPixels,
1322                        ImageDimensions desiredDimensions )
1323 {
1324   LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1325 }
1326
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 )
1333 {
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 )
1336   {
1337     if( pixelFormat == Pixel::RGB888 )
1338     {
1339       LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1340     }
1341     else if( pixelFormat == Pixel::RGBA8888 )
1342     {
1343       LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1344     }
1345     else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1346     {
1347       LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1348     }
1349     else if( pixelFormat == Pixel::LA88 )
1350     {
1351       LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1352     }
1353     else if ( pixelFormat == Pixel::RGB565 )
1354     {
1355       LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1356     }
1357     else
1358     {
1359       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1360     }
1361   }
1362   else
1363   {
1364     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1365   }
1366 }
1367
1368 } /* namespace Platform */
1369 } /* namespace Internal */
1370 } /* namespace Dali */