Revert "[4.0] Exposing Exif Image metadata"
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / portable / image-operations.cpp
1 /*
2  * Copyright (c) 2017 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 <limits>
25 #include <dali/integration-api/debug.h>
26 #include <dali/public-api/common/dali-vector.h>
27 #include <dali/public-api/math/vector2.h>
28 #include <resampler.h>
29 #include <image-loading.h>
30
31 // INTERNAL INCLUDES
32
33 namespace Dali
34 {
35 namespace Internal
36 {
37 namespace Platform
38 {
39
40 namespace
41 {
42
43 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
44 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
45 // We can optionally use a Vector4 color here, but at reduced fill speed.
46 const uint8_t BORDER_FILL_VALUE( 0x00 );
47 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
48 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
49
50 // Constants used by the ImageResampler.
51 const float DEFAULT_SOURCE_GAMMA = 1.75f;   ///< Default source gamma value used in the Resampler() function. Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
52 const float FILTER_SCALE = 1.f;             ///< Default filter scale value used in the Resampler() function. Filter scale - values < 1.0 cause aliasing, but create sharper looking mips.
53
54 using Integration::Bitmap;
55 using Integration::BitmapPtr;
56 typedef unsigned char PixelBuffer;
57
58 /**
59  * @brief 4 byte pixel structure.
60  */
61 struct Pixel4Bytes
62 {
63   uint8_t r;
64   uint8_t g;
65   uint8_t b;
66   uint8_t a;
67 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
68
69 /**
70  * @brief RGB888 pixel structure.
71  */
72 struct Pixel3Bytes
73 {
74   uint8_t r;
75   uint8_t g;
76   uint8_t b;
77 } __attribute__((packed, aligned(1)));
78
79 /**
80  * @brief RGB565 pixel typedefed from a short.
81  *
82  * Access fields by manual shifting and masking.
83  */
84 typedef uint16_t PixelRGB565;
85
86 /**
87  * @brief a Pixel composed of two independent byte components.
88  */
89 struct Pixel2Bytes
90 {
91   uint8_t l;
92   uint8_t a;
93 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
94
95
96 #if defined(DEBUG_ENABLED)
97 /**
98  * Disable logging of image operations or make it verbose from the commandline
99  * as follows (e.g., for dali demo app):
100  * <code>
101  * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
102  * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
103  * </code>
104  */
105 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
106 #endif
107
108 /** @return The greatest even number less than or equal to the argument. */
109 inline unsigned int EvenDown( const unsigned int a )
110 {
111   const unsigned int evened = a & ~1u;
112   return evened;
113 }
114
115 /**
116  * @brief Log bad parameters.
117  */
118 void ValidateScalingParameters( const unsigned int inputWidth,
119                                 const unsigned int inputHeight,
120                                 const unsigned int desiredWidth,
121                                 const unsigned int desiredHeight )
122 {
123   if( desiredWidth > inputWidth || desiredHeight > inputHeight )
124   {
125     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
126   }
127
128   if( desiredWidth == 0u || desiredHeight == 0u )
129   {
130     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
131   }
132
133   if( inputWidth == 0u || inputHeight == 0u )
134   {
135     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
136   }
137 }
138
139 /**
140  * @brief Do debug assertions common to all scanline halving functions.
141  * @note Inline and in anon namespace so should boil away in release builds.
142  */
143 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
144 {
145   DALI_ASSERT_DEBUG( pixels && "Null pointer." );
146   DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
147   DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
148 }
149
150 /**
151  * @brief Assertions on params to functions averaging pairs of scanlines.
152  * @note Inline as intended to boil away in release.
153  */
154 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
155                                                const uint8_t * const scanline2,
156                                                uint8_t* const outputScanline,
157                                                const size_t widthInComponents )
158 {
159   DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
160   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
161   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
162   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
163   DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
164 }
165
166 /**
167  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
168  */
169 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
170 {
171   BoxDimensionTest dimensionTest;
172   dimensionTest = BoxDimensionTestEither;
173
174   switch( fittingMode )
175   {
176     // Shrink to fit attempts to make one or zero dimensions smaller than the
177     // desired dimensions and one or two dimensions exactly the same as the desired
178     // ones, so as long as one dimension is larger than the desired size, box
179     // filtering can continue even if the second dimension is smaller than the
180     // desired dimensions:
181     case FittingMode::SHRINK_TO_FIT:
182     {
183       dimensionTest = BoxDimensionTestEither;
184       break;
185     }
186     // Scale to fill mode keeps both dimensions at least as large as desired:
187     case FittingMode::SCALE_TO_FILL:
188     {
189       dimensionTest = BoxDimensionTestBoth;
190       break;
191     }
192     // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
193     case FittingMode::FIT_WIDTH:
194     {
195       dimensionTest = BoxDimensionTestX;
196       break;
197     }
198     // X Dimension is ignored by definition in FIT_HEIGHT mode:
199     case FittingMode::FIT_HEIGHT:
200     {
201       dimensionTest = BoxDimensionTestY;
202       break;
203     }
204   }
205
206   return dimensionTest;
207 }
208
209 /**
210  * @brief Work out the dimensions for a uniform scaling of the input to map it
211  * into the target while effecting ShinkToFit scaling mode.
212  */
213 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
214 {
215   // Scale the input by the least extreme of the two dimensions:
216   const float widthScale  = target.GetX() / float(source.GetX());
217   const float heightScale = target.GetY() / float(source.GetY());
218   const float scale = widthScale < heightScale ? widthScale : heightScale;
219
220   // Do no scaling at all if the result would increase area:
221   if( scale >= 1.0f )
222   {
223     return source;
224   }
225
226   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
227 }
228
229 /**
230  * @brief Work out the dimensions for a uniform scaling of the input to map it
231  * into the target while effecting SCALE_TO_FILL scaling mode.
232  * @note An image scaled into the output dimensions will need either top and
233  * bottom or left and right to be cropped away unless the source was pre-cropped
234  * to match the destination aspect ratio.
235  */
236 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
237 {
238   DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0  && "Zero-area rectangles should not be passed-in" );
239   // Scale the input by the least extreme of the two dimensions:
240   const float widthScale  = target.GetX() / float(source.GetX());
241   const float heightScale = target.GetY() / float(source.GetY());
242   const float scale = widthScale > heightScale ? widthScale : heightScale;
243
244   // Do no scaling at all if the result would increase area:
245   if( scale >= 1.0f )
246   {
247     return source;
248   }
249
250   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
251 }
252
253 /**
254  * @brief Work out the dimensions for a uniform scaling of the input to map it
255  * into the target while effecting FIT_WIDTH scaling mode.
256  */
257 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
258 {
259   DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
260   const float scale  = target.GetX() / float(source.GetX());
261
262   // Do no scaling at all if the result would increase area:
263   if( scale >= 1.0f )
264   {
265    return source;
266   }
267   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
268 }
269
270 /**
271  * @brief Work out the dimensions for a uniform scaling of the input to map it
272  * into the target while effecting FIT_HEIGHT scaling mode.
273  */
274 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
275 {
276   DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
277   const float scale = target.GetY() / float(source.GetY());
278
279   // Do no scaling at all if the result would increase area:
280   if( scale >= 1.0f )
281   {
282     return source;
283   }
284
285   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
286 }
287
288 /**
289  * @brief Generate the rectangle to use as the target of a pixel sampling pass
290  * (e.g., nearest or linear).
291  */
292 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
293 {
294   ImageDimensions fitDimensions;
295   switch( fittingMode )
296   {
297     case FittingMode::SHRINK_TO_FIT:
298     {
299       fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
300       break;
301     }
302     case FittingMode::SCALE_TO_FILL:
303     {
304       fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
305       break;
306     }
307     case FittingMode::FIT_WIDTH:
308     {
309       fitDimensions = FitForFitWidth( requestedSize, sourceSize );
310       break;
311     }
312     case FittingMode::FIT_HEIGHT:
313     {
314       fitDimensions = FitForFitHeight( requestedSize, sourceSize );
315       break;
316     }
317   }
318
319   return fitDimensions;
320 }
321
322 /**
323  * @brief Calculate the number of lines on the X and Y axis that need to be
324  * either added or removed with repect to the specified fitting mode.
325  * (e.g., nearest or linear).
326  * @param[in]     sourceSize      The size of the source image
327  * @param[in]     fittingMode     The fitting mode to use
328  * @param[in/out] requestedSize   The target size that the image will be fitted to.
329  *                                If the source image is smaller than the requested size, the source is not scaled up.
330  *                                So we reduce the target size while keeping aspect by lowering resolution.
331  * @param[out]    scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
332  * @param[out]    columnsToCrop   The number of columns to remove from the image (can be negative to represent X borders required)
333  */
334 void CalculateBordersFromFittingMode(  ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
335 {
336   const unsigned int sourceWidth( sourceSize.GetWidth() );
337   const unsigned int sourceHeight( sourceSize.GetHeight() );
338   const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
339   int finalWidth = 0;
340   int finalHeight = 0;
341
342   switch( fittingMode )
343   {
344     case FittingMode::FIT_WIDTH:
345     {
346       finalWidth = sourceWidth;
347       finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
348
349       columnsToCrop = 0;
350       scanlinesToCrop = -( finalHeight - sourceHeight );
351       break;
352     }
353
354     case FittingMode::FIT_HEIGHT:
355     {
356       finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
357       finalHeight = sourceHeight;
358
359       columnsToCrop = -( finalWidth - sourceWidth );
360       scanlinesToCrop = 0;
361       break;
362     }
363
364     case FittingMode::SHRINK_TO_FIT:
365     {
366       const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
367       if( sourceAspect > targetAspect )
368       {
369         finalWidth = sourceWidth;
370         finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
371
372         columnsToCrop = 0;
373         scanlinesToCrop = -( finalHeight - sourceHeight );
374       }
375       else
376       {
377         finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
378         finalHeight = sourceHeight;
379
380         columnsToCrop = -( finalWidth - sourceWidth );
381         scanlinesToCrop = 0;
382       }
383       break;
384     }
385
386     case FittingMode::SCALE_TO_FILL:
387     {
388       const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
389       if( sourceAspect > targetAspect )
390       {
391         finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
392         finalHeight = sourceHeight;
393
394         columnsToCrop = -( finalWidth - sourceWidth );
395         scanlinesToCrop = 0;
396       }
397       else
398       {
399         finalWidth = sourceWidth;
400         finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
401
402         columnsToCrop = 0;
403         scanlinesToCrop = -( finalHeight - sourceHeight );
404       }
405       break;
406     }
407   }
408
409   requestedSize.SetWidth( finalWidth );
410   requestedSize.SetHeight( finalHeight );
411 }
412
413 /**
414  * @brief Construct a bitmap with format and dimensions requested.
415  */
416 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
417 {
418   DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
419
420   // Allocate a pixel buffer to hold the image passed in:
421   Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
422   newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
423   return newBitmap;
424 }
425
426 /**
427  * @brief Construct a bitmap object from a copy of the pixel array passed in.
428  */
429 BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
430 {
431   DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
432
433   // Allocate a pixel buffer to hold the image passed in:
434   Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
435
436   // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
437   memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
438   return newBitmap;
439 }
440
441 /**
442  * @brief Work out the desired width and height, accounting for zeros.
443  *
444  * @param[in] bitmapWidth Width of image before processing.
445  * @param[in] bitmapHeight Height of image before processing.
446  * @param[in] requestedWidth Width of area to scale image into. Can be zero.
447  * @param[in] requestedHeight Height of area to scale image into. Can be zero.
448  * @return Dimensions of area to scale image into after special rules are applied.
449  */
450 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
451 {
452   unsigned int maxSize = Dali::GetMaxTextureSize();
453
454   // If no dimensions have been requested, default to the source ones:
455   if( requestedWidth == 0 && requestedHeight == 0 )
456   {
457     if( bitmapWidth <= maxSize && bitmapHeight <= maxSize )
458     {
459       return ImageDimensions( bitmapWidth, bitmapHeight );
460     }
461     else
462     {
463       // Calculate the size from the max texture size and the source image aspect ratio
464       if( bitmapWidth > bitmapHeight )
465       {
466         return ImageDimensions( maxSize, bitmapHeight * maxSize / static_cast< float >( bitmapWidth ) + 0.5f );
467       }
468       else
469       {
470         return ImageDimensions( bitmapWidth * maxSize / static_cast< float >( bitmapHeight ) + 0.5f, maxSize );
471       }
472     }
473   }
474
475   // If both dimensions have values requested, use them both:
476   if( requestedWidth != 0 && requestedHeight != 0 )
477   {
478     if( requestedWidth <= maxSize && requestedHeight <= maxSize )
479     {
480       return ImageDimensions( requestedWidth, requestedHeight );
481     }
482     else
483     {
484       // Calculate the size from the max texture size and the source image aspect ratio
485       if( requestedWidth > requestedHeight )
486       {
487         return ImageDimensions( maxSize, requestedHeight * maxSize / static_cast< float >( requestedWidth ) + 0.5f );
488       }
489       else
490       {
491         return ImageDimensions( requestedWidth * maxSize / static_cast< float >( requestedHeight ) + 0.5f, maxSize );
492       }
493     }
494   }
495
496   // Only one of the dimensions has been requested. Calculate the other from
497   // the requested one and the source image aspect ratio:
498   if( requestedWidth != 0 )
499   {
500     requestedWidth = std::min( requestedWidth, maxSize );
501     return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
502   }
503
504   requestedHeight = std::min( requestedHeight, maxSize );
505   return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
506 }
507
508 } // namespace - unnamed
509
510 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
511 {
512   return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
513 }
514
515 /**
516  * @brief Apply cropping and padding for specified fitting mode.
517  *
518  * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
519  * based on the fitting mode.
520  *
521  * This will add vertical or horizontal borders if necessary.
522  * Crop the source image data vertically or horizontally if necessary.
523  * The aspect of the source image is preserved.
524  * If the source image is smaller than the desired size, the algorithm will modify the the newly created
525  *   bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
526  *   GPU scaling to be performed at render time giving the same result with less texture traversal.
527  *
528  * @param[in] bitmap            The source bitmap to perform modifications on.
529  * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
530  * @param[in] fittingMode       The fitting mode to use.
531  *
532  * @return                      A new bitmap with the padding and cropping required for fitting mode applied.
533  *                              If no modification is needed or possible, the passed in bitmap is returned.
534  */
535 Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
536
537 /**
538  * @brief Adds horizontal or vertical borders to the source image.
539  *
540  * @param[in] targetPixels     The destination image pointer to draw the borders on.
541  * @param[in] bytesPerPixel    The number of bytes per pixel of the target pixel buffer.
542  * @param[in] targetDimensions The dimensions of the destination image.
543  * @param[in] padDimensions    The columns and scanlines to pad with borders.
544  */
545 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
546
547 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
548 {
549   if( bitmap )
550   {
551     // Calculate the desired box, accounting for a possible zero component:
552     const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
553
554     // If a different size than the raw one has been requested, resize the image
555     // maximally using a repeated box filter without making it smaller than the
556     // requested size in either dimension:
557     bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode );
558
559     // Cut the bitmap according to the desired width and height so that the
560     // resulting bitmap has the same aspect ratio as the desired dimensions.
561     // Add crop and add borders if necessary depending on fitting mode.
562     if( bitmap && bitmap->GetPackedPixelsProfile() )
563     {
564       bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
565     }
566
567     // Examine the image pixels remaining after cropping and scaling to see if all
568     // are opaque, allowing faster rendering, or some have non-1.0 alpha:
569     if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
570     {
571       bitmap->GetPackedPixelsProfile()->TestForTransparency();
572     }
573   }
574
575   return bitmap;
576 }
577
578 BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
579 {
580   const unsigned int inputWidth = bitmap->GetImageWidth();
581   const unsigned int inputHeight = bitmap->GetImageHeight();
582
583   if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
584   {
585     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
586   }
587   else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
588   {
589     // Calculate any padding or cropping that needs to be done based on the fitting mode.
590     // Note: If the desired size is larger than the original image, the desired size will be
591     // reduced while maintaining the aspect, in order to save unnecessary memory usage.
592     int scanlinesToCrop = 0;
593     int columnsToCrop = 0;
594
595     CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
596
597     unsigned int desiredWidth( desiredDimensions.GetWidth() );
598     unsigned int desiredHeight( desiredDimensions.GetHeight() );
599
600     // Action the changes by making a new bitmap with the central part of the loaded one if required.
601     if( scanlinesToCrop != 0 || columnsToCrop != 0 )
602     {
603       // Split the adding and removing of scanlines and columns into separate variables,
604       // so we can use one piece of generic code to action the changes.
605       unsigned int scanlinesToPad = 0;
606       unsigned int columnsToPad = 0;
607       if( scanlinesToCrop < 0 )
608       {
609         scanlinesToPad = -scanlinesToCrop;
610         scanlinesToCrop = 0;
611       }
612       if( columnsToCrop < 0 )
613       {
614         columnsToPad = -columnsToCrop;
615         columnsToCrop = 0;
616       }
617
618       // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
619       if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
620           ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
621       {
622         DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
623         return bitmap;
624       }
625
626       // Create a new bitmap with the desired size.
627       BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
628       Integration::Bitmap::PackedPixelsProfile *packedView = croppedBitmap->GetPackedPixelsProfile();
629       DALI_ASSERT_DEBUG( packedView );
630       const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
631       packedView->ReserveBuffer( pixelFormat, desiredWidth, desiredHeight, desiredWidth, desiredHeight );
632
633       // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
634       // The cropping is added to the source pointer, and the padding is added to the destination.
635       const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
636       const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
637       PixelBuffer * const targetPixels = croppedBitmap->GetBuffer();
638       PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
639       DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
640
641       // Copy the image data to the new bitmap.
642       // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
643       unsigned int outputSpan( desiredWidth * bytesPerPixel );
644       if( columnsToCrop == 0 && columnsToPad == 0 )
645       {
646         memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
647       }
648       else
649       {
650         // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
651         // Precalculate any constants to optimize the inner loop.
652         const unsigned int inputSpan( inputWidth * bytesPerPixel );
653         const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
654         const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
655
656         for( unsigned int y = 0; y < scanlinesToCopy; ++y )
657         {
658           memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
659         }
660       }
661
662       // Add vertical or horizontal borders to the final image (if required).
663       desiredDimensions.SetWidth( desiredWidth );
664       desiredDimensions.SetHeight( desiredHeight );
665       AddBorders( croppedBitmap->GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
666       // Overwrite the loaded bitmap with the cropped version
667       bitmap = croppedBitmap;
668     }
669   }
670
671   return bitmap;
672 }
673
674 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
675 {
676   // Assign ints for faster access.
677   unsigned int desiredWidth( targetDimensions.GetWidth() );
678   unsigned int desiredHeight( targetDimensions.GetHeight() );
679   unsigned int columnsToPad( padDimensions.GetWidth() );
680   unsigned int scanlinesToPad( padDimensions.GetHeight() );
681   unsigned int outputSpan( desiredWidth * bytesPerPixel );
682
683   // Add letterboxing (symmetrical borders) if needed.
684   if( scanlinesToPad > 0 )
685   {
686     // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
687     memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
688
689     // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
690     // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
691     unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
692
693     // Bottom border.
694     memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
695   }
696   else if( columnsToPad > 0 )
697   {
698     // Add a left and right border.
699     // Left:
700     // Pre-calculate span size outside of loop.
701     unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
702     for( unsigned int y = 0; y < desiredHeight; ++y )
703     {
704       memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
705     }
706
707     // Right:
708     // Pre-calculate the initial x offset as it is always the same for a small optimization.
709     // We subtract columnsToPad/2 from columnsToPad so that we have the correct
710     // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
711     unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
712     PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
713     unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
714
715     for( unsigned int y = 0; y < desiredHeight; ++y )
716     {
717       memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
718     }
719   }
720 }
721
722 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
723                                         ImageDimensions desired,
724                                         FittingMode::Type fittingMode,
725                                         SamplingMode::Type samplingMode )
726 {
727   // Source dimensions as loaded from resources (e.g. filesystem):
728   const unsigned int bitmapWidth  = bitmap.GetImageWidth();
729   const unsigned int bitmapHeight = bitmap.GetImageHeight();
730   // Desired dimensions (the rectangle to fit the source image to):
731   const unsigned int desiredWidth = desired.GetWidth();
732   const unsigned int desiredHeight = desired.GetHeight();
733
734   BitmapPtr outputBitmap( &bitmap );
735
736   // If a different size than the raw one has been requested, resize the image:
737   if( bitmap.GetPackedPixelsProfile() &&
738       (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
739       ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
740   {
741     const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
742
743     // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
744     unsigned int shrunkWidth = -1, shrunkHeight = -1;
745     DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
746
747     // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
748     const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
749     const unsigned int filteredWidth = filteredDimensions.GetWidth();
750     const unsigned int filteredHeight = filteredDimensions.GetHeight();
751
752     // Run a filter to scale down the bitmap if it needs it:
753     bool filtered = false;
754     if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
755     {
756       if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
757           samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
758       {
759         outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
760         if( outputBitmap )
761         {
762           if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
763           {
764             LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
765           }
766           else
767           {
768             PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
769           }
770           filtered = true;
771         }
772       }
773     }
774     // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
775     if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
776     {
777       outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
778     }
779   }
780
781   return outputBitmap;
782 }
783
784 namespace
785 {
786 /**
787  * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
788  * @param test Which combination of the two dimensions matter for terminating the filtering.
789  * @param scaledWidth The width of the current downscaled image.
790  * @param scaledHeight The height of the current downscaled image.
791  * @param desiredWidth The target width for the downscaling.
792  * @param desiredHeight The target height for the downscaling.
793  */
794 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
795 {
796   bool keepScaling = false;
797   const unsigned int nextWidth = scaledWidth >> 1u;
798   const unsigned int nextHeight = scaledHeight >> 1u;
799
800   if( nextWidth >= 1u && nextHeight >= 1u )
801   {
802     switch( test )
803     {
804       case BoxDimensionTestEither:
805       {
806         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
807         break;
808       }
809       case BoxDimensionTestBoth:
810       {
811         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
812         break;
813       }
814       case BoxDimensionTestX:
815       {
816         keepScaling = nextWidth >= desiredWidth;
817         break;
818       }
819       case BoxDimensionTestY:
820       {
821         keepScaling = nextHeight >= desiredHeight;
822         break;
823       }
824     }
825   }
826
827   return keepScaling;
828 }
829
830 /**
831  * @brief A shared implementation of the overall iterative box filter
832  * downscaling algorithm.
833  *
834  * Specialise this for particular pixel formats by supplying the number of bytes
835  * per pixel and two functions: one for averaging pairs of neighbouring pixels
836  * on a single scanline, and a second for averaging pixels at corresponding
837  * positions on different scanlines.
838  **/
839 template<
840   int BYTES_PER_PIXEL,
841   void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
842   void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
843 >
844 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
845                                   const unsigned int inputWidth,
846                                   const unsigned int inputHeight,
847                                   const unsigned int desiredWidth,
848                                   const unsigned int desiredHeight,
849                                   BoxDimensionTest dimensionTest,
850                                   unsigned& outWidth,
851                                   unsigned& outHeight )
852 {
853   if( pixels == 0 )
854   {
855     return;
856   }
857   ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
858
859   // Scale the image until it would be smaller than desired, stopping if the
860   // resulting height or width would be less than 1:
861   unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
862   while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
863   {
864     const unsigned int lastWidth = scaledWidth;
865     scaledWidth  >>= 1u;
866     scaledHeight >>= 1u;
867
868     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
869
870     const unsigned int lastScanlinePair = scaledHeight - 1;
871
872     // Scale pairs of scanlines until any spare one at the end is dropped:
873     for( unsigned int y = 0; y <= lastScanlinePair; ++y )
874     {
875       // Scale two scanlines horizontally:
876       HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
877       HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
878
879       // Scale vertical pairs of pixels while the last two scanlines are still warm in
880       // the CPU cache(s):
881       // Note, better access patterns for cache-coherence are possible for very large
882       // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
883       // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
884       AverageScanlines(
885           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
886           &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
887           &pixels[y * scaledWidth * BYTES_PER_PIXEL],
888           scaledWidth );
889     }
890   }
891
892   ///@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.
893   outWidth = scaledWidth;
894   outHeight = scaledHeight;
895 }
896
897 }
898
899 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
900 {
901   DebugAssertScanlineParameters( pixels, width );
902
903   const unsigned int lastPair = EvenDown( width - 2 );
904
905   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
906   {
907     // Load all the byte pixel components we need:
908     const unsigned int c11 = pixels[pixel * 3];
909     const unsigned int c12 = pixels[pixel * 3 + 1];
910     const unsigned int c13 = pixels[pixel * 3 + 2];
911     const unsigned int c21 = pixels[pixel * 3 + 3];
912     const unsigned int c22 = pixels[pixel * 3 + 4];
913     const unsigned int c23 = pixels[pixel * 3 + 5];
914
915     // Save the averaged byte pixel components:
916     pixels[outPixel * 3]     = AverageComponent( c11, c21 );
917     pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
918     pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
919   }
920 }
921
922 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
923 {
924   DebugAssertScanlineParameters( pixels, width );
925   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
926
927   uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
928
929   const unsigned int lastPair = EvenDown( width - 2 );
930
931   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
932   {
933     const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
934     alignedPixels[outPixel] = averaged;
935   }
936 }
937
938 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
939 {
940   DebugAssertScanlineParameters( pixels, width );
941   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
942
943   uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
944
945   const unsigned int lastPair = EvenDown( width - 2 );
946
947   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
948   {
949     const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
950     alignedPixels[outPixel] = averaged;
951   }
952 }
953
954 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
955 {
956   DebugAssertScanlineParameters( pixels, width );
957
958   const unsigned int lastPair = EvenDown( width - 2 );
959
960   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
961   {
962     // Load all the byte pixel components we need:
963     const unsigned int c11 = pixels[pixel * 2];
964     const unsigned int c12 = pixels[pixel * 2 + 1];
965     const unsigned int c21 = pixels[pixel * 2 + 2];
966     const unsigned int c22 = pixels[pixel * 2 + 3];
967
968     // Save the averaged byte pixel components:
969     pixels[outPixel * 2]     = AverageComponent( c11, c21 );
970     pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
971   }
972 }
973
974 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
975 {
976   DebugAssertScanlineParameters( pixels, width );
977
978   const unsigned int lastPair = EvenDown( width - 2 );
979
980   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
981   {
982     // Load all the byte pixel components we need:
983     const unsigned int c1 = pixels[pixel];
984     const unsigned int c2 = pixels[pixel + 1];
985
986     // Save the averaged byte pixel component:
987     pixels[outPixel] = AverageComponent( c1, c2 );
988   }
989 }
990
991 /**
992  * @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.
993  * 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.
994  */
995 void AverageScanlines1( const unsigned char * const scanline1,
996                         const unsigned char * const __restrict__ scanline2,
997                         unsigned char* const outputScanline,
998                         const unsigned int width )
999 {
1000   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
1001
1002   for( unsigned int component = 0; component < width; ++component )
1003   {
1004     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1005   }
1006 }
1007
1008 void AverageScanlines2( const unsigned char * const scanline1,
1009                         const unsigned char * const __restrict__ scanline2,
1010                         unsigned char* const outputScanline,
1011                         const unsigned int width )
1012 {
1013   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1014
1015   for( unsigned int component = 0; component < width * 2; ++component )
1016   {
1017     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1018   }
1019 }
1020
1021 void AverageScanlines3( const unsigned char * const scanline1,
1022                         const unsigned char * const __restrict__ scanline2,
1023                         unsigned char* const outputScanline,
1024                         const unsigned int width )
1025 {
1026   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
1027
1028   for( unsigned int component = 0; component < width * 3; ++component )
1029   {
1030     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1031   }
1032 }
1033
1034 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
1035                                const unsigned char * const __restrict__ scanline2,
1036                                unsigned char * const outputScanline,
1037                                const unsigned int width )
1038 {
1039   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
1040   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1041   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1042   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1043
1044   const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1045   const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1046   uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1047
1048   for( unsigned int pixel = 0; pixel < width; ++pixel )
1049   {
1050     alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1051   }
1052 }
1053
1054 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1055                              const unsigned char * const __restrict__ scanline2,
1056                              unsigned char * const outputScanline,
1057                              const unsigned int width )
1058 {
1059   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1060   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1061   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1062   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1063
1064   const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1065   const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1066   uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1067
1068   for( unsigned int pixel = 0; pixel < width; ++pixel )
1069   {
1070     alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1071   }
1072 }
1073
1074 /// Dispatch to pixel format appropriate box filter downscaling functions.
1075 void DownscaleInPlacePow2( unsigned char * const pixels,
1076                            Pixel::Format pixelFormat,
1077                            unsigned int inputWidth,
1078                            unsigned int inputHeight,
1079                            unsigned int desiredWidth,
1080                            unsigned int desiredHeight,
1081                            FittingMode::Type fittingMode,
1082                            SamplingMode::Type samplingMode,
1083                            unsigned& outWidth,
1084                            unsigned& outHeight )
1085 {
1086   outWidth = inputWidth;
1087   outHeight = inputHeight;
1088   // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1089   if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1090   {
1091     // Check the pixel format is one that is supported:
1092     if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1093     {
1094       const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1095
1096       if( pixelFormat == Pixel::RGBA8888 )
1097       {
1098         Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1099       }
1100       else if( pixelFormat == Pixel::RGB888 )
1101       {
1102         Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1103       }
1104       else if( pixelFormat == Pixel::RGB565 )
1105       {
1106         Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1107       }
1108       else if( pixelFormat == Pixel::LA88 )
1109       {
1110         Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1111       }
1112       else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1113       {
1114         Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1115       }
1116       else
1117       {
1118         DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1119       }
1120     }
1121   }
1122   else
1123   {
1124     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1125   }
1126 }
1127
1128 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1129                                  unsigned int inputWidth,
1130                                  unsigned int inputHeight,
1131                                  unsigned int desiredWidth,
1132                                  unsigned int desiredHeight,
1133                                  BoxDimensionTest dimensionTest,
1134                                  unsigned& outWidth,
1135                                  unsigned& outHeight )
1136 {
1137   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1138 }
1139
1140 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1141                                    unsigned int inputWidth,
1142                                    unsigned int inputHeight,
1143                                    unsigned int desiredWidth,
1144                                    unsigned int desiredHeight,
1145                                    BoxDimensionTest dimensionTest,
1146                                    unsigned& outWidth,
1147                                    unsigned& outHeight )
1148 {
1149   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1150   DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1151 }
1152
1153 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1154                                  unsigned int inputWidth,
1155                                  unsigned int inputHeight,
1156                                  unsigned int desiredWidth,
1157                                  unsigned int desiredHeight,
1158                                  BoxDimensionTest dimensionTest,
1159                                  unsigned int& outWidth,
1160                                  unsigned int& outHeight )
1161 {
1162   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1163 }
1164
1165 /**
1166  * @copydoc DownscaleInPlacePow2RGB888
1167  *
1168  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1169  */
1170 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1171                                         unsigned int inputWidth,
1172                                         unsigned int inputHeight,
1173                                         unsigned int desiredWidth,
1174                                         unsigned int desiredHeight,
1175                                         BoxDimensionTest dimensionTest,
1176                                         unsigned& outWidth,
1177                                         unsigned& outHeight )
1178 {
1179   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1180 }
1181
1182 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1183                                              unsigned int inputWidth,
1184                                              unsigned int inputHeight,
1185                                              unsigned int desiredWidth,
1186                                              unsigned int desiredHeight,
1187                                              BoxDimensionTest dimensionTest,
1188                                              unsigned int& outWidth,
1189                                              unsigned int& outHeight )
1190 {
1191   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1192 }
1193
1194 namespace
1195 {
1196
1197 /**
1198  * @brief Point sample an image to a new resolution (like GL_NEAREST).
1199  *
1200  * Template is used purely as a type-safe code generator in this one
1201  * compilation unit. Generated code is inlined into type-specific wrapper
1202  * functions below which are exported to rest of module.
1203  */
1204 template<typename PIXEL>
1205 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1206                                    unsigned int inputWidth,
1207                                    unsigned int inputHeight,
1208                                    uint8_t * outPixels,
1209                                    unsigned int desiredWidth,
1210                                    unsigned int desiredHeight )
1211 {
1212   DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1213       outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1214       "The input and output buffers must not overlap for an upscaling.");
1215   DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( inPixels )  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1216   DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( outPixels ) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1217
1218   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1219   {
1220     return;
1221   }
1222   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1223   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
1224   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1225   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1226
1227   unsigned int inY = 0;
1228   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1229   {
1230     // Round fixed point y coordinate to nearest integer:
1231     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1232     const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1233     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1234
1235     DALI_ASSERT_DEBUG( integerY < inputHeight );
1236     DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1237     DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1238
1239     unsigned int inX = 0;
1240     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1241     {
1242       // Round the fixed-point x coordinate to an integer:
1243       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1244       const PIXEL* const inPixelAddress = &inScanline[integerX];
1245       const PIXEL pixel = *inPixelAddress;
1246       outScanline[outX] = pixel;
1247       inX += deltaX;
1248     }
1249     inY += deltaY;
1250   }
1251 }
1252
1253 }
1254
1255 // RGBA8888
1256 void PointSample4BPP( const unsigned char * inPixels,
1257                       unsigned int inputWidth,
1258                       unsigned int inputHeight,
1259                       unsigned char * outPixels,
1260                       unsigned int desiredWidth,
1261                       unsigned int desiredHeight )
1262 {
1263   PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1264 }
1265
1266 // RGB565, LA88
1267 void PointSample2BPP( const unsigned char * inPixels,
1268                       unsigned int inputWidth,
1269                       unsigned int inputHeight,
1270                       unsigned char * outPixels,
1271                       unsigned int desiredWidth,
1272                       unsigned int desiredHeight )
1273 {
1274   PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1275 }
1276
1277 // L8, A8
1278 void PointSample1BPP( const unsigned char * inPixels,
1279                       unsigned int inputWidth,
1280                       unsigned int inputHeight,
1281                       unsigned char * outPixels,
1282                       unsigned int desiredWidth,
1283                       unsigned int desiredHeight )
1284 {
1285   PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1286 }
1287
1288 /* RGB888
1289  * RGB888 is a special case as its pixels are not aligned addressable units.
1290  */
1291 void PointSample3BPP( const uint8_t * inPixels,
1292                       unsigned int inputWidth,
1293                       unsigned int inputHeight,
1294                       uint8_t * outPixels,
1295                       unsigned int desiredWidth,
1296                       unsigned int desiredHeight )
1297 {
1298   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1299   {
1300     return;
1301   }
1302   const unsigned int BYTES_PER_PIXEL = 3;
1303
1304   // Generate fixed-point 16.16 deltas in input image coordinates:
1305   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1306   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1307
1308   // Step through output image in whole integer pixel steps while tracking the
1309   // corresponding locations in the input image using 16.16 fixed-point
1310   // coordinates:
1311   unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1312   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1313   {
1314     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1315     const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1316     uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1317     unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1318
1319     for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1320     {
1321       // Round the fixed-point input coordinate to the address of the input pixel to sample:
1322       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1323       const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1324
1325       // Issue loads for all pixel color components up-front:
1326       const unsigned int c0 = inPixelAddress[0];
1327       const unsigned int c1 = inPixelAddress[1];
1328       const unsigned int c2 = inPixelAddress[2];
1329       ///@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.
1330
1331       // Output the pixel components:
1332       outScanline[outX]     = c0;
1333       outScanline[outX + 1] = c1;
1334       outScanline[outX + 2] = c2;
1335
1336       // Increment the fixed-point input coordinate:
1337       inX += deltaX;
1338     }
1339
1340     inY += deltaY;
1341   }
1342 }
1343
1344 // Dispatch to a format-appropriate point sampling function:
1345 void PointSample( const unsigned char * inPixels,
1346                   unsigned int inputWidth,
1347                   unsigned int inputHeight,
1348                   Pixel::Format pixelFormat,
1349                   unsigned char * outPixels,
1350                   unsigned int desiredWidth,
1351                   unsigned int desiredHeight )
1352 {
1353   // Check the pixel format is one that is supported:
1354   if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1355   {
1356     if( pixelFormat == Pixel::RGB888 )
1357     {
1358       PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1359     }
1360     else if( pixelFormat == Pixel::RGBA8888 )
1361     {
1362       PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1363     }
1364     else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1365     {
1366       PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1367     }
1368     else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1369     {
1370       PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1371     }
1372     else
1373     {
1374       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1375     }
1376   }
1377   else
1378   {
1379     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1380   }
1381 }
1382
1383 // Linear sampling group below
1384
1385 namespace
1386 {
1387
1388 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1389 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1390 {
1391   return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1392 }
1393
1394 /** @copydoc BilinearFilter1BPPByte */
1395 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1396 {
1397   Pixel2Bytes pixel;
1398   pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1399   pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1400   return pixel;
1401 }
1402
1403 /** @copydoc BilinearFilter1BPPByte */
1404 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1405 {
1406   Pixel3Bytes pixel;
1407   pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1408   pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1409   pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1410   return pixel;
1411 }
1412
1413 /** @copydoc BilinearFilter1BPPByte */
1414 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1415 {
1416   const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1417                             (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1418                              BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1419   return pixel;
1420 }
1421
1422 /** @copydoc BilinearFilter1BPPByte */
1423 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1424 {
1425   Pixel4Bytes pixel;
1426   pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1427   pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1428   pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1429   pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1430   return pixel;
1431 }
1432
1433 /**
1434  * @brief Generic version of bilinear sampling image resize function.
1435  * @note Limited to one compilation unit and exposed through type-specific
1436  * wrapper functions below.
1437  */
1438 template<
1439   typename PIXEL,
1440   PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1441   bool DEBUG_ASSERT_ALIGNMENT
1442 >
1443 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1444                        ImageDimensions inputDimensions,
1445                        unsigned char * __restrict__ outPixels,
1446                        ImageDimensions desiredDimensions )
1447 {
1448   const unsigned int inputWidth = inputDimensions.GetWidth();
1449   const unsigned int inputHeight = inputDimensions.GetHeight();
1450   const unsigned int desiredWidth = desiredDimensions.GetWidth();
1451   const unsigned int desiredHeight = desiredDimensions.GetHeight();
1452
1453   DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth   * inputHeight   * sizeof(PIXEL)) ||
1454                       (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1455                      "Input and output buffers cannot overlap.");
1456   if( DEBUG_ASSERT_ALIGNMENT )
1457   {
1458     DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( inPixels )  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1459     DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
1460   }
1461
1462   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1463   {
1464     return;
1465   }
1466   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1467   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
1468   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1469   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1470
1471   unsigned int inY = 0;
1472   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1473   {
1474     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1475
1476     // Find the two scanlines to blend and the weight to blend with:
1477     const unsigned int integerY1 = inY >> 16u;
1478     const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1479     const unsigned int inputYWeight = inY & 65535u;
1480
1481     DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1482     DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1483
1484     const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1485     const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1486
1487     unsigned int inX = 0;
1488     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1489     {
1490       // Work out the two pixel scanline offsets for this cluster of four samples:
1491       const unsigned int integerX1 = inX >> 16u;
1492       const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1493
1494       // Execute the loads:
1495       const PIXEL pixel1 = inScanline1[integerX1];
1496       const PIXEL pixel2 = inScanline2[integerX1];
1497       const PIXEL pixel3 = inScanline1[integerX2];
1498       const PIXEL pixel4 = inScanline2[integerX2];
1499       ///@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.
1500
1501       // Weighted bilinear filter:
1502       const unsigned int inputXWeight = inX & 65535u;
1503       outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1504
1505       inX += deltaX;
1506     }
1507     inY += deltaY;
1508   }
1509 }
1510
1511 }
1512
1513 // Format-specific linear scaling instantiations:
1514
1515 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1516                        ImageDimensions inputDimensions,
1517                        unsigned char * __restrict__ outPixels,
1518                        ImageDimensions desiredDimensions )
1519 {
1520   LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1521 }
1522
1523 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1524                        ImageDimensions inputDimensions,
1525                        unsigned char * __restrict__ outPixels,
1526                        ImageDimensions desiredDimensions )
1527 {
1528   LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1529 }
1530
1531 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1532                        ImageDimensions inputDimensions,
1533                        unsigned char * __restrict__ outPixels,
1534                        ImageDimensions desiredDimensions )
1535 {
1536   LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1537 }
1538
1539 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1540                        ImageDimensions inputDimensions,
1541                        unsigned char * __restrict__ outPixels,
1542                        ImageDimensions desiredDimensions )
1543 {
1544   LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1545 }
1546
1547 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1548                        ImageDimensions inputDimensions,
1549                        unsigned char * __restrict__ outPixels,
1550                        ImageDimensions desiredDimensions )
1551 {
1552   LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1553 }
1554
1555
1556 void Resample( const unsigned char * __restrict__ inPixels,
1557                ImageDimensions inputDimensions,
1558                unsigned char * __restrict__ outPixels,
1559                ImageDimensions desiredDimensions,
1560                Resampler::Filter filterType,
1561                int numChannels, bool hasAlpha )
1562 {
1563   // Got from the test.cpp of the ImageResampler lib.
1564   const float ONE_DIV_255 = 1.0f / 255.0f;
1565   const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1566   const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1567   const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
1568
1569   static bool loadColorSpaces = true;
1570   static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1571   static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1572
1573   if( loadColorSpaces ) // Only create the color space conversions on the first execution
1574   {
1575     loadColorSpaces = false;
1576
1577     for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
1578     {
1579       srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
1580     }
1581
1582     const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
1583     const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1584
1585     for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
1586     {
1587       int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
1588       if( k < 0 )
1589       {
1590         k = 0;
1591       }
1592       else if( k > MAX_UNSIGNED_CHAR )
1593       {
1594         k = MAX_UNSIGNED_CHAR;
1595       }
1596       linearToSrgb[i] = static_cast<unsigned char>( k );
1597     }
1598   }
1599
1600   Resampler* resamplers[numChannels];
1601   Vector<float> samples[numChannels];
1602
1603   const int srcWidth = inputDimensions.GetWidth();
1604   const int srcHeight = inputDimensions.GetHeight();
1605   const int dstWidth = desiredDimensions.GetWidth();
1606   const int dstHeight = desiredDimensions.GetHeight();
1607
1608   // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1609   // used for the other components (a memory and slight cache efficiency optimization).
1610   resamplers[0] = new Resampler( srcWidth,
1611                                  srcHeight,
1612                                  dstWidth,
1613                                  dstHeight,
1614                                  Resampler::BOUNDARY_CLAMP,
1615                                  0.0f,           // sample_low,
1616                                  1.0f,           // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1617                                  filterType,    // The type of filter.
1618                                  NULL,           // Pclist_x,
1619                                  NULL,           // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1620                                  FILTER_SCALE,   // src_x_ofs,
1621                                  FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1622   samples[0].Resize( srcWidth );
1623   for( int i = 1; i < numChannels; ++i )
1624   {
1625     resamplers[i] = new Resampler( srcWidth,
1626                                    srcHeight,
1627                                    dstWidth,
1628                                    dstHeight,
1629                                    Resampler::BOUNDARY_CLAMP,
1630                                    0.0f,
1631                                    1.0f,
1632                                    filterType,
1633                                    resamplers[0]->get_clist_x(),
1634                                    resamplers[0]->get_clist_y(),
1635                                    FILTER_SCALE,
1636                                    FILTER_SCALE );
1637     samples[i].Resize( srcWidth );
1638   }
1639
1640   const int srcPitch = srcWidth * numChannels;
1641   const int dstPitch = dstWidth * numChannels;
1642   int dstY = 0;
1643
1644   for( int srcY = 0; srcY < srcHeight; ++srcY )
1645   {
1646     const unsigned char* pSrc = &inPixels[srcY * srcPitch];
1647
1648     for( int x = 0; x < srcWidth; ++x )
1649     {
1650       for( int c = 0; c < numChannels; ++c )
1651       {
1652         if( c == ALPHA_CHANNEL && hasAlpha )
1653         {
1654           samples[c][x] = *pSrc++ * ONE_DIV_255;
1655         }
1656         else
1657         {
1658           samples[c][x] = srgbToLinear[*pSrc++];
1659         }
1660       }
1661     }
1662
1663     for( int c = 0; c < numChannels; ++c )
1664     {
1665       if( !resamplers[c]->put_line( &samples[c][0] ) )
1666       {
1667         DALI_ASSERT_DEBUG( !"Out of memory" );
1668       }
1669     }
1670
1671     for(;;)
1672     {
1673       int compIndex;
1674       for( compIndex = 0; compIndex < numChannels; ++compIndex )
1675       {
1676         const float* pOutputSamples = resamplers[compIndex]->get_line();
1677         if( !pOutputSamples )
1678         {
1679           break;
1680         }
1681
1682         const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
1683         DALI_ASSERT_DEBUG( dstY < dstHeight );
1684         unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
1685
1686         for( int x = 0; x < dstWidth; ++x )
1687         {
1688           if( isAlphaChannel )
1689           {
1690             int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
1691             if( c < 0 )
1692             {
1693               c = 0;
1694             }
1695             else if( c > MAX_UNSIGNED_CHAR )
1696             {
1697               c = MAX_UNSIGNED_CHAR;
1698             }
1699             *pDst = static_cast<unsigned char>( c );
1700           }
1701           else
1702           {
1703             int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
1704             if( j < 0 )
1705             {
1706               j = 0;
1707             }
1708             else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
1709             {
1710               j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
1711             }
1712             *pDst = linearToSrgb[j];
1713           }
1714
1715           pDst += numChannels;
1716         }
1717       }
1718       if( compIndex < numChannels )
1719       {
1720         break;
1721       }
1722
1723       ++dstY;
1724     }
1725   }
1726
1727   // Delete the resamplers.
1728   for( int i = 0; i < numChannels; ++i )
1729   {
1730     delete resamplers[i];
1731   }
1732 }
1733
1734 void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
1735                         ImageDimensions inputDimensions,
1736                         unsigned char * __restrict__ outPixels,
1737                         ImageDimensions desiredDimensions )
1738 {
1739   Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true );
1740 }
1741
1742 void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
1743                         ImageDimensions inputDimensions,
1744                         unsigned char * __restrict__ outPixels,
1745                         ImageDimensions desiredDimensions )
1746 {
1747   // For L8 images
1748   Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false );
1749 }
1750
1751 // Dispatch to a format-appropriate linear sampling function:
1752 void LinearSample( const unsigned char * __restrict__ inPixels,
1753                    ImageDimensions inDimensions,
1754                    Pixel::Format pixelFormat,
1755                    unsigned char * __restrict__ outPixels,
1756                    ImageDimensions outDimensions )
1757 {
1758   // Check the pixel format is one that is supported:
1759   if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1760   {
1761     if( pixelFormat == Pixel::RGB888 )
1762     {
1763       LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1764     }
1765     else if( pixelFormat == Pixel::RGBA8888 )
1766     {
1767       LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1768     }
1769     else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1770     {
1771       LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1772     }
1773     else if( pixelFormat == Pixel::LA88 )
1774     {
1775       LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1776     }
1777     else if ( pixelFormat == Pixel::RGB565 )
1778     {
1779       LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1780     }
1781     else
1782     {
1783       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1784     }
1785   }
1786   else
1787   {
1788     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1789   }
1790 }
1791
1792 } /* namespace Platform */
1793 } /* namespace Internal */
1794 } /* namespace Dali */