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